HTTP API

Call the hosted API with a bearer key: the session API, the twelve tools, quotes, identity, files and webhooks, with limits and billing.

Updated

The HTTP API is how a backend reaches Wavemaker. One host serves the hosted agent (the session API), the twelve tools, quotes, identity, file transfer and webhooks. The web app's chat runs on the same session service, and MCP hosts, the CLI and the SDKs call the same tools, so a call behaves the same from every surface.

This page covers authentication, a worked example, the endpoint reference grouped by resource, errors and limits, and billing. To have the agent make a whole ad from a brief, follow Make an ad through the session API. The live operation browser at the end of this page lists the routes the service publishes.

Make your first call

  1. In the web app, open Connections, choose the Developer setup tab, then Create credential.
  2. Name it, keep Read projects and results, and add Create and edit source and Run work if your integration uploads or makes media.
  3. Copy the credential when it is shown. It is shown once.
  4. Store it in your server's private configuration, for example as WAVEMAKER_API_KEY.
  5. Ask the service who the credential is:
curl --fail-with-body \
  --header "Authorization: Bearer $WAVEMAKER_API_KEY" \
  https://api.wavemaker.io/v1/identity
# → 200 {"ok": true, "data": {"schema": "wmcore.identity/1", "workspace": "...",
#        "kind": "api_key", "mode": "live", "scopes": ["read", "run", "write"],
#        "projects": null, "expires_at": ...}, "error": null, ...}

Check that it worked

  • ok is true and data.workspace is the workspace you expected.
  • data.scopes lists every scope you need (see Scopes).
  • data.expires_at is in the future. Credentials from Developer setup expire after 1, 7 or 30 days.

A 401 with unauthenticated means the key is missing, mistyped, expired or revoked. See Credentials for replacing and revoking keys.

Base URL and conventions

ItemValue
Base URLhttps://api.wavemaker.io. API requests are answered directly, with no redirects, so you can send them with redirects turned off. Only a top-level browser navigation to a file is redirected.
HealthGET /health answers {"ok": true, "service": "wmcore"} and needs no credential.
Request bodiesJSON, UTF-8, sent with Content-Type: application/json. A JSON body over 2 MB is refused with too_large (413). Send media as bytes, never inline in JSON.
Unknown fieldsSession, webhook and tool requests refuse unknown fields with invalid_request; details.path points at the problem when the service can name it. Responses may gain fields at any time: ignore fields you don't know.
MoneyInteger microcredits. 1 credit is 1,000,000 microcredits. The largest cap you can send is 9000000000000000.
TimesUnix seconds, as numbers.
IdsOpaque strings. Don't parse them.
FilesIdentified by the sha256 of their bytes, as wm://artifact/{sha256}. Files are immutable.

The response envelope

Every /v1 route answers in one envelope:

{"ok": true, "data": {...}, "error": null, "job": null, "cost": null, "hints": []}
  • ok: true: data holds the result and error is null.
  • ok: false: data is null and error is {code, message, retryable, retry_after_s?, card?, details?}. The HTTP status follows error.code.
  • job: a handle {id, uri, state, ...} when the call started long-running work.
  • cost: what the call quoted, held or charged, in microcredits.
  • hints: up to two suggestions for a next step. They are advice, not instructions.

Each session data object also carries a schema literal, such as wmcore.session/1, wmcore.turn/1, wmcore.quote/1 or wmcore.events/1. Branch on error.code and error.retryable, never on message. A failure in front of the service can be non-JSON, so check the HTTP status as well. See Errors, limits and safe retries.

Authentication

Send the credential on every call:

Authorization: Bearer {credential}

The credential fixes the workspace. Every read and write is scoped to it, so you never send a workspace id. A credential can also be limited to some projects: GET /v1/identity lists them in projects (null means every project). A call for a project the credential doesn't hold is refused with scope_denied; a session in such a project reads as not_found.

