Skip to content

POST /v1/completions

Updated
Reading time
7 min
Level
beginner

The native endpoint. Everything Revoye can do is expressible here — let Revoye choose the model or pick one, wait for the response or queue the request, choose Revoye Cloud or Revoye Desk, set your own timeouts, continue an earlier conversation, and attach metadata that comes back untouched.

curl https://api.revoye.com/v1/completions \
  -H "Authorization: Bearer $REVOYE_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 4f1c9d2e-8b3a-4c1d-9e2f-7a6b5c4d3e2f" \
  -d '{
    "prompt": "Write a two-sentence product description for a handmade ceramic mug.",
    "channel": "auto",
    "wait": true
  }'

Request

FieldTypeDefaultNotes
promptstring, 1–100 000 charsrequiredThe text of your request
providerstring | nullnullA model id from GET /v1/models. null lets Revoye choose, which is usually right
channelauto | desk | cloudautoWhere it is processed: cloud on Revoye Cloud, desk by Revoye Desk on your device. See choosing a channel
waitbooleantrueHold the HTTP request open until the request finishes
timeout_ms5 000–600 000180 000Per attempt
deadline_ms10 000–3 600 000900 000For the whole request, across all attempts
priority−10…100Higher starts first within your own queue
modenew | continuenewcontinue requires conversation_ref
conversation_refstringnullOpaque; returned by an earlier request. Send it back unchanged
agent_idstringnullRevoye Desk only, advanced: keep the request on the same Desk capacity as an earlier request, by the agent_id it returned
callback_urlhttps URLnullWebhook when the request finishes
metadataobject ≤ 4 KiB{}Echoed back verbatim. Revoye never reads it

Headers. Idempotency-Key (at most 255 characters) is optional and strongly recommended — see retrying safely.

Choosing a model, or not

Omitting provider is usually right: Revoye chooses among the models your plan can use, which keeps your integration working when one of them is busy. Name a model only when the quality of the response depends on which one produces it.

Naming one is a constraint, not a preference. Revoye will not silently answer with a different model than the one you asked for — that would be a correctness bug, not a helpful fallback.

Choosing a channel

Your plan decides which channels you have. auto — the default — uses the ones it includes: with only one, that one; with both, the channel you prefer (set on the Routing page of the dashboard), moving to the other if Revoye Desk is not online on any of your devices, Revoye Cloud cannot process that model right now, or one channel's monthly allowance is used up. A request still queued on its first channel after your fallback delay moves to the other once; it is never on both at the same time.

Naming desk or cloud is a constraint, like naming a model: Revoye processes the request on that channel or refuses it with CHANNEL_NOT_ENTITLED (not in your plan) or QUOTA_EXCEEDED (allowance used up). It never quietly sends the request to the other one. mode: "continue" and agent_id fix the channel too, because an earlier conversation lives on exactly one.

channel on the response says where the request is processed, and fallback_at, while it is queued, when it may move.

metadata is yours

Whatever you put in comes back out, byte for byte, on the response and on the webhook. Use it to correlate a request with your own records — a tenant id, a trace id, a row id — so that a webhook arriving an hour later can be routed without a database round trip.

Response — wait: true, success (200)

{
  "id": "job_01JAY7Q2K8XYZ",
  "status": "succeeded",
  "response": "Shaped by hand and glazed in small batches, this mug…",
  "channel": "cloud",
  "attempts": 1,
  "queue_ms": 240,
  "run_ms": 18432,
  "created_at": "2026-10-13T09:14:02Z",
  "finished_at": "2026-10-13T09:14:21Z",
  "content_pruned_at": null,
  "metadata": { "order_id": "ord_8812" }
}

The object also carries provider, agent_id, agent_name and conversation_ref, described below.

