Authentication
Most API requests require authentication via API key. Public endpoints (validate, render, normalize) do not require a key - see Endpoints for details.
API key format
Keys are prefixed for identification: dsp_sk_<random-32-hex-chars>
The server stores only the SHA-256 hash of the key. The plaintext key is shown once on creation and cannot be retrieved again.
Sending your key
Include the key in the Authorization header:
Authorization: Bearer dsp_sk_abc123...
Per-user scene isolation
Each API key is associated with an ownerId. Scenes created with a key are only accessible to that key’s owner. Attempting to access another user’s scene returns 404.
Email verification
Write endpoints return 403 Forbidden with { "error": "email_not_verified", "verifyUrl": "/verify-email" } if the account has not verified its email. Send the user to https://zindex.ai/verify-email to clear it. Read endpoints and the public stateless endpoints are never gated.
Creating API keys
Use the key management endpoint to create additional keys:
curl -s https://api.zindex.ai/v1/auth/keys \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Production Key" }'
The response includes the new key - store it securely, as it cannot be retrieved again.
Plan limits
Each workspace is on a plan (free, builder, growth, enterprise) that caps four resources: persisted scenes, monthly diagram updates, monthly renders, and API keys. The full numbers are on the pricing page. When a cap is reached the affected write endpoint returns 403 Forbidden with a structured payload:
{
"error": "Plan limit reached on the free plan: 50 of 50 persisted scenes. Free a slot by deleting an existing scene, or upgrade your plan at https://zindex.ai/pricing.",
"code": "PLAN_LIMIT_EXCEEDED",
"limit": "scenes",
"current": 50,
"maximumAllowed": 50,
"plan": "free",
"upgradeUrl": "https://zindex.ai/pricing"
}
The error string is self-contained - it includes the plan slug, the count, and the upgrade URL inline so an agent or UI can display it verbatim. The structured fields (code, limit, current, maximumAllowed, plan, upgradeUrl) support programmatic branching: switch on code === "PLAN_LIMIT_EXCEEDED", read limit to know which cap was hit, render an “upgrade” CTA pointing at upgradeUrl.
Which endpoints enforce which cap:
| Cap | Endpoint | Resets |
|---|---|---|
scenes | POST /v1/scenes | When you delete an existing scene |
updatesPerMonth | POST /v1/scenes/{id}/applyOps | Start of the next calendar month |
rendersPerMonth | POST /v1/scenes/{id}/render | Start of the next calendar month |
apiKeys | POST /v1/auth/keys | When you revoke an existing key |
Read endpoints, the public stateless endpoints (POST /v1/scenes/validate, /render, /normalize), and enterprise-plan workspaces are never gated. Do not retry on a PLAN_LIMIT_EXCEEDED response - the cap is sticky until capacity is freed or the workspace upgrades. The dashboard surfaces current usage against caps so users can see where they stand before hitting one.
Rate limiting
All endpoints are rate-limited to prevent abuse. Authenticated and public endpoints have separate tiers:
| Tier | Requests/min | Burst | Identifier |
|---|---|---|---|
| Authenticated | 60 | 10 | API key |
| Public | 30 | 10 | IP address |
When exceeded, the API returns 429 Too Many Requests with a Retry-After header.
Response headers on every request:
X-RateLimit-Remaining- requests remaining in the current windowX-RateLimit-Reset- seconds until the limit resets