{"openapi":"3.1.0","info":{"title":"SaaufAI API","version":"1.0.0","summary":"One OpenAI-compatible endpoint for models from many AI companies.","description":"SaaufAI is an OpenAI-compatible gateway: point any OpenAI SDK at `https://api.saauf.xyz/v1` and send your SaaufAI key. Each model has a public YES/NO disclosure sheet on how your data is handled, at https://ai.saauf.xyz/models. Prompts and answers are not stored; only metadata such as time, model, token counts and status. Errors are JSON in the OpenAI shape plus a `resolution` hint.","contact":{"name":"SaaufAI support","email":"ai@saauf.xyz","url":"https://ai.saauf.xyz/support"},"termsOfService":"https://ai.saauf.xyz/policies/terms"},"externalDocs":{"description":"SaaufAI API reference","url":"https://ai.saauf.xyz/docs/api"},"servers":[{"url":"https://api.saauf.xyz","description":"SaaufAI API"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Chat","description":"Generate model answers."},{"name":"Models","description":"The models your key may use."}],"paths":{"/v1/chat/completions":{"post":{"operationId":"createChatCompletion","tags":["Chat"],"summary":"Create a chat completion","description":"Sends a conversation to a model and returns its reply, in the OpenAI Chat Completions format. Works with every API model; get model ids from listModels. With `stream: true` the answer arrives as server-sent events (`chat.completion.chunk` objects), ending with `data: [DONE]`. Requests may be checked by safeguards first; a blocked request gets a normal assistant reply saying why.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletionRequest"},"example":{"model":"glm-5.3","messages":[{"role":"user","content":"Write a haiku about tea."}]}}}},"responses":{"200":{"description":"The model's reply. A JSON object, or an event stream when `stream` is true.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChatCompletion"}},"text/event-stream":{"schema":{"type":"string","description":"Server-sent events, each `data:` line a ChatCompletionChunk, then `data: [DONE]`."}}}},"400":{"description":"Invalid request (`invalid_request_error`): the body isn't JSON, or `model` or `messages` is missing or malformed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"Missing, wrong or revoked API key (`authentication_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"402":{"description":"No credit left for a paid model (`insufficient_credit`). Add credit or use a free model.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key or account may not do this: `permission_error` (scoped to another model or provider), `key_expired` or `account_disabled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such model, or it is disabled (`model_not_found`). Call listModels for valid ids.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong HTTP method (`method_not_allowed`); see the Allow header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests (`rate_limit_exceeded`). Wait for the Retry-After seconds, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}}},"500":{"description":"Unexpected error on SaaufAI's side (`server_error`). Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The model provider failed (`upstream_error`). Retry with backoff, or try another model.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"The model is temporarily unavailable (`model_unavailable`). Retry later or try another model.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"504":{"description":"The model did not answer in time (`upstream_error`). Retry, or ask for a shorter answer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/decisions":{"post":{"operationId":"createDecision","tags":["Decisions"],"summary":"Answer typed questions","description":"Asks a decision model typed questions about a state: `noul` answers with the probability of yes, `choice` picks one option from `criteria`. Charged per input token at the model's input price; answers cost nothing. Works with decision models only.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionRequest"}}}},"responses":{"200":{"description":"The answers, one per question id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecisionResponse"}}}},"401":{"description":"Missing, wrong or revoked API key (`authentication_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key or account may not do this: `permission_error` (scoped to another model or provider), `key_expired` or `account_disabled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong HTTP method (`method_not_allowed`); see the Allow header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests (`rate_limit_exceeded`). Wait for the Retry-After seconds, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}}},"500":{"description":"Unexpected error on SaaufAI's side (`server_error`). Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/models":{"get":{"operationId":"listModels","tags":["Models"],"summary":"List models","description":"Lists the models the API key may use, in the OpenAI format. Models with thinking levels are also listed once per level (for example `glm-5.3:low`), for clients that can't send `reasoning_effort`. Details and data disclosures for each model are at https://ai.saauf.xyz/models/{id}.","responses":{"200":{"description":"The models this key may use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ModelList"}}}},"401":{"description":"Missing, wrong or revoked API key (`authentication_error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key or account may not do this: `permission_error` (scoped to another model or provider), `key_expired` or `account_disabled`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Wrong HTTP method (`method_not_allowed`); see the Allow header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests (`rate_limit_exceeded`). Wait for the Retry-After seconds, then retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}}},"500":{"description":"Unexpected error on SaaufAI's side (`server_error`). Retry with backoff.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sk-saauf-...","description":"A SaaufAI API key, sent as `Authorization: Bearer sk-saauf-...`. Create one at https://ai.saauf.xyz/account/keys (shown once). Keys can be scoped to one model or provider, rate-limited, capped and given an expiry."}},"schemas":{"DecisionRequest":{"type":"object","required":["model","questions"],"properties":{"model":{"type":"string","description":"A decision model id.","example":"d3"},"state":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"The text to decide about, up to 200,000 characters. `input` is the same field in the OpenAI Decisions format."},"input":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"}}],"description":"Used instead of `state` when `questions` is an array."},"questions":{"type":["object","array"],"description":"Up to 64 questions: an object by id (letters, digits, `_`, `.`, `-`), or an array of named `predicate`, `choice` (with `choices`) or `score` (with `levels`) questions in the OpenAI Decisions format. Answers come back in the same shape.","additionalProperties":{"type":"object","required":["type","instructions"],"properties":{"type":{"type":"string","example":"noul"},"instructions":{"type":"string"},"criteria":{"type":"object","additionalProperties":{"type":"string"},"description":"For `choice`: 2 to 64 option names and what each means."}}}}}},"DecisionResponse":{"type":"object","required":["id","object","model","answers","usage"],"properties":{"id":{"type":"string"},"object":{"type":"string","enum":["decision"]},"model":{"type":"string"},"answers":{"type":["object","array"],"description":"One answer per question, in the shape the questions were sent in."},"usage":{"type":"object","properties":{"input_tokens":{"type":"integer"},"output_tokens":{"type":"integer"},"total_tokens":{"type":"integer"}}}}},"ChatCompletionRequest":{"type":"object","required":["model","messages"],"properties":{"model":{"type":"string","description":"A model id from listModels, e.g. `glm-5.3` or `glm-5.3:low`."},"messages":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/ChatMessage"},"description":"The conversation so far, oldest first."},"stream":{"type":"boolean","default":false,"description":"Stream the answer as server-sent events."},"temperature":{"type":"number","minimum":0,"maximum":2,"description":"Sampling temperature."},"top_p":{"type":"number","minimum":0,"maximum":1,"description":"Nucleus sampling probability mass."},"max_tokens":{"type":"integer","minimum":1,"description":"Maximum tokens to generate."},"max_completion_tokens":{"type":"integer","minimum":1,"description":"Maximum tokens to generate, including reasoning (newer name for max_tokens)."},"stop":{"oneOf":[{"type":"string"},{"type":"array","items":{"type":"string"},"maxItems":4}],"description":"Text that ends the answer."},"tools":{"type":"array","items":{"$ref":"#/components/schemas/Tool"},"description":"Functions the model may call."},"tool_choice":{"oneOf":[{"type":"string","enum":["auto","none","required"]},{"type":"object","required":["type","function"],"properties":{"type":{"type":"string","const":"function"},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string"}}}}}],"description":"Whether and which tool the model must call."},"reasoning_effort":{"type":"string","enum":["none","low","medium","high"],"description":"How much a reasoning model thinks before answering. Less is faster and cheaper."}}},"ChatMessage":{"type":"object","required":["role"],"properties":{"role":{"type":"string","enum":["system","developer","user","assistant","tool"]},"content":{"description":"Text, or content parts (text and images) for models that accept images.","oneOf":[{"type":"string"},{"type":"array","items":{"$ref":"#/components/schemas/ContentPart"}},{"type":"null"}]},"name":{"type":"string"},"tool_calls":{"type":"array","items":{"$ref":"#/components/schemas/ToolCall"},"description":"Calls the assistant made (assistant messages)."},"tool_call_id":{"type":"string","description":"The call this message answers (tool messages)."}}},"ContentPart":{"oneOf":[{"type":"object","required":["type","text"],"properties":{"type":{"type":"string","const":"text"},"text":{"type":"string"}}},{"type":"object","required":["type","image_url"],"properties":{"type":{"type":"string","const":"image_url"},"image_url":{"type":"object","required":["url"],"properties":{"url":{"type":"string","description":"An https or data: URL."},"detail":{"type":"string"}}}}}]},"Tool":{"type":"object","required":["type","function"],"properties":{"type":{"type":"string","const":"function"},"function":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"parameters":{"type":"object","description":"A JSON Schema for the arguments.","additionalProperties":true}}}}},"ToolCall":{"type":"object","required":["id","type","function"],"properties":{"id":{"type":"string"},"type":{"type":"string","const":"function"},"function":{"type":"object","required":["name","arguments"],"properties":{"name":{"type":"string"},"arguments":{"type":"string","description":"The arguments as a JSON string."}}}}},"ChatCompletion":{"type":"object","required":["id","object","created","model","choices"],"properties":{"id":{"type":"string"},"object":{"type":"string","const":"chat.completion"},"created":{"type":"integer","description":"Unix time in seconds."},"model":{"type":"string"},"choices":{"type":"array","items":{"type":"object","required":["index","message","finish_reason"],"properties":{"index":{"type":"integer"},"message":{"type":"object","required":["role","content"],"properties":{"role":{"type":"string","const":"assistant"},"content":{"type":["string","null"]},"tool_calls":{"type":"array","items":{"$ref":"#/components/schemas/ToolCall"}}}},"finish_reason":{"type":["string","null"],"enum":["stop","length","tool_calls","content_filter",null]}}}},"usage":{"$ref":"#/components/schemas/Usage"}}},"Usage":{"type":"object","required":["prompt_tokens","completion_tokens","total_tokens"],"properties":{"prompt_tokens":{"type":"integer"},"completion_tokens":{"type":"integer"},"total_tokens":{"type":"integer"},"prompt_tokens_details":{"type":"object","properties":{"cached_tokens":{"type":"integer"},"cache_write_tokens":{"type":"integer"}}}}},"ModelList":{"type":"object","required":["object","data"],"properties":{"object":{"type":"string","const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Model"}}}},"Model":{"type":"object","required":["id","object","created","owned_by"],"properties":{"id":{"type":"string","description":"The id to send as `model`."},"object":{"type":"string","const":"model"},"created":{"type":"integer","description":"Unix time in seconds when the model was added."},"owned_by":{"type":"string","const":"saaufai"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["message","type","code","resolution"],"properties":{"message":{"type":"string","description":"What went wrong."},"type":{"type":"string","description":"A stable machine-readable kind, e.g. `authentication_error` or `rate_limit_exceeded`."},"code":{"type":"integer","description":"The HTTP status."},"resolution":{"type":"string","description":"What to do next."},"docs":{"type":"string","description":"Where errors are explained."}}}},"example":{"error":{"message":"Invalid or missing API key","type":"authentication_error","code":401,"resolution":"Authenticate: the API (/v1) needs `Authorization: Bearer sk-saauf-...` with a key from /account/keys; account endpoints need you signed in at /login.","docs":"/docs/errors"}}}}}}