Gravity Feed API reference
Every Gravity Feed route, authentication, rate limits and QPS, idempotency, plan expiry and error codes.
This is the complete reference for the Gravity Feed REST API, version 1. It covers the base address, authentication, every route, rate limits, idempotency, plan freshness and error codes. The request and response fields are described in the data model.
Base address
| Item | Value |
|---|---|
| Base URL | https://spatial9.ai/api/gravity-feed/v1 |
| Format | JSON over HTTPS |
| Schema version | 1.0 |
| Maximum body | 65,536 bytes |
Authentication
| Caller | How to authenticate | Counted as |
|---|---|---|
| DAW script, plugin or server | X-API-Key: s9gf_... or Authorization: Bearer s9gf_... | rest |
| Spatial9 website | Your signed-in session | web |
MCP tool gravity_feed_plan | Your Spatial9 MCP sign-in | mcp |
A2A skill gravity_feed_plan | Your Spatial9 A2A sign-in | a2a |
Keys are created and revoked in the console. Only a hash of each key is stored.
Routes
| Method and path | Auth | Uses limit | Purpose |
|---|---|---|---|
GET /capabilities | None | No | Schema version, limits, styles, shapes, effect kinds, transports |
GET /profiles | None | No | Gravity Profiles with parameters, ranges, OSC addresses and status |
POST /validate | None | No | Check a request body without planning |
POST /plans | Required | Yes | Create a plan |
GET /usage | Required | No | Your last 30 days of requests and current allowance |
GET /keys | Signed-in session | No | List your keys |
POST /keys | Signed-in session | No | Create a key, body { "name": "..." } |
DELETE /keys/{id} | Signed-in session | No | Revoke a key |
POST /validate
Returns { "valid": true|false, "schema_version": "1.0", "fields": [...] }. Each field error has path, code and message.
GET /usage
{
"window_days": 30,
"truncated": false,
"rate_limit": { "sustained_qps": 1, "burst": 5, "available_now": 5 },
"totals": { "last_24h": 12, "last_30d": 140, "succeeded": 138, "failed": 2,
"by_transport": { "rest": 120, "web": 10, "mcp": 8, "a2a": 2 } },
"latency_ms": { "p50": 2100, "p95": 4800 },
"recent": []
}
latency_ms is null until you have a successful request. The numbers above illustrate the shape only.
Keys
GET /keys returns { "keys": [{ "id", "name", "key_prefix", "created_at", "last_used_at", "revoked_at" }] }. POST /keys returns { "key": {...}, "secret": "s9gf_...", "notice": "..." }; the secret is never shown again. DELETE /keys/{id} returns { "revoked": "<id>" }.
Rate limits and QPS
QPS means queries per second. One query is one POST /plans call, however many tracks it covers (up to 32) and however long the window (up to 128 beats).
| Plan | Sustained | Burst | Monthly volume |
|---|---|---|---|
| Free | 1 query per second | 5 | Unlimited for now |
| Higher volume | By agreement | By agreement | Contact sales |
The limit is a token bucket per account. It holds up to 5 tokens and refills at 1 per second. Every plan request uses one token, whether it arrives by REST, MCP, A2A or the website, and whichever key it uses. Creating more keys does not raise the limit.
Every plan response carries:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Sustained queries per second |
X-RateLimit-Burst | Bucket size |
X-RateLimit-Remaining | Whole tokens left right now |
Retry-After | On 429, seconds to wait |
Idempotency
request_id is your idempotency key. Sending the same request_id with the same body returns the stored result with Idempotent-Replayed: true and does not use a token. A different body under the same request_id returns 409 idempotency_conflict. While the first request is still running, a repeat returns 409 request_in_progress with Retry-After: 2.
Plan freshness
Plans are proposals. Each one records preconditions.session_id, preconditions.session_revision and an input hash, and expires 600 seconds after creation. An adapter must not write a plan when the session revision has changed or expires_at has passed; it requests a new plan instead.
Errors
Errors always use this shape:
{ "error": { "code": "invalid_request", "message": "The request did not match schema 1.0.", "fields": [] } }
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_json | Send valid JSON |
| 401 | unauthorized | Send a valid key or sign in |
| 404 | not_found | Check the path, version or key id |
| 405 | method_not_allowed | Use the documented method |
| 409 | idempotency_conflict | Use a new request_id |
| 409 | request_in_progress | Wait for Retry-After, then repeat |
| 409 | key_limit | Revoke a key before creating another |
| 413 | payload_too_large | Send fewer tracks or shorter names |
| 422 | invalid_request | Fix the listed fields |
| 429 | rate_limited | Wait for Retry-After; contact sales for more |
| 500 | internal_error | Retry later |
| 502 | inference_failed | Retry with the same request_id |
| 503 | admission_unavailable | Wait for Retry-After, then repeat |
FAQ
Does a validate call count against my limit?
No. /validate, /capabilities, /profiles, /usage and key management never use a token.
Do MCP and A2A have separate limits?
No. All four ways in share one bucket per account.