Three kinds of credential reach the API:

  • Developer credentials from Connections › Developer setup, for your own backend.
  • OAuth sign-in from an MCP host or the CLI. See MCP setup and CLI.
  • Older workspace keys that carry the agent scope. The session API accepts agent in place of run.

A 401 reply carries a WWW-Authenticate: Bearer realm="wavemaker" header that points OAuth clients to the service's protected-resource metadata.

Keep credentials in private server configuration. Never put them in a URL, a prompt, a graph, a log or browser code. A write sent from a web page on another site is refused with origin_rejected (403), so a browser can't call the API with your key on another origin.

Scopes

ScopeWhat it allows
readRead sessions, events and turns; search, read and watch; download files; read webhooks, destinations and the workspace policy.
writewrite (files, uploads, job cancellation) and deliver; upload with POST /api/v1/inputs; manage webhooks and export destinations.
runCreate sessions, post turns, decide approvals, interrupt and close; generate, edit, compose, listen, compare, review and run.
publishPublishing versions and community listings. The session API and the twelve tools never need it.

Most integrations need all three. POST /v1/quotes and GET /v1/identity need only a live credential. Saving the workspace policy also needs a credential issued by a workspace admin.

Request identity and replays

Every write that can start or change work takes a request_id: a key you choose for that one piece of work, such as campaign-123:hero-still-v1. Save it before you send.

  • Session writes (create, claim, post a turn, decide an approval, interrupt, close) require request_id, matching ^[A-Za-z0-9._:-]{1,200}$. The same id with the same body returns the original result with the response header Wavemaker-Replayed: true, and nothing runs or is charged twice. The same id with a different body is refused with request_id_mismatch (409).
  • Tool calls that can spend (generate, edit, compose, listen, compare, review, run) require request_id, except with quote_only or validate_only, or edit with op: "logo_prepare". The same id for the same work returns the original jobs. The same id for different work, another project or another tool is refused with request_conflict (409). For generate, edit and run, changing only max_microcredits, quote_id, rush or wait_s still replays.

After a network error or your own timeout, resend the identical request with the same id. Never invent a new id for the same work. See Errors, limits and safe retries.

Paging and waiting

ResourcePage withWait with
Session listlimit (1–200, default 50), before from the previous page's next_before–
Session eventsafter (the last seq you processed), limit (1–500, default 200); read again while more is truewait_s up to 25, or Accept: text/event-stream
Jobs–read of wm://job/{id} with wait_s up to 85; up to 20 jobs at once with uris
Search resultslimit (default 10, up to 50; up to 500 when the call names kinds), cursor from the previous reply's next–
Webhook deliverieslimit (1–200, default 50), before from next_before; filter by endpoint or state–
Project foldersread of a wm://file/{project}/ URI lists up to 1,000 files and sets truncated when there are more–

A wait that ends with nothing new is normal. It never cancels work. Read again with the same cursor or job id.

Worked example: price, make and download one still

This example uses the tools directly. Your code chooses the model and the arguments. The session API example is in Make an ad.

# 1. Price it. quote_only starts nothing and holds nothing.
curl --fail-with-body \
  --header "Authorization: Bearer $WAVEMAKER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"args": {"route": "gemini.image", "quote_only": true,
                    "inputs": {"prompt": "A cold brew can on a sunlit commuter train seat"},
                    "params": {"model": "gemini-3-pro-image", "aspect_ratio": "1:1", "image_size": "1K"}}}' \
  https://api.wavemaker.io/v1/tools/generate
# → 200 {"ok": true, "data": {"quote": {"quote_id": "...", "p50_microcredits": ...,
#        "hold_microcredits": ..., "expires_at": ...}, "count": 1, ...}}

# 2. Submit once, with a request_id and an optional ceiling at or above the quote's hold.
curl --fail-with-body \
  --header "Authorization: Bearer $WAVEMAKER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"request_id": "campaign-123:hero-still-v1",
           "args": {"route": "gemini.image", "max_microcredits": 10000000,
                    "inputs": {"prompt": "A cold brew can on a sunlit commuter train seat"},
                    "params": {"model": "gemini-3-pro-image", "aspect_ratio": "1:1", "image_size": "1K"}}}' \
  https://api.wavemaker.io/v1/tools/generate
