Developer · REST API · v0.1

The Drishti API.

Brand-locked image generation as a REST endpoint. Fourteen tools shipped today. One JSON-RPC URL, one Bearer token, the same verification stack that grades every output 0–100 before it ships.

npm i @drishti/sdk· official TypeScript client + webhook verifier
  • JSON-RPC 2.0
  • Bearer auth
  • 60 / min / tool / workspace
  • v1 · ap-south-1
Endpoint
POST https://13-204-237-151.nip.io/api/mcp
Auth header
Authorization: Bearer dr_live_…
Fourteen tools shipped
  • list_modes
  • brand_vault_set
  • list_engines
  • estimate_cost
  • list_recent
  • fix_one_thing
  • brand_vault_get
  • marketplace_export
  • upload_reference
  • batch_generate
  • generate_image
  • recall_recipe
  • verify_image
  • mistake_check
Quick start

From zero to a verified generation in three steps.

  1. 01

    Sign up and create a workspace

    Drishti accounts are workspace-scoped. Your first workspace is created automatically on sign-up; API keys live inside a workspace and inherit its credit balance.

  2. 02

    Generate an API key

    Open Workspace → API Keys (UI lands in P7.5; until then keys are issued by support). Keys are shown once at creation and stored as SHA-256 hashes. They never appear in logs.

  3. 03

    Try a curl

    Hit POST /api/mcp with a JSON-RPC envelope. tools/list is open (no auth required) so you can introspect before you have a key.

Authentication

One header. Two environments. Never on the client.

Drishti uses static Bearer tokens scoped to a single workspace. Tokens are prefixed dr_live_ for production traffic and dr_test_ for sandbox runs against the same engines but billed against a separate test balance. The wire format is a standard HTTP Authorization header.

POST /api/mcp HTTP/1.1
Host: 13-204-237-151.nip.io
Authorization: Bearer dr_live_a1f3c8b9d2e7…
Content-Type: application/json

Rotation. Keys can be revoked instantly from the workspace dashboard. We recommend a 90-day rotation cadence and a separate key per service so a leaked CI runner does not cascade. Revocation is single-write — once revoked, all in-flight requests using that key receive -32001 Unauthorized on the next call.

Storage. Never embed a key in a browser bundle, a mobile app, or any client-side code. The Drishti API is server-to-server. If you need a user-attributed flow from a browser, the OAuth PKCE path lands in P7.5 — until then, proxy requests through your own backend.

Token formats
dr_live_*
Production. Real credits. Audit-logged.
dr_test_*
Sandbox. Separate balance. Same engine pool.
Hash on disk

SHA-256 hex. Plaintext is never persisted; the dashboard shows the prefix + last-four only.

First call

List the fourteen tools — no key required.

The tools/list method is open to anonymous callers so MCP clients can introspect before a key is configured. It returns the name, description, and JSON-schema input for every tool the server exposes.

curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'

Live endpoint is https://13-204-237-151.nip.io/api/mcp — paste exactly that into your client. Once the production domain lands we'll publish the new hostname here with a 90-day overlap window so existing integrations don't break.

A successful response is a JSON-RPC envelope with a result.tools array. Each entry carries an inputSchema you can feed straight into a form generator or a tool-calling LLM.

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      { "name": "list_modes",       "description": "List available generation modes…", "inputSchema": {…} },
      { "name": "list_engines",     "description": "Return the static catalog of Gemini engines…", "inputSchema": {…} },
      { "name": "list_recent",      "description": "Cursor-paginated list of recent generations…", "inputSchema": {…} },
      { "name": "brand_vault_get",  "description": "Return the full Brand Vault snapshot…", "inputSchema": {…} },
      { "name": "upload_reference", "description": "Upload a base64-encoded reference image…", "inputSchema": {…} },
      { "name": "generate_image",   "description": "Kick off a generation pipeline run…", "inputSchema": {…} }
    ]
  }
}
Concepts

Five nouns that show up everywhere.

Workspace

The billing + isolation unit. Every API key, every brand vault, every generation belongs to exactly one workspace. Workspace IDs are UUIDs; you never need to send one — it is resolved from your key.

Brand vault

A bundle of locks, references, recipes, and known mistakes that tells the verifier what your brand actually looks like. Pass brandId on a generate_image call to lock the run against it; omit it and the run is generic.

Mode

A studio recipe: an engine choice, a prompt template, an output count, and a credit cost. Slugs are stable strings (studio_default, marketplace_export, multi_angle, …). Call list_modes to enumerate.

Fidelity score

