{"openapi":"3.1.0","info":{"title":"Clavis","description":"Authentication, session management, and rate limit orchestration for AI agents. Auth0 for bots, not humans.","version":"0.1.0"},"paths":{"/v1/auth/register":{"post":{"tags":["auth","auth"],"summary":"Register","description":"Create a new developer account on the Hobby plan.\n\nReturns the developer profile plus the full API key — this is the only\nresponse that ever contains it; only its hash is stored. Use\nPOST /v1/auth/login to get a token.\n\nAn email that is already registered gets the same 201 as a new one, rather\nthan the 409 this used to raise. The 409 was a one-request existence oracle\n— cheaper than login, since it answered without a password and echoed the\naddress back — so the answer now goes only to the address itself, by email.","operationId":"register_v1_auth_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeveloperCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeveloperRegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/login":{"post":{"tags":["auth","auth"],"summary":"Login","description":"Authenticate and receive a JWT token.\n\nPass the token as 'Authorization: Bearer <token>' on all subsequent requests.","operationId":"login_v1_auth_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/me":{"get":{"tags":["auth","auth"],"summary":"Me","description":"Return the currently authenticated developer's profile.","operationId":"me_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeveloperResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/auth/me/billing":{"get":{"tags":["auth","auth"],"summary":"Me Billing","description":"Return the current month's billing summary: plan, usage, and limits.","operationId":"me_billing_v1_auth_me_billing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Me Billing V1 Auth Me Billing Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/auth/logout":{"post":{"tags":["auth","auth"],"summary":"Logout","description":"Invalidate the current JWT by adding its jti to the Redis blocklist.\n\nThe token is rejected on all subsequent requests even before its natural\nexpiry.  The developer must call POST /v1/auth/login to get a new token.","operationId":"logout_v1_auth_logout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Logout V1 Auth Logout Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/auth/request-reset":{"post":{"tags":["auth","auth"],"summary":"Request Password Reset","description":"Request a password reset link. Always returns 200 to prevent email enumeration.","operationId":"request_password_reset_v1_auth_request_reset_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetRequestBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Request Password Reset V1 Auth Request Reset Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/auth/reset-password":{"post":{"tags":["auth","auth"],"summary":"Reset Password","description":"Reset password using a valid token from the reset email.","operationId":"reset_password_v1_auth_reset_password_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetPasswordBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Reset Password V1 Auth Reset Password Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/oauth/authorize-info":{"get":{"tags":["oauth"],"summary":"Authorize Info","description":"Validate an authorize request and return client display info for the consent page.","operationId":"authorize_info_v1_oauth_authorize_info_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"client_id","in":"query","required":true,"schema":{"type":"string","title":"Client Id"}},{"name":"redirect_uri","in":"query","required":true,"schema":{"type":"string","title":"Redirect Uri"}},{"name":"response_type","in":"query","required":true,"schema":{"type":"string","title":"Response Type"}},{"name":"code_challenge","in":"query","required":true,"schema":{"type":"string","title":"Code Challenge"}},{"name":"code_challenge_method","in":"query","required":true,"schema":{"type":"string","title":"Code Challenge Method"}},{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Authorize Info V1 Oauth Authorize Info Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/oauth/authorize":{"post":{"tags":["oauth"],"summary":"Authorize","description":"Issue an authorization code (or deny) and return the redirect target for the consent page's JS to follow.","operationId":"authorize_v1_oauth_authorize_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthorizeDecision"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Authorize V1 Oauth Authorize Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/services":{"get":{"tags":["services","services"],"summary":"List Services","description":"List all active services registered by the current developer.","operationId":"list_services_v1_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ServiceResponse"},"type":"array","title":"Response List Services V1 Services Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["services","services"],"summary":"Create Service","description":"Register a new third-party service.\n\nOptionally include ``rate_limit_rules`` to override the connector's defaults.","operationId":"create_service_v1_services_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/services/{service_id}":{"get":{"tags":["services","services"],"summary":"Get Service","description":"Get details for a specific service (returns it even if soft-deleted).","operationId":"get_service_v1_services__service_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["services","services"],"summary":"Update Service","description":"Update service display metadata or toggle the active flag.","operationId":"update_service_v1_services__service_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["services","services"],"summary":"Delete Service","description":"Soft-delete a service: sets ``is_active = False``.\n\nThe service record and its credentials remain in the database for audit\npurposes. Use PATCH to reactivate.","operationId":"delete_service_v1_services__service_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/services/{service_id}/credentials":{"get":{"tags":["credentials","credentials"],"summary":"Get Credentials Metadata","description":"Return credential metadata for a service — NEVER returns plaintext secrets.\n\nOnly safe fields (token_type, timestamps) are included in the response.","operationId":"get_credentials_metadata_v1_services__service_id__credentials_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["credentials","credentials"],"summary":"Store Credentials","description":"Encrypt and store credentials for a service.\n\nCredentials are Fernet-encrypted before storage — plaintext secrets are\nnever written to the database.","operationId":"store_credentials_v1_services__service_id__credentials_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"tags":["credentials","credentials"],"summary":"Update Credentials","description":"Replace credential data for a service.\n\n- Re-encrypts the new payload.\n- Clears cached access/refresh tokens so a fresh token fetch is triggered.\n- Invalidates the Redis token cache for this service.","operationId":"update_credentials_v1_services__service_id__credentials_put","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CredentialResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["credentials","credentials"],"summary":"Delete Credentials","description":"Permanently remove stored credentials for a service.","operationId":"delete_credentials_v1_services__service_id__credentials_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Service Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credentials/{service_name}/check":{"get":{"tags":["credentials","credentials"],"summary":"Check Credential Status","description":"Dry-run credential check — validates credentials without external API calls.\n\nQueries the database and Redis only. Never contacts the upstream service.\n\nReturns a status object with validity, expiry, rate limit, and last-used info.","operationId":"check_credential_status_v1_credentials__service_name__check_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Check Credential Status V1 Credentials  Service Name  Check Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credentials/{service_name}/verify":{"post":{"tags":["credentials","credentials"],"summary":"Verify Credential","description":"Verify a credential against the upstream provider OAuth2 / userinfo API.","operationId":"verify_credential_v1_credentials__service_name__verify_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Verify Credential V1 Credentials  Service Name  Verify Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credentials/{service_name}/headers":{"post":{"tags":["credentials","credentials"],"summary":"Get Signed Headers","description":"Generate per-request signed headers for kalshi_rsa and coinbase_jwt services.\n\nBody: {\"method\": \"GET\", \"path\": \"/trade-api/v2/balance\", \"body\": \"\"}\nReturns: {\"headers\": {\"KALSHI-ACCESS-KEY\": ..., \"KALSHI-ACCESS-TIMESTAMP\": ..., \"KALSHI-ACCESS-SIGNATURE\": ...}}\nor {\"headers\": {\"Authorization\": \"Bearer <jwt>\"}} for coinbase_jwt.","operationId":"get_signed_headers_v1_credentials__service_name__headers_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeadersRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Signed Headers V1 Credentials  Service Name  Headers Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credentials/{service_name}/ws_headers":{"post":{"tags":["credentials","credentials"],"summary":"Get Ws Jwt","description":"Generate a Coinbase WebSocket JWT with kid+nonce JOSE headers.\n\nOnly supported for services using the coinbase_jwt connector.\nBody: {} (no parameters required)\nReturns: {\"jwt\": \"<signed_jwt_string>\"}","operationId":"get_ws_jwt_v1_credentials__service_name__ws_headers_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Ws Jwt V1 Credentials  Service Name  Ws Headers Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/credentials/summary":{"get":{"tags":["credentials","credentials"],"summary":"Credential Summary","description":"Return an enriched credential status list for all of the developer's services.\n\nDesigned for the dashboard UI — returns status, human-readable expiry,\nlast-used timestamp, and rate-limit headroom for each service.","operationId":"credential_summary_v1_credentials_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Response Credential Summary V1 Credentials Summary Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/tokens/{service_name}":{"get":{"tags":["tokens","tokens"],"summary":"Get Token","description":"Get a valid token for a service, auto-refreshing if needed.\n\nThe token is ready to use in your API calls — no refresh logic needed on your end.","operationId":"get_token_v1_tokens__service_name__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServiceTokenResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/tokens/{service_name}/invalidate":{"post":{"tags":["tokens","tokens"],"summary":"Invalidate Token","description":"Force invalidate the cached token for a service, triggering a fresh fetch next time.","operationId":"invalidate_token_v1_tokens__service_name__invalidate_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/proxy/{service_name}":{"post":{"tags":["proxy","proxy"],"summary":"Proxy request (path in body)","description":"Proxy a request through Clavis with automatic auth and rate limiting.\n\nBody: ``{\"method\": \"GET\", \"path\": \"/api/endpoint\", \"params\": {}, \"body\": {}}``\n\nRate limit headers are always included in the response.","operationId":"proxy_request_v1_proxy__service_name__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Proxy Request V1 Proxy  Service Name  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/proxy/{service_name}/{path}":{"post":{"tags":["proxy","proxy"],"summary":"Proxy request (path in URL)","description":"Proxy a request using the target path from the URL.\n\n``POST /v1/proxy/{service_name}/v1/models`` forwards to the service's\n``/v1/models`` endpoint using the configured auth.\n\nBody: ``{\"method\": \"GET\", \"params\": {}, \"body\": {}}`` — no ``path`` field needed.\n\nRate limit headers are always included in the response.","operationId":"proxy_request_with_path_v1_proxy__service_name___path__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}},{"name":"path","in":"path","required":true,"schema":{"type":"string","title":"Path"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProxyRequestBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Proxy Request With Path V1 Proxy  Service Name   Path  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/agents":{"get":{"tags":["agents","agents"],"summary":"List Agents","description":"List all agents registered by the current developer.","operationId":"list_agents_v1_agents_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/AgentResponse"},"type":"array","title":"Response List Agents V1 Agents Get"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["agents","agents"],"summary":"Register Agent","description":"Register a new AI agent under the current developer account.","operationId":"register_agent_v1_agents_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/agents/{agent_id}/sessions":{"get":{"tags":["agents","agents"],"summary":"Get Agent Sessions","description":"Get active sessions for a specific agent.","operationId":"get_agent_sessions_v1_agents__agent_id__sessions_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Agent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AgentSessionResponse"},"title":"Response Get Agent Sessions V1 Agents  Agent Id  Sessions Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/agents/{agent_id}/audit":{"get":{"tags":["agents","agents"],"summary":"Get Agent Audit","description":"Get the audit log for a specific agent.","operationId":"get_agent_audit_v1_agents__agent_id__audit_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"agent_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Agent Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AuditLogResponse"},"title":"Response Get Agent Audit V1 Agents  Agent Id  Audit Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/checkout":{"post":{"tags":["billing","billing"],"summary":"Start Stripe Checkout","description":"Create a Stripe Checkout session for the given plan (builder or pro).\n\nReturns ``{\"checkout_url\": \"...\", \"session_id\": \"...\"}`` — redirect the user to ``checkout_url``.","operationId":"create_checkout_v1_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Checkout V1 Billing Checkout Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/billing/webhook":{"post":{"tags":["billing","billing"],"summary":"Stripe webhook receiver","description":"Receive and process Stripe webhook events.\n\nVerifies the ``Stripe-Signature`` header. Returns ``{\"received\": true}`` on success.","operationId":"stripe_webhook_v1_billing_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Stripe Webhook V1 Billing Webhook Post"}}}}}}},"/v1/billing/subscription":{"get":{"tags":["billing","billing"],"summary":"Get subscription status","description":"Return current plan, monthly limit, live usage, trial status, and next reset time.","operationId":"get_subscription_v1_billing_subscription_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Subscription V1 Billing Subscription Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/billing/cancel":{"post":{"tags":["billing","billing"],"summary":"Cancel subscription","description":"Cancel the active subscription and downgrade to Hobby immediately.","operationId":"cancel_subscription_v1_billing_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Cancel Subscription V1 Billing Cancel Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/billing/portal":{"post":{"tags":["billing","billing"],"summary":"Create Stripe Billing Portal session","description":"Create a Stripe Billing Portal session.\n\nThe developer must have completed a checkout first (requires a Stripe customer ID).","operationId":"create_portal_v1_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create Portal V1 Billing Portal Post"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/billing/success":{"get":{"tags":["billing","billing"],"summary":"Post-checkout success landing","description":"Public landing endpoint Stripe redirects to after a successful checkout.\n\nNo auth required — Stripe appends ``?session_id=...`` automatically.","operationId":"checkout_success_v1_billing_success_get","parameters":[{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Checkout Success V1 Billing Success Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["billing","billing"],"summary":"Post-checkout success landing","description":"Public landing endpoint Stripe redirects to after a successful checkout.\n\nNo auth required — Stripe appends ``?session_id=...`` automatically.","operationId":"checkout_success_v1_billing_success_post","parameters":[{"name":"session_id","in":"query","required":false,"schema":{"type":"string","default":"","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Checkout Success V1 Billing Success Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/billing/cancel_return":{"get":{"tags":["billing","billing"],"summary":"Post-checkout cancel landing","description":"Public landing endpoint Stripe redirects to when checkout is cancelled.","operationId":"checkout_cancel_v1_billing_cancel_return_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Checkout Cancel V1 Billing Cancel Return Get"}}}}}}},"/v1/call/{service_name}":{"post":{"tags":["proxy"],"summary":"Call with server-side credential injection","description":"Make an API call using stored credentials without exposing them to the agent.\n\nCredentials are fetched from the Clavis vault, injected server-side into the\nupstream request, and only the API response is returned to the caller.\nThe agent never sees raw credential values, eliminating prompt-injection\nexfiltration as an attack vector.\n\nThe ``url`` field must target the connector's configured base domain —\ncross-domain calls are rejected (SSRF prevention).\n\nReturns ``{\"status_code\": int, \"headers\": dict, \"body\": dict | str}``.","operationId":"call_service_v1_call__service_name__post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"service_name","in":"path","required":true,"schema":{"type":"string","title":"Service Name"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CallRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Call Service V1 Call  Service Name  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/oauth/token":{"post":{"summary":"Token","description":"Exchange an authorization code for a Clavis JWT. Public endpoint — PKCE is the credential.","operationId":"token_oauth_token_post","requestBody":{"content":{"application/x-www-form-urlencoded":{"schema":{"$ref":"#/components/schemas/Body_token_oauth_token_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Token Oauth Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/.well-known/oauth-authorization-server":{"get":{"summary":"Oauth Metadata","description":"RFC 8414 authorization server metadata document.","operationId":"oauth_metadata__well_known_oauth_authorization_server_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Oauth Metadata  Well Known Oauth Authorization Server Get"}}}}}}},"/.well-known/oauth-protected-resource":{"get":{"tags":["oauth"],"summary":"Oauth Protected Resource Metadata","description":"RFC 9728 OAuth 2.0 Protected Resource Metadata for the /mcp endpoint.\n\nSeparate from yesterday's /.well-known/oauth-authorization-server (which\ndescribes the authorization server itself); this document identifies\n/mcp as a protected resource and points clients at that authorization\nserver.","operationId":"oauth_protected_resource_metadata__well_known_oauth_protected_resource_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Oauth Protected Resource Metadata  Well Known Oauth Protected Resource Get"}}}}}}},"/health":{"get":{"tags":["health"],"summary":"Health","description":"Liveness check. Proves the process is up and serving — nothing more.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Head"}}}}}},"head":{"tags":["health"],"summary":"Health","description":"Liveness check. Proves the process is up and serving — nothing more.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Health Health Head"}}}}}}},"/health/ready":{"get":{"tags":["health"],"summary":"Readiness","description":"Readiness check: 200 only if both Redis and Postgres answered.","operationId":"readiness_health_ready_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"tags":["health"],"summary":"Readiness","description":"Readiness check: 200 only if both Redis and Postgres answered.","operationId":"readiness_health_ready_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AgentCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"description":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Description"}},"type":"object","required":["name"],"title":"AgentCreate","description":"Request body for registering an agent."},"AgentResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"developer_id":{"type":"string","format":"uuid","title":"Developer Id"},"name":{"type":"string","title":"Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","developer_id","name","description","is_active"],"title":"AgentResponse","description":"Agent details."},"AgentSessionResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"agent_id":{"type":"string","format":"uuid","title":"Agent Id"},"service_id":{"type":"string","format":"uuid","title":"Service Id"},"is_active":{"type":"boolean","title":"Is Active"}},"type":"object","required":["id","agent_id","service_id","is_active"],"title":"AgentSessionResponse","description":"Agent session details."},"AuditLogResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"event_type":{"type":"string","title":"Event Type"},"event_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Event Data"},"ip_address":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ip Address"}},"type":"object","required":["id","event_type","event_data","ip_address"],"title":"AuditLogResponse","description":"Audit log entry."},"AuthorizeDecision":{"properties":{"client_id":{"type":"string","title":"Client Id"},"redirect_uri":{"type":"string","title":"Redirect Uri"},"response_type":{"type":"string","title":"Response Type"},"code_challenge":{"type":"string","title":"Code Challenge"},"code_challenge_method":{"type":"string","title":"Code Challenge Method"},"state":{"type":"string","title":"State"},"scope":{"type":"string","title":"Scope","default":""},"allow":{"type":"boolean","title":"Allow"}},"type":"object","required":["client_id","redirect_uri","response_type","code_challenge","code_challenge_method","state","allow"],"title":"AuthorizeDecision"},"Body_token_oauth_token_post":{"properties":{"grant_type":{"type":"string","title":"Grant Type"},"code":{"type":"string","title":"Code"},"redirect_uri":{"type":"string","title":"Redirect Uri"},"client_id":{"type":"string","title":"Client Id"},"code_verifier":{"type":"string","title":"Code Verifier"}},"type":"object","required":["grant_type","code","redirect_uri","client_id","code_verifier"],"title":"Body_token_oauth_token_post"},"CallRequest":{"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"title":"Method","default":"GET"},"url":{"type":"string","title":"Url"},"headers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Headers"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params"},"json":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Json"},"data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"string"},{"type":"null"}],"title":"Data"}},"type":"object","required":["url"],"title":"CallRequest","description":"Request body for the credential-injecting call endpoint.\n\nCredentials are fetched server-side and injected into the upstream request.\nThe agent only ever receives the API response — raw credentials are never\nreturned."},"CheckoutRequest":{"properties":{"plan":{"type":"string","title":"Plan"}},"type":"object","required":["plan"],"title":"CheckoutRequest"},"CredentialCreate":{"properties":{"token_type":{"type":"string","enum":["api_key","oauth2","jwt"],"title":"Token Type"},"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["token_type","data"],"title":"CredentialCreate","description":"Request body for storing credentials.\n\nAll ``data`` fields are Fernet-encrypted before writing to the database.\nWe never store plaintext secrets.\n\nRequired fields per ``token_type``:\n- ``api_key``  → ``data.api_key``\n- ``oauth2``   → ``data.client_id``, ``data.client_secret``\n- ``jwt``      → ``data.private_key`` **or** ``data.api_key``"},"CredentialResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"service_id":{"type":"string","format":"uuid","title":"Service Id"},"token_type":{"type":"string","title":"Token Type"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","service_id","token_type","expires_at","last_used_at","created_at","updated_at"],"title":"CredentialResponse","description":"Credential metadata returned to the developer.\n\nNEVER includes plaintext secrets, encrypted blobs, or token values.\nOnly safe metadata is exposed."},"CredentialUpdate":{"properties":{"data":{"additionalProperties":true,"type":"object","title":"Data"}},"type":"object","required":["data"],"title":"CredentialUpdate","description":"Request body for updating credentials.\n\nReplaces the entire encrypted payload and clears any cached tokens.\nThe ``token_type`` is preserved from the original credential."},"DeveloperCreate":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"DeveloperCreate","description":"Request body for developer registration."},"DeveloperRegisterResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"api_key_prefix":{"type":"string","title":"Api Key Prefix"},"plan":{"type":"string","title":"Plan"},"api_key":{"type":"string","title":"Api Key"}},"type":"object","required":["id","email","is_active","created_at","api_key_prefix","plan","api_key"],"title":"DeveloperRegisterResponse","description":"Registration response — the only place the full API key is ever returned."},"DeveloperResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"email":{"type":"string","title":"Email"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"api_key_prefix":{"type":"string","title":"Api Key Prefix"},"plan":{"type":"string","title":"Plan"}},"type":"object","required":["id","email","is_active","created_at","api_key_prefix","plan"],"title":"DeveloperResponse","description":"Public developer profile.\n\nCarries only the API key *prefix* — the full key is stored hashed and is\nshown exactly once, in the response to POST /v1/auth/register."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HeadersRequest":{"properties":{"method":{"type":"string","title":"Method","default":"GET"},"path":{"type":"string","title":"Path"},"body":{"type":"string","title":"Body","default":""}},"type":"object","required":["path"],"title":"HeadersRequest"},"LoginRequest":{"properties":{"email":{"type":"string","format":"email","title":"Email"},"password":{"type":"string","title":"Password"}},"type":"object","required":["email","password"],"title":"LoginRequest","description":"Request body for developer login."},"ProxyRequest":{"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"title":"Method","default":"GET"},"path":{"type":"string","title":"Path"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params"},"body":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body"},"headers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Headers"}},"type":"object","required":["path"],"title":"ProxyRequest","description":"Request body for the proxy endpoint."},"ProxyRequestBody":{"properties":{"method":{"type":"string","enum":["GET","POST","PUT","PATCH","DELETE","HEAD","OPTIONS"],"title":"Method","default":"GET"},"params":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Params"},"body":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Body"},"headers":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Headers"}},"type":"object","title":"ProxyRequestBody","description":"Request body for the path-based proxy endpoint.\n\nThe target path is taken from the URL (``/v1/proxy/{service_name}/{path:path}``),\nso this body only contains method, params, headers, and body."},"RateLimitRuleCreate":{"properties":{"requests":{"type":"integer","exclusiveMinimum":0.0,"title":"Requests","description":"Max requests allowed in the window."},"window_seconds":{"type":"integer","exclusiveMinimum":0.0,"title":"Window Seconds","description":"Window duration in seconds."},"endpoint_pattern":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Endpoint Pattern","description":"Regex pattern to scope this rule to specific endpoints. None = global."}},"type":"object","required":["requests","window_seconds"],"title":"RateLimitRuleCreate","description":"A rate limit rule to persist alongside a service."},"RateLimitRuleResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"requests":{"type":"integer","title":"Requests"},"window_seconds":{"type":"integer","title":"Window Seconds"},"endpoint_pattern":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Endpoint Pattern"}},"type":"object","required":["id","requests","window_seconds","endpoint_pattern"],"title":"RateLimitRuleResponse","description":"Rate limit rule returned in service responses."},"ResetPasswordBody":{"properties":{"token":{"type":"string","title":"Token"},"new_password":{"type":"string","title":"New Password"}},"type":"object","required":["token","new_password"],"title":"ResetPasswordBody"},"ResetRequestBody":{"properties":{"email":{"type":"string","format":"email","title":"Email"}},"type":"object","required":["email"],"title":"ResetRequestBody"},"ServiceCreate":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"connector_name":{"type":"string","enum":["kalshi","coinbase","openai","anthropic","github","stripe","brave-search","alpaca","api_key","kalshi_rsa","coinbase_jwt"],"title":"Connector Name"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"rate_limit_rules":{"anyOf":[{"items":{"$ref":"#/components/schemas/RateLimitRuleCreate"},"type":"array"},{"type":"null"}],"title":"Rate Limit Rules"}},"type":"object","required":["name","connector_name"],"title":"ServiceCreate","description":"Request body for registering a new service."},"ServiceResponse":{"properties":{"id":{"type":"string","format":"uuid","title":"Id"},"developer_id":{"type":"string","format":"uuid","title":"Developer Id"},"name":{"type":"string","title":"Name"},"connector_name":{"type":"string","title":"Connector Name"},"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"type":"boolean","title":"Is Active"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"rate_limit_rules":{"items":{"$ref":"#/components/schemas/RateLimitRuleResponse"},"type":"array","title":"Rate Limit Rules","default":[]},"auth_type":{"type":"string","title":"Auth Type","description":"Auth type derived from the service connector (e.g. 'api_key', 'oauth2').","readOnly":true}},"type":"object","required":["id","developer_id","name","connector_name","display_name","description","is_active","created_at","updated_at","auth_type"],"title":"ServiceResponse","description":"Service details returned to the developer.\n\n``auth_type`` is derived at response time from the connector registry\nso it always reflects the connector's current configuration."},"ServiceTokenResponse":{"properties":{"service_name":{"type":"string","title":"Service Name"},"token_type":{"type":"string","title":"Token Type"},"access_token":{"type":"string","title":"Access Token"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"rate_limit_remaining":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Rate Limit Remaining"}},"type":"object","required":["service_name","token_type","access_token"],"title":"ServiceTokenResponse","description":"A valid token for calling a third-party service."},"ServiceUpdate":{"properties":{"display_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Display Name"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"is_active":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Active"}},"type":"object","title":"ServiceUpdate","description":"Request body for updating service metadata."},"TokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token"},"token_type":{"type":"string","title":"Token Type","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In"}},"type":"object","required":["access_token","expires_in"],"title":"TokenResponse","description":"JWT token returned on successful login."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}