API Reference
Base URL: https://inbox.dog
All endpoints accept and return JSON. CORS on /api/* and /oauth/* is restricted to origins in the ALLOWED_ORIGINS env var (open if unset). MCP and /.well-known routes allow all origins. Machine-readable spec: openapi.json
Authentication
Authenticated requests use your client_id and client_secret, obtained from POST /api/keys.
| Credential | Format | Usage |
|---|---|---|
| client_id | id_... | Query param or request body |
| client_secret | sk_... | Request body or X-Client-Secret header |
Never expose client_secret in client-side code.
Endpoints
/api/keys Create API key Create a new API key. No authentication required.
Request Body
{
"name": "my-app" // optional, defaults to "default"
} Response 200
{
"client_id": "id_abc123...",
"client_secret": "sk_xyz789...",
"name": "my-app",
"redirect_uris": []
} /api/keys/{client_id} Get key info Retrieve API key details.
Headers
| X-Client-Secret | Required. Your client secret. |
Response 200
{
"client_id": "id_abc123...",
"name": "my-app",
"created_at": 1700000000000
} /api/device/code Start device auth (CLI/agent) Get an auth URL for CLI/agent flows. Uses inbox.dog's hosted code display page as the redirect — no callback server needed. See CLI & Agent Auth guide.
Request Body
{
"client_id": "id_...",
"client_secret": "sk_...",
"scope": "email:full" // optional, defaults to "email"
} Response 200
{
"auth_url": "https://inbox.dog/oauth/authorize?client_id=id_...&redirect_uri=https%3A%2F%2Finbox.dog%2Fconnect%2Foauth&scope=email%3Afull",
"redirect_uri": "https://inbox.dog/connect/oauth",
"expires_in": 600
} /oauth/token Exchange code for tokens Exchange an authorization code for access and refresh tokens. Auth codes expire after 5 minutes.
Request Body
{
"code": "AUTH_CODE_FROM_CALLBACK",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
} Response 200
{
"access_token": "ya29.a0AfH6...",
"refresh_token": "1//0eXyz...",
"token_type": "Bearer",
"expires_in": 3600,
"email": "user@example.com"
} /oauth/token Refresh access token Use a refresh token to obtain a new access token.
Request Body
{
"grant_type": "refresh_token",
"refresh_token": "YOUR_REFRESH_TOKEN",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
} Response 200
{
"access_token": "ya29.a0AfH6...",
"token_type": "Bearer",
"expires_in": 3600
} /oauth/revoke Revoke a session token Revoke an MCP session token. Requires client credentials to prove ownership. Returns 200 regardless of whether the token existed (per RFC 7009). Accepts JSON or application/x-www-form-urlencoded.
Request Body
{
"token": "mcp_...",
"client_id": "id_...",
"client_secret": "sk_..."
} Response 200
{ "revoked": true } /health Health check Check if the service is running.
Response 200
{ "status": "ok", "service": "inbox.dog-oauth" } Scopes
Pass the scope parameter to /oauth/authorize to control Gmail permissions.
| Scope | Gmail Permission | Google Scope |
|---|---|---|
| Read-only access (default) | gmail.readonly + userinfo.email | |
| email:read | Read-only access | gmail.readonly + userinfo.email |
| email:send | Send emails only | gmail.send + userinfo.email |
| email:full | Full access (read, send, modify) | gmail.modify + userinfo.email |
Rate Limits & Quotas
| Limit | Value | Notes |
|---|---|---|
| OAuth state TTL | 10 minutes | User must complete consent within this window |
| Authorization code TTL | 5 minutes | Exchange the code promptly after callback |
| Access token lifetime | 3600 seconds (1 hour) | Set by Google. Use refresh token to renew. |
| POST /api/keys | 5/min per IP | Key creation |
| POST /oauth/register | 10/min per IP | Dynamic client registration |
| POST /oauth/token | 20/min per IP | Token exchange and refresh |
| GET /api/tokens | 20/min per IP | Token retrieval |
| POST /mcp | 60/min per IP | MCP JSON-RPC calls |