Every output gets a 0–100 grade from a separate verifier stack (DINOv2 + SAM-2 + PaddleOCR + ΔE-2000 + a Gemini judge). ≥75 ships, <75 is refunded automatically. The score is surfaced on every list_recent row.

Credits

Drishti bills per verified output. Generate-image calls debit the workspace's credit_balance up-front and refund automatically on any output whose verdict is refund (fidelity < 75) or whose pipeline fails.

Refunds

Automatic. You never file one. The pipeline writes credit_balance += cost back to the workspace on a failed run, an under-threshold verdict, or an explicit operator override. Audit-logged.

Endpoint reference

Six of fourteen, with input/output schemas. The other eight ship via tools/list.

Every tool is called through POST /api/mcp with a JSON-RPC envelope. The method is always tools/call; the tool name lives inside params.name and its arguments inside params.arguments.

list_modesCatalog of generation modes (Studio Default, Marketplace, Multi-Angle, etc.).
+
Input
{
  "activeOnly": false
}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d '{
    "jsonrpc":"2.0","id":1,
    "method":"tools/call",
    "params":{"name":"list_modes","arguments":{"activeOnly":true}}
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"modes\":[{\"slug\":\"studio_default\",\"name\":\"Studio Default\",\"defaultEngine\":\"gemini_2_5_flash_image\",\"creditsPerRun\":1,\"isActive\":true,\"sortOrder\":1}, …]}"
      }
    ]
  }
}
list_enginesStatic catalog of Gemini engines available to your workspace.
+
Input
{}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d '{
    "jsonrpc":"2.0","id":2,
    "method":"tools/call",
    "params":{"name":"list_engines","arguments":{}}
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"engines\":[{\"slug\":\"gemini_2_5_flash_image\",\"label\":\"Flash\",\"hint\":\"Default · fastest\",\"costMultiplier\":1.0},{\"slug\":\"gemini_3_pro_image\",\"label\":\"Pro\",\"hint\":\"Heavy detail\",\"costMultiplier\":2.5}, …]}"
    }]
  }
}
list_recentCursor-paginated list of recent generations scoped to this workspace.
+
Input
{
  "limit":   20,
  "status":  "shipped" | "warned" | "refunded" | "failed" | "queued" | "running" | "verifying" | "retrying",
  "brandId": "uuid",
  "cursor":  "ISO timestamp from a previous page's nextCursor"
}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d '{
    "jsonrpc":"2.0","id":3,
    "method":"tools/call",
    "params":{"name":"list_recent","arguments":{"limit":5,"status":"shipped"}}
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"items\":[{\"id\":\"a3e1…\",\"status\":\"shipped\",\"modeSlug\":\"studio_default\",\"cost\":1,\"fidelityScore\":94,\"verdict\":\"ship\",\"createdAt\":\"2026-05-18T11:14:02Z\"}, …],\"nextCursor\":\"2026-05-18T10:02:11Z\"}"
    }]
  }
}
brand_vault_getThe full Brand Vault snapshot: brand row + locks + refs + recipes + recent mistakes.
+
Input
{
  "brandId": "uuid"   // required
}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d '{
    "jsonrpc":"2.0","id":4,
    "method":"tools/call",
    "params":{
      "name":"brand_vault_get",
      "arguments":{"brandId":"7c8f1c3e-4b7a-4d2a-9b40-1a3a8a6f0001"}
    }
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"brand\":{\"id\":\"7c8f…\",\"name\":\"Essence Nocturne\",\"slug\":\"essence-nocturne\",\"category\":\"beauty\",\"completenessScore\":86},\"locks\":{\"color\":100,\"composition\":80,\"typography\":90,\"shape\":80},\"refs\":[…],\"recipes\":[…],\"mistakes\":[…]}"
    }]
  }
}
upload_referenceUpload a base64-encoded reference image into a brand's vault.
+
Input
{
  "base64":   "iVBORw0KGgoAAAANSU…",   // max ~30 MB decoded
  "mimeType": "image/png",
  "role":     "logo" | "product" | "lifestyle" | "mistake" | "color" | "typography" | "composition",
  "brandId":  "uuid"
}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d "{
    \"jsonrpc\":\"2.0\",\"id\":5,
    \"method\":\"tools/call\",
    \"params\":{
      \"name\":\"upload_reference\",
      \"arguments\":{
        \"base64\":\"$(base64 -w0 logo.png)\",
        \"mimeType\":\"image/png\",
        \"role\":\"logo\",
        \"brandId\":\"7c8f1c3e-4b7a-4d2a-9b40-1a3a8a6f0001\"
      }
    }
  }"