# → 200 {"ok": true, "data": {"jobs": [{"id": "3f2c...", "uri": "wm://job/3f2c...", "state": "queued"}]},
#        "job": {"id": "3f2c...", "state": "queued", ...}, "cost": {"held_microcredits": ..., ...}}

# 3. Wait on the original job. Never resubmit to check progress.
curl --fail-with-body \
  --header "Authorization: Bearer $WAVEMAKER_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{"args": {"uri": "wm://job/3f2c...", "wait_s": 60}}' \
  https://api.wavemaker.io/v1/tools/read
# → 200 {"ok": true, "data": {"state": "succeeded", "result": {"artifacts": [
#        {"uri": "wm://artifact/9f86d0...", "sha256": "9f86d0...", "size": ..., "mime": "..."}]}, ...}}

# 4. Download the bytes with the credential and keep the sha256.
#    Name the file from the job's mime: gemini.image stills are image/jpeg.
curl --fail-with-body --output still.jpg \
  --header "Authorization: Bearer $WAVEMAKER_API_KEY" \
  https://api.wavemaker.io/api/v1/files/artifact/9f86d0...

Read the amounts from your own quote. Compare the sha256 of the saved file with the one in the job result before you use it. See Quote and run work for job states, ceilings and cancellation.

Endpoint reference

Shapes below are abbreviated: ? marks an optional field, ... means more fields, and → shows the status and the data of a success. For every field, use GET /v1/tools for tool arguments and the operation browser below for routes.

Sessions (the hosted agent)

Sessions need run to write and read to read. Only the session's current controller (the token returned at create or claim) can post turns, decide approvals, interrupt or close.

POST /v1/sessions: create a session. Creating one is free; nothing runs until the first turn.

{request_id, project?, title?, harness?, model?, collaboration?, max_microcredits?,
 deadline_at?, controller_seconds?, metadata?}
→ 201 {schema: "wmcore.session/1", id, project, state: "active", ..., controller: {token, expires_at, generation}}

GET /v1/sessions: list the workspace's sessions, newest updated first. Query: project, state (active or closed), limit, before.

→ 200 {schema: "wmcore.sessions/1", items: [{id, project, title, state, closed_reason, created_at, updated_at, active_turn_id, last_seq, turns}], next_before}

GET /v1/sessions/{id}: the session, with turns keyed by request_id, pending_approvals, last_seq, active_turn_id and cost.

POST /v1/sessions/{id}/claim: take or renew the controller lease. The token rotates on every claim.

{request_id, seconds?, controller? | takeover?}
→ 200 {schema: "wmcore.claim/1", session_id, controller: {token, expires_at, generation}}

POST /v1/sessions/{id}/turns: post a message to the agent, with attachments and deliverables. With quote_only: true it returns a quote instead and needs no request_id or controller.

{request_id, controller, text, attachments?, deliverables?, collaboration?, models?,
 max_microcredits?, quote_id?, quote_only?, answer?}
→ 202 {schema: "wmcore.turn/1", id, state: "queued" | "running", ...}
→ 200 {schema: "wmcore.quote/1", ...}   (quote_only)

GET /v1/sessions/{id}/turns/{turn_id} and GET /v1/sessions/{id}/requests/{request_id}: one turn, with its outcome, error and cost. Each read mints fresh signed download links on its deliveries.

GET /v1/sessions/{id}/events: the session's events after a cursor. Query: after, limit, wait_s, turn_id. Send Accept: text/event-stream for a stream.

→ 200 {schema: "wmcore.events/1", items: [{seq, session_id, type, at, turn_id, request_id, data}],
       next_after, more, session_state, active_turn_id}

