Send requests through Cloud Score
Call POST /v1/requests with a gateway key: the request fields, the response envelope, status versus HTTP code, error types, SSE streaming, and the two GET endpoints.
On this page
Prompt Optimization: optimize, review, then run
Prompt Optimization has one ON/OFF control, defaults ON, and accepts any prompt length. In Playground, Optimize prompt prepares a complete optimized prompt before answer routing, at the level you pick — Standard, Balanced or Thorough. Live counts use a real backend tokenizer. Review the original, optimized version and diff, then choose Run original or Run optimized. Our checks cannot guarantee that meaning is preserved, which is why you review the result. Editing the request invalidates its comparison. Workspace defaults are under Optimization › Settings.
For an interactive native flow, POST the full request to /v1/prompt-optimizations/prepare as {"request_type":"native","request":{...}} using the same bearer execution key. Include authoring options with the optimization strength (deterministic, balanced or aggressive) and the editable message indices. Then send the unchanged source request to /v1/requests with constraints.prompt_optimization containing enabled, proposal_id, the sealed proposal_token and choice (original or optimized). Use a distinct Idempotency-Key for preparation and execution. Keep the sealed token in memory only. Explicit {"enabled":false} preserves the original. Legacy compression fields are deprecated compatibility aliases; do not combine conflicting old and new controls.
Playground explicitly requests a bounded optimization and shows its recorded cost. A rejected, unavailable or unhelpful result falls back to your original without hiding its expense. Requests sent ON without a prepared proposal are optimized by structural rules only. Comparison details show the outcome, the rules applied and the measured work time. Infrastructure still consumes resources; unallocated compute keeps fully loaded savings unknown. Historical optimization expenses remain recorded and are not reset to zero.
The execution receipt distinguishes the selected prompt from the prompt actually sent for the answer, including no provider send when a cache supplies it. Optimization can have a recorded cost even when the original is selected or no answer is generated. Unknown infrastructure cost keeps fully loaded net unknown. Fewer tokens do not establish a latency or quality improvement. Analytics, ROI and Reports reuse Prompt Optimization evidence with production/demo filters and daily, weekly and monthly views.
See Playground for comparison and recovery, and Analytics for measurement coverage. Historical compression receipts do not establish choices or costs that were never recorded.
The endpoint
The native endpoint is POST /v1/requests. Your application sends one input and, optionally, constraints; Cloud Score classifies it, picks a model, calls it with retries and failover, checks the answer, and returns an envelope that says what happened and what it cost. Every call is logged and appears in Logs.
Your app
POST /v1/requests with a dex_sk_ key
Cloud Score pipeline
classify, match, policy, cache, select, execute, guard, validate
Envelope
id, status, output, routing, usage, error
The same four endpoints are listed in the app under Admin › Developer › Endpoints, with the purposes below. This page covers the three native ones; the OpenAI-compatible one has its own page.
Method | Path | Purpose |
|---|---|---|
POST | /v1/requests | Native API — strategies, constraints, decision trace, SSE streaming. |
GET | /v1/requests/{id} | Retrieve a past request with its full routing trace. |
POST | /v1/chat/completions | OpenAI-compatible. model: "auto" routes; a concrete name pins. |
GET | /v1/models | Models enabled for your workspace, with capabilities and pricing. |
Authenticate
Send a gateway key on every request as Authorization: Bearer dex_sk_…. Keys are created under Admin › API keys with Create key and shown once; the key decides the workspace, the security profile, the budget, body storage, and the sandbox flag a request runs under.
| Header | Direction | What it does |
|---|---|---|
Authorization: Bearer dex_sk_… | request, required | Authenticates the call. Invalid and disabled keys get the same 401 with the message missing or invalid API key. |
Content-Type: application/json | request | The body is JSON. |
X-Dex-Error-Status: http|envelope | request, optional | Chooses the status contract for this one request. See Status versus HTTP code. |
X-Dex-Cache-Control: no-cache|no-store | request, optional | The cache directive as a header, with the same two bypass values as constraints.cache. |
X-Dex-team, X-Dex-application, X-Dex-agent, X-Dex-environment, X-Dex-end-user | request, optional | Header form of metadata for cost attribution. The body wins over these headers; these headers win over the key's stored defaults. |
X-Dex-Status-Contract: envelope|http | response, always | Names the contract that produced the response, so a client never has to infer it. |
X-Dex-Budget-State: ok|soft|hard|error|none | response, non-stream | The budget verdict for this request. The Playground does not read it. |
Every authenticated call updates the key's last-used timestamp, is policy-checked, logged, and billed. Revoke and re-create keys from Admin › API keys. Keys carry no per-model restrictions; model availability is workspace-level.
The request body
Only input is required. Everything else narrows or annotates the request. A body that sends a cost-first strategy, a cost cap, a JSON requirement, and attribution tags:
{
"input": "Summarize this quarterly update in three bullets: …",
"constraints": {
"strategy": "cost",
"max_cost_usd": "0.005",
"required_capabilities": ["json_mode"]
},
"response_format": { "type": "json_object" },
"stream": false,
"metadata": { "team": "growth", "application": "onboarding-bot" }
}| Field | Values | What it does |
|---|---|---|
input | string, required | The text to classify and route. |
intent | string, optional | Optional free text, returned by GET /v1/requests/{id}. |
constraints.strategy | auto · balanced · cost · intelligence · latency | Overrides the scoring weights for this request. auto is no override and defers to policies and workspace defaults; balanced pins 40 / 20 / 40 even where a policy prefers a tier. See Routing strategies. |
constraints.quality | low · balanced · high | Maps to the cost, balanced, and quality presets. An explicit constraints.strategy takes precedence. |
constraints.max_cost_usd | decimal, as a string | Rules out candidates priced above it; the lower of this and a policy cap applies. With nothing left the request fails no_candidates. |
constraints.max_latency_ms | integer | Lowers the request deadline and, when no constraints.quality is sent, applies the latency preset. Running out of time ends the request deadline_exceeded. |
constraints.model_override | a model key | Pins one model and disables cross-model failover. An unknown or disabled key ends no_candidates. |
constraints.required_capabilities | json_mode · streaming · tools · vision | Added to what the classifier infers; models lacking them are excluded at Match. An unknown value is a 422. |
constraints.policy_ids | list of policy ids | Evaluate only these policies. Sandbox keys only, otherwise a 403 policy selection requires a sandbox API key. An empty list means no policies; the budget gate always runs. |
constraints.cache | default · no-cache · no-store | no-store: nothing is read or written. no-cache: the lookup is skipped, the write-back is kept. Recorded as bypass_no_store / bypass_no_cache. |
response_format | json_object · json_schema | Adds the json_mode capability, turns on validation with one re-prompt, and disables the semantic cache. A failed re-prompt ends validation_failed with partial_output. |
stream | boolean | Switches the response to SSE. See Stream with SSE. |
metadata | team · application · agent · environment · end_user | Cost attribution tags, echoed back as routing.attribution. Other keys are ignored; values are capped at 120 characters. |
Pinning a model removes failover.
The app's Explicit model mode warns: Pinning a model disables cross-model failover — a hard provider failure fails the request instead of rerouting. Availability is workspace-level; keys don't carry per-key model restrictions.
The response envelope
The envelope is the same for a routed answer, a cache hit, a block, and a failure. The sample below is the one the app shows under Admin › Developer › Response envelope.
{
"id": "req_5f8e6d2a-…",
"status": "completed",
"output": "• Revenue grew 18% QoQ…",
"routing": {
"model": "gpt-4o-mini",
"provider": "openai",
"classifier": "llm",
"task_type": "summarization",
"complexity": 2,
"candidates_considered": 6,
"attempts": 1
},
"usage": {
"input_tokens": 412,
"output_tokens": 96,
"cost_usd": 0.000119,
"latency_ms": 842
}
}| Member | Meaning | When absent |
|---|---|---|
id | The request id, prefixed req_…. Use it with GET /v1/requests/{id} and to find the row in Logs. | never |
status | completed, blocked, or failed. The outcome lives here, not in the HTTP code. | never |
output | The answer. On a stream, the value here is authoritative over the deltas you were shown. | null when nothing was delivered, for example a block at Guard out |
routing.model, routing.provider | The model that answered and its provider. | null when the request ended before a model was selected |
routing.classifier | Where the classification came from: llm, cache, or heuristic. | null when not classified |
routing.task_type, complexity | The classified task type and its 1 to 5 complexity. | null when not recorded |
candidates_considered | How many enabled models passed the capability and context-window filter. | null when not recorded |
routing.attempts | Billed provider calls, counting failover, the validation re-prompt, and the output-budget rollback. Same-model transient retries do not count. | never; 0 is a real count |
routing.cache | status (hit / miss / ineligible / off), source (exact / semantic), reason, similarity. See Caching and deduplication. | similarity only on semantic paths |
routing.attribution, routing.budget, routing.optimization | The resolved attribution tags, the budget verdict, and the optimization plan summary. | not in the static sample; added by the backend when present |
usage.input_tokens, usage.output_tokens | Tokens billed across every provider call. | null, for example when blocked before execution |
usage.cost_usd | Measured list-price cost. A served cache hit is 0. | null when blocked before execution |
usage.latency_ms | End-to-end latency. | null when not recorded |
usage.saved_usd, usage.saved_tokens | What serving from the response cache avoided, estimated at the producing model's price. | only on cache hits; stripped if Guard out blocks the hit |
usage.cached_input_tokens, usage.provider_saved_usd | Provider-side prompt-cache reads and the estimated discount. | only when the provider reported cached reads |
usage.reasoning_tokens | Reasoning tokens, where the provider reports them. | null when the provider reports none |
security | What AI Security did to the prompt and the answer under the key's profile. | null when not recorded |
error.type, error.message | Why the request is blocked or failed. See Error types. | absent when status is completed |
Money is a float here.
usage.cost_usd, usage.saved_usd, and usage.provider_saved_usdare JSON floats on this endpoint, a grandfathered exception to the platform's rule that money is a decimal string. Everything the admin app shows about the same request uses decimal strings.
Status versus HTTP code
Check the envelope, not the HTTP code.
The app states the rule as: The native API answers HTTP 200 even for failed requests — the outcome lives in status. Always check the envelope, not the HTTP code.
That is the default contract, envelope. A key, or one request, can opt into real status codes with the http contract. Either way the response header X-Dex-Status-Contract names which one applied, and under http a non-2xx body is still the full envelope.
| Contract | How it is chosen | What you get |
|---|---|---|
envelope | The default when neither the request header nor the key says otherwise. Streams always use it. | HTTP 200 for every settled request; read status and error.type. |
http | Send X-Dex-Error-Status: http, or have the key's stored default set to it. The request header wins over the key. | 403 for blocked, 409 for no_candidates, 502 for provider_error and validation_failed, 504 for deadline_exceeded, each with the full envelope as the body. |
no_candidates maps to 409, not a 5xx, so an SDK retry policy does not amplify a deterministic configuration failure. Plain 400, 401, 403, 404, and 422 responses (a bad key, a non-sandbox key sending constraints.policy_ids, an unknown request id, a malformed body) are not envelopes; they carry a message only.
Error types
error.type says which stage gave up; error.messageis the stage's own text. The four types the app lists under Response envelopeare quoted with the app's meaning; the last two are recorded the same way.
error.type | Envelope status | Meaning | Messages you will see |
|---|---|---|---|
no_candidates | failed | No enabled model satisfies the constraints (including an unknown or disabled pinned model). | no enabled model matches the required capabilities and context window (at Match) or no candidate models remain after constraints, policies, and failover exclusions (at Select). |
blocked | blocked | A routing policy or budget blocked the request before execution. | blocked by policy '<name>' (or the policy's own message), or budget exhausted: <scope> <scope_value> at <utilization_pct>% (model_spend). |
provider_error | failed | Every eligible model failed after retries and failover. | <model_key>: <scrubbed provider error>, truncated to 300 characters and scrubbed of secrets. |
validation_failed | failed | The output failed validation; a partial_output preview is included. | empty response, or response failed schema validation. partial_output holds the first 500 characters unless a security profile enforces output. |
security_blocked | blocked | AI Security blocked the prompt at Guard in or withheld the answer at Guard out. | The profile's own blocked reason, else blocked by AI Security. See AI Security. |
deadline_exceeded | failed | The request deadline ran out before another billed attempt could start. Does not fail over. | <model_key>: request deadline exhausted before another attempt could start. |
Stream with SSE
Send stream true and the response is text/event-stream. Frames use the format event: <name>\ndata: <json>\n\n. Five event names occur; the Playground's Stream the answer option uses the same stream to draw the stations in Details › Routing.
| Event | Data | What to do with it |
|---|---|---|
dex_trace | One event per pipeline stage: stage, at, ms, detail. Never retracted. | Optional. Render progress, or ignore. The generated SDK snippets skip it. |
delta | {text}, settled output only. | Append to what you show. |
reset | A reason: validation_failed, provider_error, or output_budget_rollback. | Discard everything shown so far; a re-prompt, failover, or rollback follows. The Playground shows Output failed validation. Retrying… or Attempt failed. Rerouting…. |
done | The full envelope. | Authoritative. Replace the streamed text with output and check status. |
error | {message}, only on an unexpected pipeline crash; the message is internal error. | Treat as a failed request. |
A stream can end without done.
A dropped connection closes the stream with no final event. Treat a stream that ends without done or error as failed; the Playground reports it as stream closed before completion. Closing the connection from your side does not cancel the request: it finishes, is recorded, and is billed server-side.
Streams always use the envelopecontract. When the key's security profile enforces output (a redact or block action on any output category), live deltas are switched off and the answer arrives in one piece after Guard out settles; the Playground shows the Guard in station as buffered.
Retrieve a request and list models
| Call | Returns | Notes |
|---|---|---|
GET /v1/requests/{id} | The recorded request: status, output, intent, classification, usage, error, and the full trace (the same stations Logs draws). | An unknown id is a 404 with request not found. The Playground calls this after a non-streaming send to fill Details › Routing. |
GET /v1/models | A list envelope, "object": "list", whose data rows carry id, object, owned_by, display_name, capabilities, context_window, and pricing as strings. | Workspace-scoped: the models enabled under Models, which is the set constraints.model_override may pin. |
Examples
Cheapest JSON-capable model under a cap
- What you do
Send the body shown above:
constraints.strategycost,constraints.max_cost_usd0.005,constraints.required_capabilitiesjson_mode.- What Cloud Score does
Keeps only enabled models that can emit JSON, drops any priced above $0.005, scores the rest with the cost preset (70% cost · 10% latency · 20% quality), calls the winner, and validates the answer parses.
- What you see
HTTP 200 with
statuscompletedand the winner inrouting.model. If every JSON-capable model costs more than the cap:statusfailed,error.typeno_candidates, and the messageno candidate models remain after constraints, policies, and failover exclusions.
Real HTTP codes for a retry policy
- What you do
Add the header
X-Dex-Error-Status: httpto every call and retry only on 502 and 504.- What Cloud Score does
Answers a policy or budget block with 403, an empty candidate set with 409, an exhausted failover or failed validation with 502, and an exhausted deadline with 504, each with the full envelope as the body.
- What you see
X-Dex-Status-Contracthttpon every response. A 409no_candidatesis not retried, because it will not change until the workspace does; a 502provider_erroris.
Good to know
Three billed attempts per request.
Failover, the validation re-prompt, and the output-budget rollback share the cap of 3 billed provider calls; same-model transient retries (up to 2, after 0.5 s and 1.0 s) do not count. Setup failures that never reach a provider have their own cap of 6 and bill nothing. routing.attempts reports the billed count.
auto defers, balanced pins.
constraints.strategy auto inherits a policy's preferred tier and the sliders under Admin › Organization; balanced and constraints.quality balanced both pin 40 / 20 / 40. Only an absent constraint defers. A strategy changes scoring only: blocks, allow and deny lists, forced models, cost caps, budgets, and guards still apply.
Sandbox keys and policy selection.
constraints.policy_ids is accepted only from a key created with Sandbox key — may hand-pick policies per request (playground) checked; other keys get a 403. It narrows which policies are evaluated, never the budget gate.
Unknown capabilities are rejected.
An unknown capability in constraints.required_capabilities is a 422.
Try it before wiring code.
The Playground sends this endpoint with input, system, stream, and the constraints strategy, quality, max_cost_usd, max_latency_ms (Fast), required_capabilities, cache, prompt_optimization and, for sandbox keys, policy_ids, each only when set, and shows every dex_trace event in Details › Routing. Admin › Developer generates a Cloud Score client for Python, TypeScript, or Go that sends the body and parses the stream for you.
Related
- OpenAI-compatible endpointPoint the official OpenAI SDK at the Cloud Score gateway: POST /v1/chat/completions routes when model is auto, pins when it is a model key, accepts dex_strategy, and answers with a dex_routing block, HTTP 403 on a block, 409 when no tool-capable model is available, and 502 on any other failure.
- Attribute cost to teams and appsTag every request with team, application, agent, environment, and end_user so Usage & cost can break spend down by them as reported tags, budgets can cap a team or an app, and alert rules can watch them.
- SDK snippetsAdmin › Developer generates a copy-paste client for Python, TypeScript, or Go, against the native API or the OpenAI SDK, with a routing strategy or a pinned model baked in.
- Statuses and badgesEvery status word and badge Cloud Score shows, what each one means, and the page it appears on.
- TroubleshootingThe messages Cloud Score puts in front of you, grouped by the page that shows them, with what caused each one and what to do next.
- How Cloud Score routes a requestThe fixed sixteen-stage pipeline every gateway request runs, what each stage decides, the three outcomes, how failover works, and which admin page configures which stage.