Response
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"refId\":\"3a91…\",\"s3Key\":\"uploads/ws/…/brand/…/abc.png\",\"bytes\":284711,\"role\":\"logo\",\"brandId\":\"7c8f…\"}"
    }]
  }
}
generate_imageKick off a generation. Debits credits up-front, returns a queued generationId; poll list_recent for the verdict.
+
Input
{
  "uploadKey":  "uploads/ws/…/your-product.jpg",  // returned by upload_reference or /api/upload
  "uploadMime": "image/jpeg",
  "modeSlug":   "studio_default",
  "prompt":     "On a teakwood cyclorama, soft cobalt rim light.",   // optional
  "brandId":    "uuid"                                                // optional — locks the run
}
Example call
curl -sS https://13-204-237-151.nip.io/api/mcp \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer dr_live_…' \
  -d '{
    "jsonrpc":"2.0","id":6,
    "method":"tools/call",
    "params":{
      "name":"generate_image",
      "arguments":{
        "uploadKey":"uploads/ws/…/essence-50ml.jpg",
        "uploadMime":"image/jpeg",
        "modeSlug":"studio_default",
        "brandId":"7c8f1c3e-4b7a-4d2a-9b40-1a3a8a6f0001"
      }
    }
  }'
Response
{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "content": [{
      "type": "text",
      "text": "{\"generationId\":\"f4a2e1b8-9c2d-4f5e-a3b1-8c2d4f5ea3b1\",\"status\":\"queued\"}"
    }]
  }
}
Errors

JSON-RPC codes you'll see in the wild.

CodeMeaningRecovery
-32700Parse errorBody was not valid JSON. Validate before sending.
-32600Invalid RequestMissing jsonrpc:"2.0" or method. Send the canonical envelope.
-32601Method not found / Unknown toolMethod is not tools/call, or tool name not in the registry. Hit tools/list.
-32602Invalid paramsArguments failed the tool's zod schema, or brand/upload not found. Check the data field for the issue path.
-32603Internal errorServer-side fault (DB, storage, pipeline). Safe to retry with backoff; idempotent for read tools.
-32001UnauthorizedMissing or revoked Bearer token. Rotate the key from the workspace dashboard.
-32002Rate limited60 calls/min/(tool, workspace) ceiling. data.retryAt holds an epoch ms wall-clock.
-32003Insufficient creditsWorkspace credit_balance < cost. Refill from /workspace/billing or wait for an auto-refund.

Every error response is a complete JSON-RPC envelope: { "jsonrpc":"2.0", "id":1, "error":{ "code":-32602, "message":"Invalid params", "data":{…} } }. The HTTP status is always 200 — JSON-RPC owns the failure semantics, not the transport.

Rate limits

60 calls per minute, per tool, per workspace.

Drishti enforces a sliding-window cap on every tools/call at a per-tuple key of (tool, workspace). A burst of list_modes doesn't eat the budget for generate_image, and one workspace can't starve another. Hits over the ceiling return -32002 with a wall-clock retryAt in the data field.

{
  "jsonrpc": "2.0",
  "id": 7,
  "error": {
    "code": -32002,
    "message": "Rate limited",
    "data": { "retryAt": 1747662000000 }
  }
}

Generate calls are also rate-limited at the user level (30 / min) when called from the browser session path. The API-key path runs on its own counter so a hot CI pipeline never displaces an interactive user — and the inverse.

Coming in P7.5
  • Tier-scaled ceilings. Studio +50%, House +200%, Enterprise on contract.
  • Per-key budgets. Set a daily credit cap on each key so a runaway CI job can't drain the workspace.
  • Concurrency limit. Today: 8 in-flight per workspace, then 503-equivalent JSON-RPC -32603. P7.5 lifts it to tier-scaled values.
Roadmap

What lands next, and why.

  • Eight more tools

    verify_image · brand_vault_set · brand_vault_create · mistake_register · recipe_create · recipe_run · chaos_start · webhook_subscribe. Same JSON-RPC contract, no version bump.

  • OAuth PKCE

    User-attributed access from browsers and Claude Desktop. Bearer tokens stay; OAuth is additive. Scopes line up 1:1 with the API-key permission set.

  • Webhooks dispatch

    Subscribe to generation.shipped, generation.refunded, brand.completeness.changed. HMAC-SHA-256 signed; replayable from the dashboard.

  • SDKs

    Official Node (@drishti/sdk) and Python (drishti-sdk) clients. Typed envelopes, automatic retry on -32603, built-in cursor pagination for list_recent.

Need a tool that's not here yet? Mail admin@indigenservices.in with the use case — every shipped tool in v1 started as one of those mails.