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
| Field | Type | Default | Notes |
|---|---|---|---|
prompt | string, 1–100 000 chars | required | The text of your request |
provider | string | null | null | A model id from GET /v1/models. null lets Revoye choose, which is usually right |
channel | auto | desk | cloud | auto | Where it is processed: cloud on Revoye Cloud, desk by Revoye Desk on your device. See choosing a channel |
wait | boolean | true | Hold the HTTP request open until the request finishes |
timeout_ms | 5 000–600 000 | 180 000 | Per attempt |
deadline_ms | 10 000–3 600 000 | 900 000 | For the whole request, across all attempts |
priority | −10…10 | 0 | Higher starts first within your own queue |
mode | new | continue | new | continue requires conversation_ref |
conversation_ref | string | null | Opaque; returned by an earlier request. Send it back unchanged |
agent_id | string | null | Revoye Desk only, advanced: keep the request on the same Desk capacity as an earlier request, by the agent_id it returned |
callback_url | https URL | null | Webhook when the request finishes |
metadata | object ≤ 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.
| Field | Means |
|---|---|
response | The AI response, complete |
channel | desk or cloud — where it was processed, or will be |
fallback_at | While queued: when it may move to the other channel. Absent when it will not |
attempts | How many times processing was attempted. > 1 means an attempt timed out or failed |
queue_ms | How long it waited before processing first started. Set once, so a retried request still reports its original wait |
run_ms | Time spent processing |
provider | The model id that processed it, in the vocabulary of GET /v1/models |
agent_id, agent_name | Revoye Desk only: which Desk capacity processed it. Always null on Revoye Cloud |
conversation_ref | An opaque handle to the conversation. Pass it back with mode: "continue" to follow up |
content_pruned_at | When 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 yourcallback_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
queuedrequest is cancelled immediately. - A
dispatchedrequest 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
prompt | 100 000 characters |
| Request body | 1 MiB |
| Queued requests per account | 1 000 — beyond that, 403 FORBIDDEN with details.limit |
wait: true requests held open per account | 100 — beyond that, the request is accepted and answered at once as with wait: false (202 and the queued object) |
metadata | 4 KiB |
| Monthly tokens | Set by your plan, per channel. See rate limits and capacity |
Errors
| Code | HTTP | Retry? | Cause |
|---|---|---|---|
INVALID_REQUEST | 400 | No | details.field names the offender |
UNAUTHORIZED | 401 | No | Bad or revoked key |
FORBIDDEN | 403 | No | Key lacks completions:write, or your queue is full |
NOT_FOUND | 404 | No | No such request, or not yours |
CONFLICT | 409 | No | Idempotency key reused with a different body |
PAYLOAD_TOO_LARGE | 413 | No | prompt or body over the limit |
RATE_LIMITED | 429 | After Retry-After | Too many API calls per minute on this key |
CHANNEL_NOT_ENTITLED | 403 | No | You named a channel your plan does not include |
SUBSCRIPTION_INACTIVE | 403 | No | No active plan. Choose one on the Billing page |
QUOTA_EXCEEDED | 429 | After details.resets_at | This month's token allowance for the channel is used up |
NO_DEVICE_ONLINE | 503 | Yes | Revoye Desk is not online on any of your devices (only when wait: false with no callback_url) |
NO_AGENT_AVAILABLE | 503 | Yes | No capacity is free right now to process the request — retry (only when wait: false with no callback_url) |
PROVIDER_RATE_LIMITED | 503 | Yes | An hourly limit was reached for that model. Retry later, or let Revoye choose |
JOB_TIMEOUT | 504 | Yes | Held as long as you asked; the request continues |
JOB_FAILED | 502 | Maybe | Attempts exhausted. details.attempts says how many |
JOB_CANCELLED | 499 | No | Cancelled 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.