POST /v1/sessions/{id}/approvals/{approval_id}: accept or decline a step the agent asked about.

{request_id, controller, decision: "accept" | "decline", option_id?, note?}
→ 200 {schema: "wmcore.approval/1", session_id, approval: {id, state, ...}}

POST /v1/sessions/{id}/interrupt: stop a turn. Returns the turn as it stood; it ends interrupted moments later.

{request_id, controller, turn_id}
→ 202 {schema: "wmcore.turn/1", ...}

POST /v1/sessions/{id}/close: close the session. History, files and deliveries stay readable.

{request_id, controller}
→ 200 {schema: "wmcore.session/1", state: "closed", closed_reason: "client", ...}

The full walkthrough, with turn states, outcome statuses, event types and recovery, is Make an ad through the session API.

Quotes and identity

POST /v1/quotes: price a brief without a session. Starts and holds nothing. Any session in the workspace can accept the quote_id within 1,800 seconds.

{text, deliverables?, attachments?}
→ 200 {schema: "wmcore.quote/1", quote_id, p50_microcredits, p90_microcredits, hold_microcredits,
       balance_microcredits, basis: [{kind, label, p50_microcredits}], expires_at, microcredits_per_cent}

GET /v1/identity: the calling credential.

→ 200 {schema: "wmcore.identity/1", workspace, subject, credential, kind, mode: "live" | "test",
       scopes, projects, expires_at}

credential is the credential's id, never its secret. kind is api_key, oauth, session or task.

Tools

GET /v1/tools: the twelve tools with each one's name, scope, summary and args_schema. Needs a live credential.

POST /v1/tools/{tool}: call one tool. {tool} is search, read, write, generate, edit, compose, watch, listen, compare, review, deliver or run. Any other name is not_found (404).

{args: {...}, project?, request_id?}
→ 200 {ok: true, data: {...}, job?, cost?, hints}
  • args are the tool's own arguments, checked against its args_schema.
  • project defaults to the credential's project when it is scoped to exactly one.
  • Long work returns a job in data.jobs[] and in the envelope's job.

What each tool does, with its scope, is in the Capability map. How to quote, submit, wait and cancel is in Quote and run work.

Uploads

Upload a file before you attach it to a turn or pass it to a tool. Each way returns the file's sha256.

Upload link (recommended). Two write calls around one PUT. Needs write.

POST /v1/tools/write
{args: {uri: "wm://artifact/", upload: {size, sha256, mime, name?}}}
→ 200 {uri, upload: {id, url, method: "PUT", headers, expires_at}, next: {commit: {upload, sha256}}}

PUT {upload.url}         (the raw bytes, with upload.headers)

POST /v1/tools/write
{args: {uri: "wm://artifact/", commit: {upload, sha256}}}
→ 200 {uri: "wm://artifact/{sha256}", revision: null, sha256, size, mime}
  • The upload link is on https://api.wavemaker.io/v1/uploads/..., needs no credential, and expires 600 seconds after it is issued.
  • One upload can be up to 5 GiB.
  • The service checks the bytes against the sha256 you declared.
  • To also place the file in a project, write with {uri: "wm://file/{project}/{path}", from_artifact: "{sha256}"}.

One request. POST /api/v1/inputs takes the raw bytes as the body, with a positive Content-Length, X-Content-SHA256 and X-Filename. Chunked transfer encoding is refused. It needs write and answers 201 in the older wmlab.result/1 envelope, with the input's sha256 in data.

Small files. write to wm://artifact/ with content_base64 stores the bytes directly; write to a wm://file/{project}/{path} URI also takes text or JSON as content. The 2 MB body limit applies.

Downloads

GET /api/v1/files/artifact/{sha256}: the bytes of a file the workspace owns. Needs read.

  • Streams the bytes with no redirect, supports a single Range, and sends X-Content-SHA256 with the whole file's digest.
  • GET /api/v1/artifacts/{sha256} is the same file.
  • Errors use the older envelope: unauthorized (401), forbidden (403) or artifact_missing (404).

