POST /v1/completions
- Updated
- Reading time
- 5 min
- Level
- beginner
The native endpoint. Everything Revoye can do is expressible here — pick a provider or let the router choose, wait for the answer or queue it, set your own timeouts, continue an existing conversation, and attach metadata that comes back untouched.
curl https://revoyeapi.degird.com/v1/completions \
-H "Authorization: Bearer $REVOYE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f1c9d2e-8b3a-4c1d-9e2f-7a6b5c4d3e2f" \
-d '{
"prompt": "Summarise the CAP theorem in three sentences.",
"provider": "chatgpt",
"wait": true
}'Request
| Field | Type | Default | Notes |
|---|---|---|---|
prompt | string, 1–100 000 chars | required | The text typed into the provider |
provider | chatgpt | claude | gemini | deepseek | qwen | perplexity | null | null | null means any enabled provider, chosen by your rotation strategy |
agent_id | string | null | Pin to one agent. NO_AGENT_AVAILABLE if it is not eligible |
wait | boolean | true | Hold the HTTP request open until the job settles |
timeout_ms | 5 000–600 000 | 180 000 | Per attempt |
deadline_ms | 10 000–3 600 000 | 900 000 | For the whole job, across all attempts |
priority | −10…10 | 0 | Higher dispatches first within your own queue |
mode | new | continue | new | continue requires conversation_ref |
conversation_ref | string | null | Opaque; returned by a previous completion |
callback_url | https URL | null | Webhook on completion |
metadata | object ≤ 4 KiB | {} | Echoed back verbatim. Revoye never reads it |
Headers. Idempotency-Key is optional and strongly recommended — see
retrying safely.
Choosing a provider, or not
Omitting provider is usually right: your rotation strategy spreads work across everything you have
enabled, which is the point of running several accounts. Name a provider when the answer's quality
depends on which model produces it.
Naming one is a constraint, not a preference. Revoye will not silently answer a chatgpt request
from DeepSeek — receiving a different model than you asked for would be a correctness bug, not a
helpful fallback.
metadata is yours
Whatever you put in comes back out, byte for byte, on the completion and on the webhook. Use it to correlate a job 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": "The CAP theorem states that…",
"provider": "chatgpt",
"agent_id": "agt_01JAY7…",
"agent_name": "Agent 2",
"conversation_ref": "https://chatgpt.com/c/6f0a…",
"attempts": 1,
"queue_ms": 240,
"run_ms": 18432,
"created_at": "2026-08-18T09:14:02Z",
"finished_at": "2026-08-18T09:14:21Z",
"metadata": { "trace": "abc" }
}| Field | Means |
|---|---|
attempts | How many agents this job was dispatched to. > 1 means something timed out or failed |
queue_ms | How long it waited before its first dispatch. Set once, so a retried job still reports its original wait |
run_ms | Time spent actually executing |
conversation_ref | An opaque handle to the thread in the provider's own UI. Pass it back with mode: "continue" |
Response — wait: false (202)
{ "id": "job_01JAY7Q2K8XYZ", "status": "queued", "created_at": "2026-08-18T09:14:02Z" }Blocking semantics
wait: true is a long poll, not synchronous execution. The job is durable the moment it is
accepted, so:
- If your connection drops, the job keeps running.
- The result 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 job 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 healthy work — and it looks exactly like Revoye failing, which it is not.
GET /v1/completions/{id}
Returns the same object. Statuses: queued, dispatched, succeeded, failed, cancelled,
expired. A non-terminal job returns 200 with its real status and, when queued, a
queue_position.
Add ?wait=true to long-poll a job you created asynchronously.
curl "https://revoyeapi.degird.com/v1/completions/job_01JAY7Q2K8XYZ" \
-H "Authorization: Bearer $REVOYE_KEY"Requires completions:read.
DELETE /v1/completions/{id}
Cancels a job. Returns 200 with the job object.
- A
queuedjob is cancelled immediately. - A
dispatchedjob is marked cancelled and a cancel is relayed to the agent best-effort — the browser may already be mid-generation, and the extension cannot always stop it. - Cancelling a job that has already finished is a no-op, not an error.
Limits
| Prompt | 100 000 characters |
| Request body | 1 MiB |
| Queued jobs per account | 1 000 |
metadata | 4 KiB |
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 |
NOT_FOUND | 404 | No | No such job, 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 | Revoye's ingress limit |
NO_DEVICE_ONLINE | 503 | Yes | Nothing paired is connected (only when wait: false) |
NO_AGENT_AVAILABLE | 503 | Yes | Connected, but no agent is eligible right now |
PROVIDER_RATE_LIMITED | 503 | Yes | Your own hourly cap |
JOB_TIMEOUT | 504 | Yes | Held as long as you asked; the job continues |
JOB_FAILED | 502 | Maybe | Attempts exhausted. details.attempts explains |
JOB_CANCELLED | 499 | No | Cancelled by you or from the dashboard |
Full handling guidance: Errors.