FieldMeans
responseThe AI response, complete
channeldesk or cloud — where it was processed, or will be
fallback_atWhile queued: when it may move to the other channel. Absent when it will not
attemptsHow many times processing was attempted. > 1 means an attempt timed out or failed
queue_msHow long it waited before processing first started. Set once, so a retried request still reports its original wait
run_msTime spent processing
providerThe model id that processed it, in the vocabulary of GET /v1/models
agent_id, agent_nameRevoye Desk only: which Desk capacity processed it. Always null on Revoye Cloud
conversation_refAn opaque handle to the conversation. Pass it back with mode: "continue" to follow up
content_pruned_atWhen the request and response text were removed under your account's retention setting. null while the text is still there

Response — wait: false (202)

{ "id": "job_01JAY7Q2K8XYZ", "status": "queued", "channel": "desk", "fallback_at": "2026-10-13T09:14:32Z", "created_at": "2026-10-13T09:14:02Z" }

Abbreviated: the full object is the same as above, with the fields that are not known yet set to null. A retry with the same Idempotency-Key returns 200 and the original request.

Blocking semantics

wait: true is a long poll, not synchronous execution. The request is kept the moment it is accepted, so:

  • If your connection drops, the request keeps going.
  • The response is at GET /v1/completions/{id}, or delivered to your callback_url.
  • Nothing is lost by a client timeout.

The connection is held for at most min(deadline_ms, 600000). On expiry you get 504 JOB_TIMEOUT with the job id, and the request continues.

Set your HTTP client's timeout above 300 seconds, or do not use wait: true. The single commonest integration bug is a 30-second default abandoning a healthy request — and it looks exactly like Revoye failing, which it is not.

GET /v1/completions/{id}

Returns the same object. Statuses: queued, dispatched (being processed), succeeded, failed, cancelled, expired. A request that has not finished returns 200 with its real status and, when queued, a queue_position — its place among your queued requests in the order they are taken (higher priority first, then oldest first). 1 means it is next.

curl "https://api.revoye.com/v1/completions/job_01JAY7Q2K8XYZ" \
  -H "Authorization: Bearer $REVOYE_KEY"

Requires completions:read.

DELETE /v1/completions/{id}

Cancels a request. Returns 200 with the object.

  • A queued request is cancelled immediately.
  • A dispatched request is marked cancelled and stopped best-effort — it may already be nearly finished.
  • Cancelling a request that has already finished is a no-op, not an error.

Requires completions:write.

Limits

prompt100 000 characters
Request body1 MiB
Queued requests per account1 000 — beyond that, 403 FORBIDDEN with details.limit
wait: true requests held open per account100 — beyond that, the request is accepted and answered at once as with wait: false (202 and the queued object)
metadata4 KiB
Monthly tokensSet by your plan, per channel. See rate limits and capacity

Errors

CodeHTTPRetry?Cause
INVALID_REQUEST400Nodetails.field names the offender
UNAUTHORIZED401NoBad or revoked key
FORBIDDEN403NoKey lacks completions:write, or your queue is full
NOT_FOUND404NoNo such request, or not yours
CONFLICT409NoIdempotency key reused with a different body
PAYLOAD_TOO_LARGE413Noprompt or body over the limit
RATE_LIMITED429After Retry-AfterToo many API calls per minute on this key
CHANNEL_NOT_ENTITLED403NoYou named a channel your plan does not include
SUBSCRIPTION_INACTIVE403NoNo active plan. Choose one on the Billing page
QUOTA_EXCEEDED429After details.resets_atThis month's token allowance for the channel is used up
NO_DEVICE_ONLINE503YesRevoye Desk is not online on any of your devices (only when wait: false with no callback_url)
NO_AGENT_AVAILABLE503YesNo capacity is free right now to process the request — retry (only when wait: false with no callback_url)
PROVIDER_RATE_LIMITED503YesAn hourly limit was reached for that model. Retry later, or let Revoye choose
JOB_TIMEOUT504YesHeld as long as you asked; the request continues
JOB_FAILED502MaybeAttempts exhausted. details.attempts says how many
JOB_CANCELLED499NoCancelled by you or from the dashboard

When NO_DEVICE_ONLINE or NO_AGENT_AVAILABLE is returned, details.job_id names the request: it stays in your queue and is processed when capacity returns. Cancel it with DELETE if you no longer want it.

Full handling guidance: Errors.