API REFERENCE

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 header

Rate 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

POST/api/generate

Start 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

POST/api/generate/batch

Fire 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

POST/api/upload

Multi-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

GET/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

GET/api/brands

List brands in the caller's workspace.

POST/api/brands

Create 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" }'
GET/api/brands/[id]

Brand detail including completeness score, locks, recent mistakes.

Mistakes (AI Brain)

POST/api/brands/[id]/mistakes

Log 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"
GET/api/brands/[id]/mistakes

List the 20 most recent mistakes for a brand.

API key management

Requires admin scope.

GET/api/keys

List the workspace's active + revoked keys (never returns the secret).

POST/api/keys

Mint 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_..." } }
DELETE/api/keys/[id]

Soft-revoke a key. Any service using it starts failing auth immediately.

Webhook subscriptions

Requires admin scope.

GET/api/webhooks

List subscriptions with last-delivery + failure-count.

POST/api/webhooks

Create 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"]
  }'
PATCH/api/webhooks/[id]

Toggle active, change events, or rotateSecret: true to mint a new signing secret.

DELETE/api/webhooks/[id]

Delete a subscription. Events stop delivering immediately.

POST/api/webhooks/[id]/test

Fire 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.

GET/api/webhooks/[id]/deliveries

Returns 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

  • -32001 Unauthorized — missing / invalid Bearer key
  • -32002 Rate-limited — 60/min/tool per workspace
  • -32003 Insufficient credits — top up at /billing#topup
  • -32601 Method not found — likely a tool name typo
  • -32602 Invalid params — fails zod shape; error.data carries 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/sdk
tsimport { 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.