REST API reference.
Generate images, manage your brand vault, and stream events to your own systems. INR workspaces and USD workspaces share the same API surface; the response shape doesn't change with currency.
Base URL: https://13-204-237-151.nip.io · Version: v0 (preview) · live status
Authentication
Every request authenticates with a Bearer token. Mint one at /settings/api-keys and store the secret in your environment — it's shown once.
Format: dr_live_<64-char hex> in production; dr_test_<64-char hex> elsewhere. Scopes constrain what a key can do — pick the narrowest set that meets your use case (read, generate, write, admin).
bashcurl https://13-204-237-151.nip.io/api/health \
-H "Authorization: Bearer dr_live_<your-key>"Errors + rate limits
All endpoints return a JSON envelope of the form { ok: boolean, ... }. Errors carry a short machine-readable string in error and, for validation failures, an issues array from zod.
json// 422 validation
{
"ok": false,
"error": "Validation failed",
"issues": [{ "path": ["uploadKey"], "message": "Required" }]
}
// 402 insufficient credits
{ "ok": false, "error": "Insufficient credits", "required": 5 }
// 429 rate-limited
{ "ok": false, "error": "Too many requests" } // Retry-After headerRate limits are per-user per-namespace: 30 generate/min, 5 key-creates/5min, 10 webhook-creates/5min. Above the limit you get HTTP 429 with a Retry-After header in seconds.
Generate
/api/generateStart one generation. Returns a generation id immediately (HTTP 202); the pipeline runs async. Poll /api/generations/[id] or subscribe to generation.shipped for completion.
bashcurl https://13-204-237-151.nip.io/api/generate \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"uploadKey": "uploads/abc.png",
"uploadMime": "image/png",
"modeSlug": "studio_default",
"brandId": "b1c4...",
"prompt": "On a textured ceramic plate at golden hour",
"creativity": "balanced"
}'
# → 202
{ "ok": true, "generationId": "g-abc..." }Batch generate
/api/generate/batchFire up to 5 generations in parallel from distinct source photos. Mode + brand are shared across the batch; only the upload key differs per row. Credit is debited atomically for the full batch — if your workspace doesn't have enough, no work starts.
bashcurl https://13-204-237-151.nip.io/api/generate/batch \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"uploadKeys": ["uploads/a.png", "uploads/b.png", "uploads/c.png"],
"uploadMime": "image/png",
"modeSlug": "studio_default",
"brandId": "b1c4..."
}'
# → 202
{ "ok": true, "generationIds": ["g-1...", "g-2...", "g-3..."] }Upload
/api/uploadMulti-part form upload for source product photos. Returns a storage key you pass to /api/generate. PNG / JPEG / WebP / AVIF up to 25 MB.
bashcurl https://13-204-237-151.nip.io/api/upload \
-H "Authorization: Bearer dr_live_<your-key>" \
-F "file=@./product.jpg;type=image/jpeg"
# → 200
{ "ok": true, "key": "uploads/...", "url": "https://...", "mime": "image/jpeg" }Get a generation
/api/generations/[id]Returns the generation row, its outputs, and the verification verdict. Useful for polling when you can't subscribe to webhooks.
bashcurl https://13-204-237-151.nip.io/api/generations/g-abc... \
-H "Authorization: Bearer dr_live_<your-key>"
# → 200
{
"ok": true,
"gen": { "id": "g-abc...", "status": "shipped", "modeSlug": "studio_default", ... },
"outputs": [{ "id": "o-...", "url": "https://...", "fidelityScore": 87, "verdict": "ship" }],
"verification": { "totalScore": 87, "deltaE": 2.4, ... }
}Brands
/api/brandsList brands in the caller's workspace.
/api/brandsCreate a new brand vault. Locks seed at premium defaults.
bashcurl https://13-204-237-151.nip.io/api/brands \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{ "name": "Shabari Naturals", "category": "Ayurvedic skincare" }'/api/brands/[id]Brand detail including completeness score, locks, recent mistakes.
Mistakes (AI Brain)
/api/brands/[id]/mistakesLog a visual flaw for the brand. The AI Brain folds new mistakes into the BRAND ANCHOR block of every subsequent generation — so the model actually learns. Click coordinates (optional) get woven into the description so the model knows where on the frame to avoid the issue.
bashcurl https://13-204-237-151.nip.io/api/brands/b1c4.../mistakes \
-H "Authorization: Bearer dr_live_<your-key>" \
-F "description=Logo color drifted too dark" \
-F "kind=logo_color" \
-F "x=42" \
-F "y=71"/api/brands/[id]/mistakesList the 20 most recent mistakes for a brand.
API key management
Requires admin scope.
/api/keysList the workspace's active + revoked keys (never returns the secret).
/api/keysMint a new key. The full secret is returned once in the response — copy it on the spot.
bashcurl https://13-204-237-151.nip.io/api/keys \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{ "name": "ci-pipeline", "scopes": ["read", "generate"] }'
# → 201
{ "ok": true, "key": { "id": "...", "secret": "dr_live_..." } }/api/keys/[id]Soft-revoke a key. Any service using it starts failing auth immediately.
Webhook subscriptions
Requires admin scope.
/api/webhooksList subscriptions with last-delivery + failure-count.
/api/webhooksCreate a subscription. HTTPS endpoints only (in production). Secret returned once.
bashcurl https://13-204-237-151.nip.io/api/webhooks \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-domain.example/drishti-webhook",
"events": ["generation.shipped", "generation.refunded"]
}'/api/webhooks/[id]Toggle active, change events, or rotateSecret: true to mint a new signing secret.
/api/webhooks/[id]Delete a subscription. Events stop delivering immediately.
/api/webhooks/[id]/testFire a canned synthetic event at the subscription URL using the same signing + retry path as a real event. Useful for verifying a new endpoint before relying on it. Defaults to generation.shipped; pass { "event": "..." } to override.
bashcurl https://13-204-237-151.nip.io/api/webhooks/<id>/test \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{"event":"generation.shipped"}'Payload carries __test: true so receiver code can branch.
/api/webhooks/[id]/deliveriesReturns the last N attempts (max 100) plus a 7-day per-event success-rate breakdown. Powers the “Recent deliveries” panel at /settings/webhooks.
Webhook payloads + signing
Drishti POSTs JSON to your endpoint with three headers you care about:
X-Drishti-Event— the event name (e.g.generation.shipped).X-Drishti-Event-Id— stable id; use for idempotent dedup.X-Drishti-Signature—t=<unix>,v1=<hex>. Verify with HMAC SHA-256 of`${t}.${rawBody}`.
Verifying a signature (Node)
tsimport { createHmac, timingSafeEqual } from "node:crypto";
export function verify(rawBody: string, header: string, secret: string): boolean {
const m = /^t=(\d+),v1=([a-f0-9]+)$/.test(header.trim())
? header.trim().match(/^t=(\d+),v1=([a-f0-9]+)$/)
: null;
if (!m) return false;
const ts = Number(m[1]), sig = m[2];
// Reject anything older than 5 minutes to defeat replay attacks.
if (Math.abs(Date.now() / 1000 - ts) > 300) return false;
const expected = createHmac("sha256", secret)
.update(`${ts}.${rawBody}`)
.digest("hex");
return timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
}Event payloads
json// generation.shipped (warn=true when verdict='warn-ship')
{
"event": "generation.shipped",
"workspaceId": "ws-...",
"payload": {
"generationId": "g-...",
"modeSlug": "studio_default",
"outputs": [{ "outputId": "o-...", "verdict": "ship", "fidelityScore": 87 }],
"verdict": "ship",
"warn": false
},
"timestamp": "2026-05-20T08:14:00Z"
}
// generation.refunded
{
"event": "generation.refunded",
"payload": {
"generationId": "g-...",
"modeSlug": "studio_default",
"rejectedOutputs": [{ "outputId": "o-...", "verdict": "refund", "fidelityScore": 62 }]
}
}
// generation.failed (permanent error)
{
"event": "generation.failed",
"payload": {
"generationId": "g-...",
"modeSlug": "studio_default",
"failureReason": "Gemini blocked: safety policy",
"transient": false
}
}
// brand.created
{
"event": "brand.created",
"payload": { "brandId": "b-...", "slug": "shabari-naturals-ab3", "name": "Shabari Naturals", "category": "Ayurvedic skincare" }
}
// brand.mistake.added
{
"event": "brand.mistake.added",
"payload": { "brandId": "b-...", "mistakeId": "m-...", "kind": "logo_color", "description": "Logo drifted too dark...", "hasEvidence": true }
}Delivery retries: 5 attempts at 1s, 3s, 9s, 27s, 81s. At 10+ accumulated failures we stop delivering until you reset via /settings/webhooks.
MCP — full 14-tool surface
The 14 MCP tools all sit behind a single JSON-RPC endpoint at https://13-204-237-151.nip.io/api/mcp. Six of them have dedicated REST endpoints above; the remaining eight (verify_image, brand_vault_set, estimate_cost, fix_one_thing, marketplace_export, batch_generate, recall_recipe, mistake_check) are MCP-only today.
Enumerate the catalogue
bashcurl https://13-204-237-151.nip.io/api/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# → {"jsonrpc":"2.0","id":1,"result":{"tools":[...14 entries...]}}tools/list is open — no Authorization header required. Useful for clients that introspect before pasting a key.
Call a tool (Bearer required)
bashcurl https://13-204-237-151.nip.io/api/mcp \
-H "Authorization: Bearer dr_live_<your-key>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "estimate_cost",
"arguments": { "modeSlug": "marketplace_export", "count": 5 }
}
}'
# → result.content[0].text is a JSON string with the tool's payload.Error codes
-32001Unauthorized — missing / invalid Bearer key-32002Rate-limited — 60/min/tool per workspace-32003Insufficient credits — top up at/billing#topup-32601Method not found — likely a tool name typo-32602Invalid params — fails zod shape;error.datacarries the path
SDK quick start
The official @drishti/sdk package wraps every endpoint above plus the MCP catalogue. Zero runtime deps; works on Node 18+, Bun, Deno, and browsers.
bashnpm install @drishti/sdktsimport { Drishti, McpError } from "@drishti/sdk";
const dr = new Drishti({ apiKey: process.env.DRISHTI_API_KEY! });
// REST: upload → generate → wait for verdict
const file = await fetch("./product.jpg").then((r) => r.blob());
const { key, mime } = await dr.upload(file, "image/jpeg", "product.jpg");
const { generationId } = await dr.generate({
uploadKey: key,
uploadMime: mime,
modeSlug: "studio_default",
});
const done = await dr.waitForGeneration(generationId);
// MCP: any of the 14 tools via the JSON-RPC passthrough
try {
const est = await dr.mcpCall("estimate_cost", {
modeSlug: "marketplace_export",
count: 5,
});
console.log(est.content[0].text);
} catch (e) {
if (e instanceof McpError && e.code === -32003) {
console.error("Out of credits");
}
}Full method reference + lifecycle examples in the npm package README. The SDK ships separate @drishti/sdk/webhooks subpath for the signature verifier so verify-only edge functions get a smaller bundle.