GET /v1/downloads/{sha256}/{token}: a signed link. Needs no credential.

  • You get one from read of wm://artifact/{sha256} (data.link.url), from a turn's outcome.deliveries[].url, or from a delivery.created event.
  • A link lasts until its expiry, at most 3,600 seconds. read takes ttl_s (1–3,600) for a shorter one.
  • Every read mints a fresh link. Store the bytes, not the link.

Types a browser can't show inline, ZIPs included, download as application/octet-stream with Content-Disposition: attachment. Take the real type from the file's mime.

Webhooks

Webhooks push session milestones and other workspace events to your server. Polling events stays the contract; webhooks are optional. Writes need write; reads need read.

POST /v1/webhooks/endpoints: subscribe a URL. Returns the signing secret once.

{url, events: ["turn.completed" | "turn.*" | "*", ...], description?}
→ 201 {schema: "wmcore.webhook-endpoint/1", id, url, events, enabled: true, revision: 1, ..., secret: "whsec_..."}
  • The URL must be public HTTPS with no credentials, query or fragment in it. Private and loopback addresses are refused, and redirects are never followed.
  • A workspace can have up to 20 endpoints.

GET /v1/webhooks/endpoints and GET /v1/webhooks/endpoints/{id}: list or read endpoints. The secret is never shown again.

PATCH /v1/webhooks/endpoints/{id}: change url, events, enabled or description. Send expected_revision; a stale revision is conflict (409).

POST /v1/webhooks/endpoints/{id}/rotate: a new secret. The old secret keeps signing beside it for 24 hours.

POST /v1/webhooks/endpoints/{id}/test: send a signed webhook.test event now.

DELETE /v1/webhooks/endpoints/{id}: remove the endpoint.

GET /v1/webhooks/deliveries and GET /v1/webhooks/deliveries/{id}: the delivery log. Each delivery is pending, delivered or failed, with each attempt's HTTP status or error.

POST /v1/webhooks/deliveries/{id}/retry: one more attempt for a failed delivery, with the same delivery id.

Each delivery is a POST of the wmcore.event/1 envelope {schema, id, delivery_id, type, occurred_at, workspace, data, project?, session_id?, turn_id?, subject?} with these headers:

  • Wavemaker-Signature: t={unix},v1={hex}: HMAC-SHA256 of {t}.{raw body} with the endpoint's secret. During a rotation the header carries two v1 values.
  • Wavemaker-Event: the event type.
  • Wavemaker-Delivery: the delivery id. Deduplicate on it.

A 2xx answer within 10 seconds delivers. Anything else is retried after 30 seconds, 2 minutes, 10 minutes, 30 minutes, 2 hours and 6 hours, then the delivery is failed.

Workspace policy

GET /v1/workspace/policy: the workspace's model and route policy. Needs read.

PUT /v1/workspace/policy: save it. Needs write, on a credential issued by a workspace admin.

{expected_revision, allow: [{kind, value}], block: [{kind, value}]}

kind is route, model, vendor, licence_class (commercial, non_commercial, territory_limited, partner_api) or content_class (permissive). A generate or edit call that the policy forbids is refused with workspace_policy_refused (403). The service never substitutes another route.

Export to your own storage

Copy files the workspace owns to an S3-compatible bucket (AWS S3, Cloudflare R2, Backblaze B2, Wasabi). Reads need read; changes need write.

  • GET /v1/destinations: list destinations.
  • POST /v1/destinations: add one: {kind: "s3", bucket, endpoint?, region?, prefix?, name?, credentials | secret_ref}. Credentials are stored encrypted and never returned.
  • POST /v1/destinations/{id}/test: check the destination.
  • DELETE /v1/destinations/{id}: remove it.
  • POST /v1/exports: copy files: {request_id, destination, artifacts?, project?, roles?, path_template?}. The same request_id resumes the same export; reusing it for other files is conflict.

