{
  "openapi": "3.1.0",
  "info": {
    "title": "Gutter Tokens API",
    "version": "1.0.0",
    "summary": "Prepaid access to frontier models, OpenAI- and Anthropic-compatible.",
    "description": "One endpoint that speaks both the OpenAI Chat Completions protocol and the\nAnthropic Messages protocol. Credits are prepaid; there is no subscription.\n\n**Authentication.** Every request carries `Authorization: Bearer sk-…`.\nKeys are created in the dashboard at <https://guttertokens.com/dashboard>\nand can be copied again from there.\n\n**Billing.** On tokens delivered. A request that fails upstream is not\ncharged. Prompt caching is billed separately: a cache write costs 25% more\nthan the input price, each subsequent read 90% less. Current prices are\npublished at <https://guttertokens.com/docs> and are generated from the\nlive billing configuration.\n\n**Limits.** 200 concurrent connections and 100 requests per second per\nsource address, burst 200. Concurrency is the binding one — a completion\ncan hold a connection for minutes. There is no ceiling on how long a single\nrequest may run; long agentic turns are ordinary traffic. Spending is\nbounded by your prepaid balance, not by a rate limit.\n\n**CORS.** Every `/v1/*` response allows any origin, errors included, so a\nbrowser client can read both the answer and the failure. `Retry-After` is\nexposed on a 429. This is a bring-your-own-key shape only: a key in a page\nis a key given to whoever loads the page.\n\n**Not implemented.** `POST /v1/responses` returns 500 and\n`POST /v1/messages/count_tokens` returns 404. On the Vercel AI SDK the\nprovider package must be `@ai-sdk/openai-compatible`; `@ai-sdk/openai`\nsends `/v1/responses` on v5 and fails on every request with an otherwise\ncorrect configuration.\n\nAgents: see <https://guttertokens.com/docs> and the acceptable-use policy\nbefore automating anything beyond the three endpoints below.\n",
    "termsOfService": "https://guttertokens.com/legal/terms",
    "contact": {
      "name": "Gutter Tokens support",
      "email": "support@guttertokens.com",
      "url": "https://guttertokens.com/support"
    }
  },
  "externalDocs": {
    "description": "Docs, live prices and the live model list",
    "url": "https://guttertokens.com/docs"
  },
  "servers": [
    {
      "url": "https://api.guttertokens.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Chat",
      "description": "OpenAI-compatible Chat Completions."
    },
    {
      "name": "Messages",
      "description": "Anthropic-compatible Messages."
    },
    {
      "name": "Models",
      "description": "What this key can reach."
    }
  ],
  "paths": {
    "/v1/chat/completions": {
      "post": {
        "tags": [
          "Chat"
        ],
        "operationId": "createChatCompletion",
        "summary": "Create a chat completion",
        "description": "The OpenAI Chat Completions protocol. Set `stream: true` for\nserver-sent events; the stream is not buffered and tokens arrive as\nthey are produced.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChatCompletionRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "One turn, no streaming",
                  "value": {
                    "model": "claude-sonnet-5",
                    "messages": [
                      {
                        "role": "user",
                        "content": "Hello"
                      }
                    ]
                  }
                },
                "streaming": {
                  "summary": "Streamed",
                  "value": {
                    "model": "claude-haiku-4-5-20251001",
                    "messages": [
                      {
                        "role": "user",
                        "content": "Count to ten"
                      }
                    ],
                    "stream": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A completion, or — when `stream` was true — a `text/event-stream`\nof `data:` frames terminated by `data: [DONE]`.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatCompletion"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-sent events, one `data:` frame per chunk."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientQuota"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ModelUnavailable"
          }
        }
      }
    },
    "/v1/messages": {
      "post": {
        "tags": [
          "Messages"
        ],
        "operationId": "createMessage",
        "summary": "Create a message",
        "description": "The Anthropic Messages protocol. `max_tokens` is required, as upstream.\nSet `stream: true` for server-sent events.\n\n`POST /v1/messages/count_tokens` is **not** served (404): no supplier\nimplements it, so there is no ground truth to return. Count locally.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessagesRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "One turn",
                  "value": {
                    "model": "claude-sonnet-5",
                    "max_tokens": 1024,
                    "messages": [
                      {
                        "role": "user",
                        "content": "Hello"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A message, or — when `stream` was true — a `text/event-stream` of\nAnthropic event frames.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "Server-sent events, Anthropic frame types."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientQuota"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ModelUnavailable"
          }
        }
      }
    },
    "/v1/models": {
      "get": {
        "tags": [
          "Models"
        ],
        "operationId": "listModels",
        "summary": "List the models this key can reach",
        "description": "Priced nowhere and charged for nowhere, which makes it the way to check\nthat a key still authenticates without spending anything. It is what\nthe dashboard's own \"Test\" button calls.\n\nThis list is the only authority on model names. They are matched\nexactly; a near-miss is a refusal, not a fallback.\n",
        "responses": {
          "200": {
            "description": "The catalogue.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ModelList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer sk-…`. Create keys in the dashboard. A key can\ncarry a spend cap; a leaked capped key is an incident rather than a\ncatastrophe, so cap the ones you hand to automation.\n"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed body or parameters. Do not retry unchanged.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "invalid_request_error",
                "code": "invalid_request",
                "message": "Malformed body or parameters."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Key missing, wrong, or revoked. Do not retry.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "authentication_error",
                "code": "invalid_api_key",
                "message": "Invalid API key. Pass it as `Authorization: Bearer sk-...`, and check the key has not been revoked."
              }
            }
          }
        }
      },
      "InsufficientQuota": {
        "description": "Out of credit. Retrying will not help — add credit and try again. Note\nthat credit which has just landed may take up to 60 seconds to become\nspendable.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "insufficient_quota",
                "code": "insufficient_quota",
                "message": "Out of credit. Add credit and retry."
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Back off and retry.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait. Exposed to browsers as well. Honour it rather than\ninventing an interval.\n",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "rate_limit_error",
                "code": "rate_limit_exceeded",
                "message": "Too many requests. Back off and retry."
              }
            }
          }
        }
      },
      "ModelUnavailable": {
        "description": "No capacity for that model right now. Retry with backoff, or fall back\nto another model.\n",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "api_error",
                "code": "model_unavailable",
                "message": "No capacity for that model right now."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "Something failed on our side. Retry once with backoff.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "type": "api_error",
                "code": "internal_error",
                "message": "Something failed on our side."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The standard shape, on every failure, so a client can branch on\n`error.type` and `error.code`. Error bodies are sanitised at the edge:\nthe code and the sentence are ours, and nothing of an upstream response\nreaches you.\n",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "message",
              "type"
            ],
            "properties": {
              "message": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "examples": [
                  "authentication_error",
                  "invalid_request_error",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "invalid_api_key",
                  "insufficient_quota",
                  "rate_limit_exceeded",
                  "model_unavailable",
                  "internal_error"
                ]
              },
              "param": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        }
      },
      "ChatCompletionRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "model",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Exactly as returned by `GET /v1/models`."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "role"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "system",
                    "user",
                    "assistant",
                    "tool"
                  ]
                },
                "content": {
                  "description": "A string, or the multi-part array form.",
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  ]
                },
                "name": {
                  "type": "string"
                },
                "tool_calls": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "additionalProperties": true
                  }
                },
                "tool_call_id": {
                  "type": "string"
                }
              }
            }
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "Server-sent events. Not buffered."
          },
          "stream_options": {
            "type": "object",
            "additionalProperties": true
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1
          },
          "max_completion_tokens": {
            "type": "integer",
            "minimum": 1
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "stop": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "n": {
            "type": "integer"
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "tool_choice": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ]
          },
          "response_format": {
            "type": "object",
            "additionalProperties": true
          },
          "seed": {
            "type": "integer"
          },
          "user": {
            "type": "string",
            "description": "An opaque identifier of your own. **Do not put personal data here**\n— see the acceptable-use policy, which prohibits personal data in\nrequests outright.\n"
          }
        }
      },
      "ChatCompletion": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "object": {
            "type": "string",
            "examples": [
              "chat.completion"
            ]
          },
          "created": {
            "type": "integer"
          },
          "model": {
            "type": "string"
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "index": {
                  "type": "integer"
                },
                "finish_reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "message": {
                  "type": "object",
                  "additionalProperties": true,
                  "properties": {
                    "role": {
                      "type": "string"
                    },
                    "content": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "tool_calls": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  }
                }
              }
            }
          },
          "usage": {
            "$ref": "#/components/schemas/OpenAiUsage"
          }
        }
      },
      "OpenAiUsage": {
        "type": "object",
        "additionalProperties": true,
        "description": "What you are billed on. Note that token accounting is reported by the\nupstream that served the request and is not always a faithful count of\nwhat was sent; treat per-request figures as indicative and reconcile at\nthe aggregate level against your balance.\n",
        "properties": {
          "prompt_tokens": {
            "type": "integer"
          },
          "completion_tokens": {
            "type": "integer"
          },
          "total_tokens": {
            "type": "integer"
          }
        }
      },
      "MessagesRequest": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "model",
          "max_tokens",
          "messages"
        ],
        "properties": {
          "model": {
            "type": "string",
            "description": "Exactly as returned by `GET /v1/models`."
          },
          "max_tokens": {
            "type": "integer",
            "minimum": 1,
            "description": "Required, as upstream."
          },
          "messages": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "role",
                "content"
              ],
              "properties": {
                "role": {
                  "type": "string",
                  "enum": [
                    "user",
                    "assistant"
                  ]
                },
                "content": {
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": true
                      }
                    }
                  ]
                }
              }
            }
          },
          "system": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            ]
          },
          "stream": {
            "type": "boolean",
            "default": false
          },
          "temperature": {
            "type": "number"
          },
          "top_p": {
            "type": "number"
          },
          "top_k": {
            "type": "integer"
          },
          "stop_sequences": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tools": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "tool_choice": {
            "type": "object",
            "additionalProperties": true
          },
          "thinking": {
            "type": "object",
            "additionalProperties": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "**Do not put personal data here.** The acceptable-use policy\nprohibits personal data in requests outright.\n"
          }
        }
      },
      "Message": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "examples": [
              "message"
            ]
          },
          "role": {
            "type": "string",
            "examples": [
              "assistant"
            ]
          },
          "model": {
            "type": "string"
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "stop_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "stop_sequence": {
            "type": [
              "string",
              "null"
            ]
          },
          "usage": {
            "$ref": "#/components/schemas/AnthropicUsage"
          }
        }
      },
      "AnthropicUsage": {
        "type": "object",
        "additionalProperties": true,
        "description": "Which usage keys are present depends on the upstream that served the\nrequest; a reconstructed response carries fewer of them and reports no\ncaching at all. Do not depend on a key being there.\n",
        "properties": {
          "input_tokens": {
            "type": "integer"
          },
          "output_tokens": {
            "type": "integer"
          },
          "cache_creation_input_tokens": {
            "type": "integer"
          },
          "cache_read_input_tokens": {
            "type": "integer"
          }
        }
      },
      "ModelList": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "examples": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true,
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "The name to send as `model`."
                },
                "object": {
                  "type": "string",
                  "examples": [
                    "model"
                  ]
                },
                "created": {
                  "type": "integer"
                },
                "owned_by": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  }
}
