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.
- JSON-RPC 2.0
- Bearer auth
- 60 / min / tool / workspace
- v1 · ap-south-1
POST https://13-204-237-151.nip.io/api/mcpAuthorization: Bearer dr_live_…- 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
From zero to a verified generation in three steps.
- 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.
- 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.
- 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.
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.
dr_live_*- Production. Real credits. Audit-logged.
dr_test_*- Sandbox. Separate balance. Same engine pool.
SHA-256 hex. Plaintext is never persisted; the dashboard shows the prefix + last-four only.
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": {…} }
]
}
}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.
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.).Scoperead:gensCredits0+
{
"activeOnly": false
}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}}
}'{
"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.Scoperead:gensCredits0+
{}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":{}}
}'{
"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.Scoperead:gensCredits0+
{
"limit": 20,
"status": "shipped" | "warned" | "refunded" | "failed" | "queued" | "running" | "verifying" | "retrying",
"brandId": "uuid",
"cursor": "ISO timestamp from a previous page's nextCursor"
}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"}}
}'{
"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.Scoperead:vaultCredits0+
{
"brandId": "uuid" // required
}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"}
}
}'{
"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.Scopewrite:vaultCredits0+
{
"base64": "iVBORw0KGgoAAAANSU…", // max ~30 MB decoded
"mimeType": "image/png",
"role": "logo" | "product" | "lifestyle" | "mistake" | "color" | "typography" | "composition",
"brandId": "uuid"
}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\"
}
}
}"{
"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.ScopegenerateCredits1+ (mode-dependent)+
{
"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
}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"
}
}
}'{
"jsonrpc": "2.0",
"id": 6,
"result": {
"content": [{
"type": "text",
"text": "{\"generationId\":\"f4a2e1b8-9c2d-4f5e-a3b1-8c2d4f5ea3b1\",\"status\":\"queued\"}"
}]
}
}JSON-RPC codes you'll see in the wild.
| Code | Meaning | Recovery |
|---|---|---|
| -32700 | Parse error | Body was not valid JSON. Validate before sending. |
| -32600 | Invalid Request | Missing jsonrpc:"2.0" or method. Send the canonical envelope. |
| -32601 | Method not found / Unknown tool | Method is not tools/call, or tool name not in the registry. Hit tools/list. |
| -32602 | Invalid params | Arguments failed the tool's zod schema, or brand/upload not found. Check the data field for the issue path. |
| -32603 | Internal error | Server-side fault (DB, storage, pipeline). Safe to retry with backoff; idempotent for read tools. |
| -32001 | Unauthorized | Missing or revoked Bearer token. Rotate the key from the workspace dashboard. |
| -32002 | Rate limited | 60 calls/min/(tool, workspace) ceiling. data.retryAt holds an epoch ms wall-clock. |
| -32003 | Insufficient credits | Workspace 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.
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.
- 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.
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.