Quickstart
By the end of this page you will have run an agent in a managed sandbox from your shell and read what it produced. Every call is real, every field name is the one the server validates, and the response shape is shown at each step.
Creation is PUT with an id you choose
Section titled “Creation is PUT with an id you choose”Read this before the first call, because it shapes every request that follows.
Cloud has no POST /v1/sessions. You mint a UUID and PUT the resource at it:
PUT /v1/sessions/{sessionId}PUT /v1/sessions/{sessionId}/turns/{turnId}That single decision is the whole retry story. There is no Idempotency-Key
header anywhere in this API because the id is the idempotency key:
| You send | You get |
|---|---|
| A new id | 201 Created for a session, 202 Accepted for a turn, with a Location header |
| The same id and the same body | 200 for a session, 202 with created: false for a turn — no second run |
| The same id and a different body | 409 — the durable command is immutable, so the conflict is reported rather than silently applied |
So when a request times out or a connection drops, you re-send exactly the same request. You never have to ask “did that create a second run?”.
What you need first
Section titled “What you need first”Your operator provides the API origin, the token issuer, and your app’s installation. Getting a keypair registered and an installation approved is covered on Authentication — this page assumes you have finished that and can sign with your private key.
export WAMP_API=https://api.example.com # the Cloud originexport WAMP_ACCOUNT=https://api.example.com # the Account origin; the same host unless deployed splitexport TOKEN_ISSUER='<the platform JWT issuer your operator gives you>'export WAMP_APP_SLUG='<your app slug>'export WAMP_APP_KID='<your registered key id>'export WAMP_INSTALLATION_ID='<the installation a customer approved>'export WAMP_APP_PRIVATE_KEY="$(cat app-private-key.pem)"You also need curl, jq, uuidgen, and Node 20 or newer with the jose
package available (npm install jose) for the one signing step.
-
Get a token
Section titled “Get a token”Sign a short assertion with your private key, then exchange it for an installation bearer. The assertion is a JWT:
EdDSA, yourkidin the header,issandsubset to your app slug,audset to the token issuer, and a lifetime of at most 600 seconds.Terminal window ASSERTION=$(node --input-type=module -e "import { SignJWT, importPKCS8 } from 'jose';const key = await importPKCS8(process.env.WAMP_APP_PRIVATE_KEY, 'EdDSA');const now = Math.floor(Date.now() / 1000);process.stdout.write(await new SignJWT({}).setProtectedHeader({ alg: 'EdDSA', kid: process.env.WAMP_APP_KID }).setIssuer(process.env.WAMP_APP_SLUG).setSubject(process.env.WAMP_APP_SLUG).setAudience(process.env.TOKEN_ISSUER).setIssuedAt(now).setExpirationTime(now + 300).sign(key));")curl -sS -X POST "$WAMP_ACCOUNT/auth/app-installation-token" \-H 'Content-Type: application/json' \-d "$(jq -n --arg a "$ASSERTION" --arg i "$WAMP_INSTALLATION_ID" \'{assertion: $a, installationId: $i, resourceAudience: "wamp-cloud"}')"{ "success": true, "token": "eyJhbGciOiJFZERTQSIsImtpZCI6…", "expiresIn": 600 }Keep it in a variable. It lives ten minutes and is the only credential
/v1accepts:Terminal window export WAMP_TOKEN='<the token value>' -
Discover what you may use
Section titled “Discover what you may use”This call doubles as a check that the token works, and it is where you get a valid
modelid rather than guessing one.Terminal window curl -sS "$WAMP_API/v1/capabilities" \-H "Authorization: Bearer $WAMP_TOKEN"{"capabilities": {"schema": "wamp.cloud-capabilities","version": 1,"organizationId": "0f2c9a41-5f1b-4c8e-9a6d-7b3e1c5d9f02","models": { "items": [{ "id": "…", "label": "…" }], "defaults": { "slots": {}, "enabled": [] } },"runtimes": [{"id": "wamp","label": "WAMP","modelSource": "catalog","continuation": { "live": "exact", "checkpoint": "exact" },"availability": { "state": "available" }}],"environments": [{ "id": "managed", "label": "WAMP managed sandbox", "default": true }],"repositories": { "sources": [], "publications": [], "merges": [] },"artifacts": { "maxPageSize": 100 },"limits": { "maxActiveSessions": 100, "maxMessageCharacters": 100000, "maxPageSize": 100 }}}The document is filtered to what your installation may use, so two callers see different answers. Pick a model id from it:
Terminal window MODEL=$(curl -sS "$WAMP_API/v1/capabilities" \-H "Authorization: Bearer $WAMP_TOKEN" | jq -r '.capabilities.models.items[0].id') -
Create the session
Section titled “Create the session”The session is the durable conversation and its workspace. Creating one allocates no compute.
Terminal window SESSION_ID=$(uuidgen | tr 'A-Z' 'a-z')curl -sS -i -X PUT "$WAMP_API/v1/sessions/$SESSION_ID" \-H "Authorization: Bearer $WAMP_TOKEN" \-H 'Content-Type: application/json' \-d "$(jq -n --arg model "$MODEL" '{title: "Quickstart",task: "Create hello.txt containing the word hello, then summarize what you did.",model: $model}')"HTTP/1.1 201 CreatedLocation: /v1/sessions/3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f{"session": {"id": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","sessionUrl": "https://cloud.example.com/sessions/3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","organizationId": "0f2c9a41-5f1b-4c8e-9a6d-7b3e1c5d9f02","title": "Quickstart","model": "…","workspace": { "state": "unavailable" },"continuation": {"canContinue": true,"state": "fresh","conversation": { "fidelity": "none" },"workspace": { "fidelity": "none" },"environment": { "fidelity": "current" }},"phase": "idle","createdBy": { "kind": "app_installation", "id": "8a0f1c6b-92d4-4e73-b5a1-0d3e7f2c9b48" },"createdAt": "2026-08-11T09:00:00.000Z","updatedAt": "2026-08-11T09:00:00.000Z"}}The body fields, exactly:
Field Required Constraint taskyes 1–100000 characters. What this session exists to do titleno 1–160 characters, for humans modelconditional 1–128 characters. Required for the native wampruntime, which is the default. Omit it only when you set aruntimethat manages its own modelruntimeno Lowercase slug matching ^[a-z0-9][a-z0-9-]*$, up to 64 characters. Defaults towamporiginno Your back-reference: tenantKey,objectType,objectId, and optionallyendUserId,label,urlsourceno A repository or public checkout to work in. Omit for a bare workspace Unknown fields are rejected with
400 invalid_requestrather than ignored.task,origin, andsourceare immutable — onlytitle,model, andruntimecan be changed later withPATCH. -
Submit a turn
Section titled “Submit a turn”The turn is the message; its run is the execution.
Terminal window TURN_ID=$(uuidgen | tr 'A-Z' 'a-z')curl -sS -i -X PUT "$WAMP_API/v1/sessions/$SESSION_ID/turns/$TURN_ID" \-H "Authorization: Bearer $WAMP_TOKEN" \-H 'Content-Type: application/json' \-d '{"message":"Create hello.txt containing the word hello, then summarize what you did."}'HTTP/1.1 202 AcceptedLocation: /v1/sessions/3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f/runs/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d{"turn": {"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","ordinal": 1,"input": { "message": "Create hello.txt containing…", "model": "…" },"dispatch": "pending","run": { "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" },"createdAt": "2026-08-11T09:00:02.000Z"},"run": {"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","status": "queued","attempts": 0,"cancellationRequested": false,"createdAt": "2026-08-11T09:00:02.000Z","updatedAt": "2026-08-11T09:00:02.000Z"},"created": true}202is honest: the turn and aqueuedrun are committed, and no sandbox exists yet.run.idequals theturnIdyou chose.created: falseon a replay means you are looking at the turn you already submitted.Only one run per session may be non-terminal at a time. Submitting a second turn while one is in flight is
409 cloud_run_in_progresswithRetry-After: 2. -
Follow the run
Section titled “Follow the run”There is no push channel — no Server-Sent Events, no public WebSocket. You page an ordered, gap-free log with an integer cursor.
afteris exclusive and sequences start at1, soafter=0means “from the beginning”.Terminal window AFTER=0while : ; doPAGE=$(curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/events?after=$AFTER&limit=100" \-H "Authorization: Bearer $WAMP_TOKEN")echo "$PAGE" | jq -c '.events[] | {sequence, type}'AFTER=$(echo "$PAGE" | jq -r '.nextAfter')STATUS=$(echo "$PAGE" | jq -r --arg run "$TURN_ID" '[ .events[]| select(.subject.runId == $run)| select(.type | test("^wamp\\.run\\.(completed|failed|cancelled|crashed)$"))| .data.status ] | first // empty')[ -n "$STATUS" ] && break[ "$(echo "$PAGE" | jq -r '.hasMore')" = true ] || sleep 2doneecho "run finished: $STATUS"A page looks like this:
{"events": [{"id": "6f1c1d80-6c2a-4f0e-9d5f-0c2a2f0f4b11","type": "wamp.run.started","createdAt": "2026-08-11T09:00:11.412Z","sequence": 1,"visibility": "summary","subject": {"sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","runId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"},"data": { "status": "running" }}],"nextAfter": 1,"hasMore": false}Four things about that loop are worth keeping when you write the real one:
- Persist
nextAfterin your own storage. It is the entire resume protocol — there is noLast-Event-IDand no subscription state on the server. hasMore: truemeans the page was capped atlimit, so call again without sleeping.hasMore: falsemeans you have caught up, not that the run is over.- Match on
subject.runId, or a terminal event from an earlier run in the same session ends your loop early. - Ignore event types you do not recognize. The catalog grows additively.
If the agent needs a decision it emits
wamp.interaction.openedand the run moves toawaiting. You answer with a normal turn carryingreplyTo.interactionId— see Follow a run live. - Persist
-
Read the result
Section titled “Read the result”The authoritative status does not depend on having seen an event:
Terminal window curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/runs/$TURN_ID" \-H "Authorization: Bearer $WAMP_TOKEN"{"run": {"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","status": "completed","attempts": 1,"cancellationRequested": false,"createdAt": "2026-08-11T09:00:02.000Z","updatedAt": "2026-08-11T09:01:44.000Z","startedAt": "2026-08-11T09:00:11.000Z","completedAt": "2026-08-11T09:01:44.000Z"}}A run has a
completedAtexactly when its status is terminal — that equivalence is enforced by a database constraint, socompletedAt != nullis a sound “is it done” test. The statuses arequeued,dispatching,running,awaiting,completed,failed,cancelled,crashed.What the run produced is listed as artifacts:
Terminal window curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/artifacts?limit=100" \-H "Authorization: Bearer $WAMP_TOKEN"{"artifacts": [{"id": "b41d0f2a-8c5e-4d19-9f77-2a6b8c3d4e5f","sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f","runId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d","kind": "result.summary","role": "output","contentType": "text/markdown; charset=utf-8","size": 412,"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08","state": "available","metadata": { "truncated": false },"createdAt": "2026-08-11T09:01:44.000Z"}],"nextAfter": "b41d0f2a-8c5e-4d19-9f77-2a6b8c3d4e5f","hasMore": false}Then fetch the bytes. The
result.summaryartifact is the agent’s own account of what it did:Terminal window ARTIFACT_ID=$(curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/artifacts?limit=100" \-H "Authorization: Bearer $WAMP_TOKEN" \| jq -r '[.artifacts[] | select(.kind == "result.summary")] | last | .id')curl -sS "$WAMP_API/v1/sessions/$SESSION_ID/artifacts/$ARTIFACT_ID/content" \-H "Authorization: Bearer $WAMP_TOKEN"The response is the raw bytes with the artifact’s own
Content-Typeand anETagequal to itssha256. If retention has already reclaimed the bytes the manifest still lists it with"state": "pruned"and this call is410. -
Clean up
Section titled “Clean up”Terminal window curl -sS -i -X DELETE "$WAMP_API/v1/sessions/$SESSION_ID" \-H "Authorization: Bearer $WAMP_TOKEN"# HTTP/1.1 204 No ContentArchiving is a soft delete and it is idempotent. The session and its event log remain readable; it stops counting against your organization’s active-session limit.
What to read next
Section titled “What to read next”- Authentication — the exchange in full, plus the capability model that decides which of these calls your installation may make.
- Follow a run live — the production polling loop, interactions, and resuming after a restart.
- Drive Cloud from a backend — token caching, the rows you need to store, and the retry policy.
- Open a pull request — turning a repository session’s work into a reviewable, mergeable PR.
- Turns and runs operations and Events operations — the generated endpoint reference for the two calls you will make most.