Skip to Content

Sessions and Turns

To call an AI employee conversationally, pick a Deployment, create a Session, and send Turns. A Session is fixed to one Deployment, and earlier turns carry over.

Listing Deployments

  • GET /v1/deployments returns the Deployments this key can call. (deployments:read)
  • GET /v1/deployments/{deployment_id} shows the name, version, status (ACTIVE, RETIRED), model tier (model_tier), allowed tools, the declared result contract (outputs), the allowed input MIME types, and the call rate limits.
  • Actual model names are not exposed. Only the tier is shown.

Creating a Session

POST /v1/deployments/{deployment_id}/sessions creates a Session and its first Turn in one step. It needs sessions:write and turns:write, and Idempotency-Key is required.

  • The server returns 202 after it has stored the acceptance. A dropped connection does not lose an accepted Turn.
  • The response (receipt) has session_id, turn_id, status, idempotency_expires_at, status_url, and events_url. The Location header is the relative address of the Turn detail.
  • The same key with the same body as an earlier request returns the same receipt. The same key with a different body returns 409 IDEMPOTENCY_CONFLICT.
  • A receipt is kept for at least 24 hours (idempotency_expires_at) and until that Turn has ended.

Follow-up Turns

Send new input to the same Session with POST /v1/sessions/{session_id}/turns.

  • A Session runs only one Turn at a time. Sending before the previous Turn ends returns 409 SESSION_BUSY. There is no queue, so confirm that the Turn ended before you send.
  • Sending to a closed Session returns 409 SESSION_CLOSED.
  • Different Sessions run in parallel, subject to the Project’s concurrent execution limit.
{ "error": { "code": "SESSION_BUSY", "message": "The session already has an active turn", "retryable": false, "request_id": "0b7b8f0e-0000-4000-8000-000000000004", "details": {} } }

Read, cancel, close, delete

PathDescription
GET /v1/sessions/{session_id}Session status (ACTIVE, CLOSED) and the active Turn
GET /v1/sessions/{session_id}/turnsList Turns with an after and limit cursor
GET /v1/sessions/{session_id}/turns/{turn_id}Turn status, usage, error, results (outputs), and next_cursor
GET /v1/sessions/{session_id}/itemsPublic Item history
POST /v1/sessions/{session_id}/turns/{turn_id}/cancelCancel a Turn. 202 for a new cancel, 200 if it already ended
POST /v1/sessions/{session_id}/closeClose an idle Session. 409 if it is running
DELETE /v1/sessions/{session_id}Accept a delete (202). Reading content is an immediate 404 afterward

Turn statuses are QUEUED, RUNNING, REQUIRES_ACTION, CANCEL_REQUESTED, SUCCEEDED, FAILED, CANCELED, and EXPIRED. SUCCEEDED, FAILED, CANCELED, and EXPIRED are terminal. A failed Turn was still accepted successfully, so read the reason from error.code in the response.

  • A cancel stores the intent first, and effects that already completed are not undone. If a cancel and a completion overlap, whichever finished first is the final state.
  • For deletion, see the delete section of limits and data.
  • You can add X-Correlation-Id (letters, digits, dot, underscore, colon, hyphen, 1 to 128 characters) to a request for tracing on your side. A bad format returns 400 INVALID_CORRELATION_ID.

Input format

{ "input": [ {"type": "TEXT", "text": "Compare the attached report with last month."}, {"type": "FILE", "file_id": "d3c1b8a0-0000-4000-8000-000000000003"} ], "metadata": {"request_ref": "example-001"}, "limits": {"queue_seconds": 600, "execution_seconds": 3600, "max_su": 50} }
  • input is an array of TEXT and FILE items. TEXT cannot be empty or only whitespace. Multiple TEXT items are run in order, joined with line breaks.
  • The file_id of a FILE item is a value you got by uploading with POST /v1/files first. A file that is already bound to a Session or Run cannot be reused by another execution, so upload it again.
  • metadata takes at most 16 keys. A key is 1 to 64 characters and a string value is at most 512 characters.
  • limits is optional. queue_seconds is 1 to 600, execution_seconds is 1 to 3600, and max_su is above 0 and at most 100 (up to 8 decimal places). A value above the cap is not silently reduced. It is rejected with 422 LIMIT_OUT_OF_RANGE.
  • The JSON request body is at most 64KiB in UTF-8. Larger bodies return 413 INPUT_TOO_LARGE. Undeclared fields such as model, prompt, or tools return 422 INVALID_INPUT.
  • Files are 20MiB each, up to 20 per input. The allowed MIME types differ per Deployment.

