{"openapi":"3.1.0","info":{"title":"Tillpad","description":"Bounded storage and search for agent jobs. Authenticate with Bearer API key (tp_…) or session JWT.","version":"0.1.0"},"servers":[{"url":"https://tillpad.cnrcode.com"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key (tp_…) or session JWT"}}},"security":[{"bearerAuth":[]}],"paths":{"/health":{"get":{"security":[],"summary":"Health check","responses":{"200":{"description":"OK"}}}},"/api/config":{"get":{"security":[],"summary":"Public client config","responses":{"200":{"description":"Config JSON"}}}},"/api/auth/request-otp":{"post":{"security":[],"summary":"Request email OTP","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}},"required":["email"]}}}},"responses":{"200":{"description":"OTP emailed (local returns devCode)"},"500":{"description":"Email send failed / AUTH_FROM_EMAIL unset"}}}},"/api/auth/verify-otp":{"post":{"security":[],"summary":"Verify OTP and issue session","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"code":{"type":"string"}},"required":["email","code"]}}}},"responses":{"200":{"description":"Token + account"}}}},"/api/auth/logout":{"post":{"security":[],"summary":"Clear session cookie","responses":{"200":{"description":"Logged out"}}}},"/api/agents/bootstrap":{"post":{"security":[],"summary":"Zero-human agent onboarding: create bootstrap JWT from email (no outbound mail)","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string","format":"email"},"label":{"type":"string"}},"required":["email"]}}}},"responses":{"200":{"description":"{ ok, bootstrapToken, accountId, next }"},"400":{"description":"invalid_email"},"429":{"description":"rate_limited"}}}},"/api/me":{"get":{"summary":"Current account + usage","responses":{"200":{"description":"Account"}}}},"/api/usage":{"get":{"summary":"Usage snapshot + budget view","responses":{"200":{"description":"Usage"}}}},"/api/budget":{"get":{"summary":"Budget view (remaining quotas + checkout URL)","responses":{"200":{"description":"Budget"}}}},"/api/budget/estimate":{"post":{"summary":"Estimate whether an operation would 402/429","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["kvp_ops","storage_bytes","rag_index","rag_query"]},"amount":{"type":"number"},"textLength":{"type":"number"},"byteLength":{"type":"number"}},"required":["kind"]}}}},"responses":{"200":{"description":"Estimate"}}}},"/api/keys":{"get":{"summary":"List API keys (session or account key)","responses":{"200":{"description":"Keys"}}},"post":{"summary":"Create API key (account|run|sub) with optional ACL","description":"Session JWT or full-access account key required. Custom fields: name, kind, namespaces, tools, ttlSeconds, opBudget, wipeOnExpire. Omit namespaces on run keys to auto-assign a private folder. Omit tools/namespaces on sub keys for unrestricted access on that dimension.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","description":"Optional display label"},"kind":{"type":"string","enum":["account","run","sub"],"description":"account=full access; run=temporary session; sub=limited"},"ttlSeconds":{"type":"number","description":"Lifetime in seconds; run defaults to 24h if omitted"},"namespaces":{"type":"array","items":{"type":"string"},"description":"Folder allow-list; run with empty list gets an auto folder"},"tools":{"type":"array","items":{"type":"string"},"description":"Tool allow-list (MCP/REST tool names); omit for all tools"},"opBudget":{"type":"number","description":"Max metered ops for this key; omit for unlimited"},"wipeOnExpire":{"type":"boolean","description":"When true, clear namespaces and issue a wipe receipt on expiry (default true for run)"}}}}}},"responses":{"200":{"description":"Key + secret once"}}}},"/api/keys/{id}":{"delete":{"summary":"Revoke API key (session or account key)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Revoked"}}}},"/api/runs/{keyId}/finish":{"post":{"summary":"Wipe run-key namespaces and return signed wipe receipt","parameters":[{"name":"keyId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Receipt"}}}},"/api/wipe-receipts/{id}":{"get":{"summary":"Fetch wipe receipt and verify signature","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Receipt"}}}},"/api/billing/checkout":{"post":{"summary":"Create Stripe Checkout URL (human recurring subscription)","responses":{"200":{"description":"{ url }"}}}},"/api/billing/machine-pay":{"post":{"summary":"Unlock Pro or purchase agent SKUs via Stripe MPP. Accepts session JWT, tp_ key, or bootstrap JWT. Optional sku (default pro_prepaid_30d). Returns 402 Payment challenge without credential; prepaid period + account tp_ secret on bootstrap settle.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"sku":{"type":"string","enum":["pro_prepaid_30d","pro_prepaid_90d","topup_kvp_10k","topup_storage_1gb"]}}}}}},"responses":{"200":{"description":"{ ok, planStatus, planPeriodEnd, secret? (bootstrap first settle) }"},"402":{"description":"MPP Payment challenge"},"503":{"description":"MPP not configured"}}}},"/api/billing/portal":{"post":{"summary":"Create Stripe Customer Portal URL","responses":{"200":{"description":"{ url }"}}}},"/api/billing/webhook":{"post":{"security":[],"summary":"Stripe webhook (signature required)","responses":{"200":{"description":"Received"}}}},"/api/kvp/{namespace}":{"get":{"summary":"List KVP keys in namespace","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Keys page"}}}},"/api/kvp/{namespace}/{key}":{"put":{"summary":"Put KVP value","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Stored"}}},"get":{"summary":"Get KVP value","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Value"}}},"delete":{"summary":"Delete KVP key","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}},{"name":"key","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"}}}},"/api/files/types":{"get":{"summary":"Supported RAG upload file types","description":"Public capability catalog: extensions, MIME types, and extract notes for text/PDF/Excel.","responses":{"200":{"description":"Supported types catalog"}}}},"/api/files/{namespace}":{"post":{"summary":"Upload file for RAG indexing","description":"Accepts text, PDF, and Excel (.xls/.xlsx). Other binaries (images, Word, PPT, archives) return 415. PDF/Excel are text-extracted then embedded.","parameters":[{"name":"namespace","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary"}}}}}},"responses":{"201":{"description":"File row"},"415":{"description":"Unsupported media type"}}}},"/api/files":{"get":{"summary":"List files","parameters":[{"name":"namespace","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Files"}}}},"/api/files/{id}":{"get":{"summary":"Get file metadata","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"File"}}},"delete":{"summary":"Delete file + vectors","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted"}}}},"/api/rag/search":{"post":{"summary":"Semantic RAG search","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string"},"namespace":{"type":"string"},"topK":{"type":"integer"}},"required":["query"]}}}},"responses":{"200":{"description":"Hits"}}}},"/api/inspect/namespaces":{"get":{"summary":"Namespace inventory","responses":{"200":{"description":"Namespaces"}}}},"/api/namespaces/{name}":{"delete":{"summary":"Wipe a folder (KVP, files, vectors) — session or account key","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted counts"},"403":{"description":"Not allowed"},"404":{"description":"Unknown folder"}}}},"/api/inspect/reconcile-storage":{"post":{"summary":"Reconcile storage meter","responses":{"200":{"description":"Storage bytes + usage"}}}},"/api/support/contact":{"post":{"summary":"Contact Tillpad support (Pro)","description":"Sends a support message to the Tillpad team on behalf of an active Pro account. Reply-To is the account email. Limited to a small number of messages per account per UTC day. Does not consume metered quotas.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["subject","message"],"properties":{"subject":{"type":"string","maxLength":200,"description":"Short subject line"},"message":{"type":"string","maxLength":10000,"description":"Support message body"}}}}}},"responses":{"200":{"description":"Message accepted"},"400":{"description":"Validation error"},"402":{"description":"Active Pro subscription required"},"429":{"description":"Daily support contact limit reached"}}}}}}