# rapid.dance > Add rapid LLM decision making directly to your app. Most AI calls in a product are small: which repo did the user mean, is this ticket urgent, what should this thing be called. rapid.dance gives each of those questions its own URL as a JSON API. If the model can't decide within a defined time limit, the caller gets a default you set instead of an error. This file is written for coding agents. It covers what a query is, how to create and refine one through the management API, and how to call it from an application. The API base for this deployment is https://api.rapid.dance; the web app is at https://rapid.dev. ## What a query is A query is a name, a slug, one line or a paragraph of instructions, and a list of output fields. Once saved it is an HTTP endpoint: POST https://api.rapid.dance/{account}/{slug} Authorization: Bearer rq_… {"…": "any JSON object"} HTTP/2 200 OK X-Rapid-Status: ok X-Rapid-Latency-Ms: 121 {"field": "value", …} exactly the fields you defined, always all of them The contract that makes it safe to put inline in a user flow: - The response is constrained to the fields you define. Nothing is missing, nothing extra, and there is nothing to parse other than JSON. - It arrives within the query's `max_time_ms`, or the caller gets the **fallback object** instead: each field's `default`, or `""`, `0`, `false`, `[]` by type. That is still HTTP 200, with `X-Rapid-Status: ` and `X-Rapid-Fallback: true` headers. Any other failure is handled the same way. A query can be switched to `on_failure: "error"` to get 502, 503 or 504 instead. - The input is handed to the model as data, separately from the instructions, so text inside the input cannot rewrite what the query does. Still treat the output as untrusted data and validate anything that matters. - Price is a flat amount per request, by tier, not per token. ## Two kinds of key Both are created by a person on the API keys page at https://rapid.dev/app/keys. - `rq_…` **call key**: calls queries. This is the one that goes in the application, as `RAPID_API_KEY` in its environment or secret store. - `rm_…` **management key**: everything in this file, and it can call queries too. This is what you, the agent, use. Read it from `RAPID_MANAGEMENT_KEY`. Never write it into source files, committed config, or chat output, and never use it inside application code; mint a call key for that (below). A management key can mint and revoke call keys, but never other management keys; those are made and revoked by a person. ## Requests Start with this call. It confirms the key works and tells you the account slug, which is part of every query's endpoint: curl -s https://api.rapid.dance/account -H "Authorization: Bearer $RAPID_MANAGEMENT_KEY" {"account": {"id": "acc_…", "slug": "demo", "name": "Demo"}, "key": {"id": "key_…", "name": "agent", "kind": "management", "prefix": "rm_…"}, "api_base": "https://api.rapid.dance"} Request bodies are JSON objects. A member the API does not know is a 400, so a typo cannot pass silently. Replies are JSON. Authentication here is explicit, unlike the call endpoint, which answers every problem with the same 404: no key, an unknown key or a revoked key is 401; a call key is 403. ## The query object This is what you send when creating or changing a query, and what every read returns. - `name`: display name. Required. - `slug`: the last part of the endpoint URL: lowercase letters, digits and hyphens. Derived from the name when omitted. Unique among the account's live queries. - `instructions`: what to do with the input. Required. Say what each output field should contain and what to answer when the input does not allow a confident answer. The input is handed over as data, separately from these instructions, so never paste user text into them. - `output_fields`: a list of `{name, type, description, default, enum}`. Required, at least one. Every field is always present in the answer. The `default` is what the field holds in the fallback object; without one it is `""`, `0`, `false` or `[]`. - `tier`: `S`, `M`, `L`, `XL`. Sets the output size and caps `max_time_ms` and the input size. Default `S`. - `max_time_ms`: the end-to-end time budget. When it runs out the caller gets the fallback object. Default 1000, at least 50, at most the tier's bound. - `on_failure`: `fallback` (default): failures answer 200 with the fallback object and status headers. `error`: failures answer 502, 503 or 504. - `log_input`: whether request inputs are kept in the logs. Default `true`. - `enabled`: a disabled query answers with the fallback object (or 503 in error mode). Default `true`. - `sample_input`: a JSON object shown in the editor and used by test runs. `null` clears it. Read-only members in every reply: `id` (`qry_…`), `version` (goes up on every change; the editor keeps each version), `endpoint` (the absolute URL to call), `fallback` (the object callers get when the query cannot answer), `created_at` and `updated_at`. Field types: `string`, `number`, `integer`, `boolean` are plain values. `enum` is one of the strings listed in `enum` and needs a `default` from that list. `string[]` and `number[]` are flat lists. Nested objects are not supported. Keep fields few and flat; small answers come back faster and are easier to get right. ## Designing a query Before creating anything, decide: 1. **Input**: the JSON object the application will send. Keep it to what the answer needs; the tier caps the input size. 2. **Output fields**: as few and as flat as possible. Give every field a `description`; that text is what the model reads to know what to put there. 3. **Defaults**: the fallback object is built from them, so pick values the application can act on without a special error path (an empty string, `false`, an `"other"` enum value). Tell the model in the instructions to use those same values when unsure, so "unsure" and "failed" look alike to the caller. 4. **Instructions**: plain and specific. Say what each field must contain and what to do when the input does not allow a confident answer. 5. **Tier and time**: start with `tier: "S"` and `max_time_ms: 1000`. Go up a tier only if the output is long or the input is large. Raise the time only if tests time out. ## Create a query Say you run a deployment platform. A user has a few repositories connected and names a new deployment "blog". You want to guess which repo it's for: curl -s -X POST https://api.rapid.dance/queries \ -H "Authorization: Bearer $RAPID_MANAGEMENT_KEY" \ -d '{ "name": "Repo selector", "slug": "repo-selector", "instructions": "Given the user'\''s repos and the name they chose for a deployment, pick the repo the deployment most likely belongs to. If none is a plausible match, leave selected_repo empty.", "output_fields": [ {"name": "selected_repo", "type": "string", "description": "Full repo name, or empty if unsure"} ], "tier": "S", "max_time_ms": 1000, "sample_input": {"repos": ["myexample/myblog", "myexample/waitlist-api"], "deployment_name": "blog"} }' HTTP/2 201 Created {"id": "qry_…", "slug": "repo-selector", "name": "Repo selector", "instructions": "…", "output_fields": [{"name": "selected_repo", "type": "string", "description": "Full repo name, or empty if unsure"}], "tier": "S", "max_time_ms": 1000, "on_failure": "fallback", "log_input": true, "enabled": true, "sample_input": {"repos": ["myexample/myblog", "myexample/waitlist-api"], "deployment_name": "blog"}, "version": 1, "endpoint": "https://api.rapid.dance/demo/repo-selector", "fallback": {"selected_repo": ""}, "created_at": "…", "updated_at": "…"} A slug that is already in use is a 409: read the existing query and decide with the person whether to change it or pick another slug. A definition with problems is a 400 `validation_failed` whose `error.details` lists each one in plain words; fix each item and retry. ## Test it curl -s -X POST https://api.rapid.dance/queries/repo-selector/test \ -H "Authorization: Bearer $RAPID_MANAGEMENT_KEY" \ -d '{"input": {"repos": ["myexample/myblog", "myexample/waitlist-api"], "deployment_name": "blog"}}' {"request_id": "req_…", "status": "ok", "http_status": 200, "fallback": false, "output": {"selected_repo": "myexample/myblog"}, "reasoning": "…why the model chose that…", "latency_ms": 412, "version": 1} A test run is a real call: it is billed like one and appears in the logs, but it runs even when the query is disabled and it does not count toward the account's daily cap. The `reasoning` text is the useful part when an answer is wrong: read it, change the instructions or a field description, run the same input again. Try three to five realistic inputs, including one that should produce the fallback, before you call the query done. For each, look at: - `output`: is it what the application needs? - `status`: `ok` is a real answer; `empty` means the model answered with exactly the fallback values (fine when that was the right call); `timeout`, `invalid_output` and `provider_error` mean the caller would have received the fallback object. - `latency_ms` against `max_time_ms`: leave headroom. When it is close, shorten the output, raise the budget within the tier, or move up a tier. The input you test with is saved as the query's sample input. Stop iterating when every sample input gives the right output with status `ok` or an intended `empty`, and show the person the inputs and outputs you verified. ## Change it `PATCH` changes only the members you send. `PUT` replaces the whole definition: anything you leave out goes back to its default, so send the complete object. curl -s -X PATCH https://api.rapid.dance/queries/repo-selector \ -H "Authorization: Bearer $RAPID_MANAGEMENT_KEY" \ -d '{"instructions": "…the improved instructions…"}' Either way the reply is the full query with `version` increased by one. Queries can be addressed by slug or by id. ## Read, list and delete GET https://api.rapid.dance/queries # {"queries": [...]} GET https://api.rapid.dance/queries/repo-selector # one, by slug or by id DELETE https://api.rapid.dance/queries/repo-selector # 204 After a delete, calls answer 404 and the query is gone as far as the API is concerned: reading, changing or testing it is a 404 too. Its logs are kept, and a person can restore it in the editor. ## Call keys curl -s -X POST https://api.rapid.dance/keys \ -H "Authorization: Bearer $RAPID_MANAGEMENT_KEY" \ -d '{"name": "production"}' HTTP/2 201 Created {"id": "key_…", "name": "production", "kind": "call", "prefix": "rq_…", "key": "rq_…full key…", …} The full key appears once, in the create reply. Put it where the application reads secrets from, as `RAPID_API_KEY`, never in source, and do not print it. `GET /keys` lists keys without secrets; `DELETE /keys/{id}` revokes a call key (revoking a management key is 403). ## Call the query from the application The endpoint is the `endpoint` member of the query: `https://api.rapid.dance/{account-slug}/{query-slug}`. The body is the input object. It is always parsed as JSON, so `Content-Type` is optional. Success returns the output object directly; failures return the fallback object with `X-Rapid-Status` and `X-Rapid-Fallback: true` headers. Read those headers only if the application should behave differently when it got the fallback; otherwise just use the body, which is always the right shape. curl: curl -s -X POST https://api.rapid.dance/demo/repo-selector \ -H "Authorization: Bearer $RAPID_API_KEY" \ -d '{ "repos": ["myexample/myblog", "myexample/waitlist-api"], "deployment_name": "blog" }' {"selected_repo": "myexample/myblog"} JavaScript (server side): const res = await fetch(process.env.RAPID_ENDPOINT, { method: "POST", headers: { "Authorization": `Bearer ${process.env.RAPID_API_KEY}` }, body: JSON.stringify({ repos, deployment_name }), }); const { selected_repo } = await res.json(); // always present; "" when unsure or on fallback const wasFallback = res.headers.get("X-Rapid-Fallback") === "true"; Python: import os, requests r = requests.post(os.environ["RAPID_ENDPOINT"], headers={"Authorization": f"Bearer {os.environ['RAPID_API_KEY']}"}, json={"repos": repos, "deployment_name": name}, timeout=5) selected = r.json()["selected_repo"] was_fallback = r.headers.get("X-Rapid-Fallback") == "true" Go: body, _ := json.Marshal(map[string]any{"repos": repos, "deployment_name": name}) req, _ := http.NewRequest("POST", os.Getenv("RAPID_ENDPOINT"), bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+os.Getenv("RAPID_API_KEY")) resp, err := http.DefaultClient.Do(req) // decode into a struct with a SelectedRepo string `json:"selected_repo"` field If setting headers is awkward (a webhook, a no-code tool), put the key in the body instead as a top-level `__auth` field. It is removed before the input reaches the model and is never logged. Set the HTTP client's timeout a little above the query's `max_time_ms`; a reply is always written by then. Keep the key server side; there is no CORS on purpose. Where to put the pieces: the endpoint URL and the call key belong in configuration (`RAPID_ENDPOINT`, `RAPID_API_KEY`), the request and response code in one small function, and the fallback value should already be a valid "no answer" for the feature so the application needs no special error path. ## Reference Management endpoints (Bearer rm_ key): GET https://api.rapid.dance/account who am I: account slug, key, api_base GET https://api.rapid.dance/queries list POST https://api.rapid.dance/queries create → 201 GET https://api.rapid.dance/queries/{slug|id} read PATCH https://api.rapid.dance/queries/{slug|id} change the members sent PUT https://api.rapid.dance/queries/{slug|id} replace the definition DELETE https://api.rapid.dance/queries/{slug|id} delete → 204 POST https://api.rapid.dance/queries/{slug|id}/test {"input": {…}} → run it, with reasoning GET https://api.rapid.dance/keys list keys (no secrets) POST https://api.rapid.dance/keys {"name": "…"} → 201 with the full rq_ key, once DELETE https://api.rapid.dance/keys/{id} revoke a call key → 204 Call endpoint (Bearer rq_ or rm_ key, or a top-level "__auth" member in the body when headers are not possible): POST https://api.rapid.dance/{account}/{slug} JSON object in, output object out Response headers on calls: `X-Rapid-Request-Id` (find the call in the logs), `X-Rapid-Status` (`ok`, `empty`, or the failure kind), `X-Rapid-Latency-Ms` (end-to-end time inside rapid.dance), `X-Rapid-Fallback: true` (present when the fallback object was returned). Statuses (`X-Rapid-Status` on calls, `status` in test results): ok, empty, timeout, invalid_output, provider_error, rate_limited, disabled. Tiers (per request): S: input ≤ 4 KB, output ≤ 256 tokens, max time ≤ 1000 ms, $0.0010 M: input ≤ 4 KB, output ≤ 1024 tokens, max time ≤ 2000 ms, $0.0030 L: input ≤ 16 KB, output ≤ 4096 tokens, max time ≤ 5000 ms, $0.010 XL: input ≤ 16 KB, output ≤ 16384 tokens, max time ≤ 30000 ms, $0.030 Calls that engage the model (ok, empty, timeout, invalid output) are billable. Client errors, rate limits, disabled queries and provider errors are not. Management API errors: 400 bad_request (not a JSON object, unknown member, or test input not an object), 400 validation_failed (`error.details` lists the problems), 401 unauthorized, 403 forbidden (call key used, or revoking a management key), 404 not_found, 405 method_not_allowed (the `Allow` header lists the right verbs), 409 conflict (slug in use), 413 too_large (body over 256 KB, or test input over the tier's limit), 429 rate_limited. Bodies: {"error": {"code": "…", "message": "…", "details": ["…"]}}. Call endpoint errors: 401 unauthorized (no key at all), 404 not_found (unknown or revoked key, or unknown slug), 400 bad_request (not a JSON object), 413 too_large, 429 rate_limited / daily_limit. Bodies: {"error": {"code": "…", "message": "…", "request_id": "req_…"}}. Human-readable docs: https://rapid.dev/docs (calling queries) and https://rapid.dev/docs/management. Claude Code skill: https://rapid.dev/skill.md