Output format

Each item of outputs in a Turn detail is one of these four types.

[ {"id": "o1", "key": "answer", "type": "TEXT", "visibility": "FINAL", "availability": "READY", "item_id": "i1", "text": "Done."}, {"id": "o2", "key": "summary", "type": "JSON", "visibility": "FINAL", "availability": "READY", "schema_ref": "summary", "data": {"count": 3}}, {"id": "o3", "key": "report", "type": "FILE", "visibility": "FINAL", "availability": "READY", "artifact_id": "a1", "filename": "report.txt", "mime_type": "text/plain", "size_bytes": 1024, "sha256": "..."}, {"id": "o4", "key": "reference", "type": "URL", "visibility": "FINAL", "availability": "READY", "url": "https://example.com/report", "access": "UNKNOWN", "expires_at": null} ]
typeContent
TEXTThe sanitized body text. A body over 64KiB is delivered as a file.
JSONdata validated against the schema set at publication. If it is large, a file is provided through artifact_id instead of data.
FILEartifact_id, file name, MIME type, size, and sha256. See the download section of Workflows and Runs.
URLAn external HTTPS link url and its access mode (access). UpServe neither stores it nor guarantees its availability.
  • visibility is INTERMEDIATE, PARTIAL, or FINAL. Final results are FINAL.
  • Items whose availability is FAILED or EXPIRED come with an error.
  • The result list is at most 100 items and 256KiB per response. Continue with after and next_cursor.
  • A successful Turn must contain every result marked required at publication. If one is missing or malformed, the Turn closes as a failure, not a success.

Usage

The usage field of Turn and Run responses has two states.

{"status": "PENDING", "total_su": null}
  • When FINAL, total_su is the settled amount.
  • When PENDING, the cost is not decided yet and total_su is null. It is not 0 SU. Reading again later returns the settled value.
  • GET /v1/usage narrows by one of key_id, deployment_id, session_id, turn_id, or run_id and shows settled amounts, reserved amounts, and the breakdown by item. (usage:read)
  • preparations in the same response shows the preparation costs of organization works separately, with the same status, settled amount, reserved amount, and breakdown by item. An ongoing preparation does not change the execution status or total_su. For a key granted every Deployment and holding works:read, the Project’s total spend is the execution total plus the preparations total.
    • With works:read on the key it covers every preparation in the Project. Without it, only the preparations that key accepted. key_id narrows it to the preparations that key accepted.
    • With deployment_id, session_id, turn_id, or run_id, preparations is null, because a preparation belongs to none of them. It is not 0 SU.

Resolving questions (RequiredAction)

This is the feature where a Turn stops at REQUIRES_ACTION when the AI employee must ask the caller something during execution. It is not offered in the current Private Beta. It is a service-wide setting and cannot be turned on per Project. For now, published Deployments do not include the question tool, and a workflow execution that comes to need a human answer or approval ends with ACTION_REQUIRED_UNSUPPORTED. The contract below applies once the feature is offered.

  • Answer a question the caller may answer (CALLER) with POST /v1/sessions/{session_id}/actions/{action_id}/resolve. It needs actions:resolve and Idempotency-Key.
  • Workflow approvals require separate actions:approve delegation. Collect the human decision in your own UI and submit it through the Run action API. See workflow approvals.
  • After the answer deadline it returns 409 ACTION_EXPIRED.
curl -s -X POST "$BASE/v1/sessions/$SESSION_ID/actions/$ACTION_ID/resolve" \ -H "Authorization: Bearer $UPSERVE_API_KEY" \ -H "Idempotency-Key: answer-0001" \ -H "Content-Type: application/json" \ -d '{"answers":[{"question_id":"q1","text":"Use the Seoul office."}]}'