Rejected bucket credentials end the export failed with destination_unauthorized (403). An unreachable bucket leaves it incomplete; send the same request again to resume.

Older operations

POST /api/v1/ops/{name} serves part of the older operation catalog in the older wmlab.result/1 envelope ({schema, ok, data, error}) with its own codes, such as unauthorized and request_invalid. GET /api/v1/operations returns the whole older catalog, including operations this service no longer answers.

The catalog has 390 operations; 238 are served. An unserved one answers 501 operation_not_ported. That includes every agents.*, productions.*, compute.*, creative.*, generation.*, capabilities.* and api.* operation, and every jobs.* operation except jobs.list, jobs.inspect and jobs.cancel. Use the session API and the tools for new work. See Account API access for the operations that remain.

Errors and limits

LimitValueWhen you pass it
JSON body2 MBtoo_large (413)
One upload5 GiBtoo_large (413)
Turn text64,000 charactersinvalid_request (422)
Attachments per turn40invalid_request (422)
Deliverables per turn50invalid_request (422)
Request rateper credential, for tool calls and older operationsrate_limited (429) with retry_after_s
Agent turns running at once24 per workspacerate_limited (429), details.limit: "hosted_turns_per_workspace", retry_after_s: 30
Tool calls in one agent run10 per second of requested output media, at least 300; read and write don't countrate_limited with details.limit: "tool_call_budget"; further calls in that run are refused
Webhook endpoints20 per workspaceconflict (409)

There is no Wavemaker-side credit cap per run beyond the caps you set and your balance. The complete code list, with what to do for each, is in Errors, limits and safe retries.

Billing

Work is paid in credits from the workspace balance. Every amount in the API is in microcredits.

  • Quote first. generate, edit, compose, review and run take quote_only: true, and a session turn can be quoted with quote_only or POST /v1/quotes. A quote holds nothing.
  • Holds. Submitting reserves the quoted hold. When the work ends, the hold settles to the measured usage, and the rest is released. A job that fails before it starts releases its hold.
  • Ceilings. max_microcredits on a tool call, a turn or a session caps what that work can hold. A ceiling below a call's hold is refused with cap_exceeded before anything starts.
  • Receipts. A job's receipt and a turn's cost.receipt are wm://receipt/{id} URIs you can read. read of wm://credits returns the balance.
  • Starter credits. Credits granted at sign-up can't be spent through an API key until the workspace has made a payment; apps connected with sign-in (OAuth) can spend them. Such a call is refused with insufficient_credits and details.reason: "starter_credits_need_payment".
  • Your own provider keys. When a provider has a saved key you can use, generate calls to its routes run on your key and charge no credits. Wavemaker's own GPU and CPU work is always paid in credits. See Credentials.

microcredits_per_cent on a quote is the published credit-to-cash rate (150000 microcredits per US cent, so one credit is about 6.7 cents), for display only; no charge is computed from it. See Credits for buying credits and reading activity.

Schemas and the operation browser

  • GET /v1/tools returns each tool's argument schema. An MCP host sees the same schemas in tools/list.
  • GET /openapi.json is the public API contract (OpenAPI 3.1): the twelve tools (one operation each, POST /v1/tools/{tool}), the session API, and the other public routes on this page. Each operation has its request schema, its real success codes (201 for a new session or webhook endpoint, 202 for a posted turn or an interrupt) and a unique operationId, so client generators work from it. Internal routes are not in it, nor are the older operations (/api/v1/ops/{name}, listed by GET /api/v1/operations) or MCP (/mcp). It needs no credential.
  • The operation browser below reads /openapi.json live and shows each operation's parameters, request body and responses. Filter it by name, method or scope. Inspecting an operation never runs it.
curl --fail-with-body https://api.wavemaker.io/openapi.json
curl --fail-with-body --header "Authorization: Bearer $WAVEMAKER_API_KEY" https://api.wavemaker.io/v1/tools

Related guidance