Skip to main content
The Completion API is an OpenAI Chat Completions–compatible endpoint scoped to a single space. Point an OpenAI SDK at it and it works unmodified: the space is a path segment, so nothing nuwacom-specific has to be smuggled into the request body. Each request runs against a server-side agent with access to that space’s knowledge base. Fields the endpoint does not recognise are rejected rather than ignored, so a request that is accepted is a request that was honoured in full. Retrieval is off unless you ask for it: to ground answers in the knowledge base, send knowledge — see Grounding the answer. Web search is off too unless knowledge includes web — see Web search.

Before you start

  • API key: every request needs a Bearer token. Admins create API keys in the workspace settings under the API Keys section.
  • Key permissions: the key needs Workspaces: Read on the space in the URL, otherwise the request is a 403. For answers grounded in the knowledge base it also needs KB assets: Read (uploaded files) and Contents: Read (authored documents): without them retrieval finds nothing, and the answer comes back without grounding rather than as an error. A key never reaches further than the role of the admin who created it.
  • Keep the key server-side: the endpoint accepts requests from browsers, so a key shipped in client-side code can be used by anyone who reads it.
  • Model ids: ask GET .../openai/models for the ids this space accepts. They are nuwacom catalog keys (for example claude-sonnet-5-5 or openai-gpt-6.1-sol), not OpenAI model names.

Base URL

Replace {customer-tenant} with your workspace’s tenant name and {spaceId} with the space the request should run against. That URL is what an SDK takes as its base_url; the SDK appends /chat/completions and /models itself.

Endpoints

GET /models returns the public model catalog narrowed to what this space’s administrators have enabled in Settings, and GET /models/{model} answers one entry of that same list — which is also the set POST /chat/completions gates on, so the three cannot disagree.

Model capabilities

Each model entry carries a capabilities object, a nuwacom extension the OpenAI SDKs keep as an extra field (model.model_extra["capabilities"] in Python). It is read off the same rules POST /chat/completions enforces, so what it allows is what a request may send:
This API serves Chat Completions and Models only. Other OpenAI endpoints — the Responses API, Embeddings, Files — answer with an OpenAI-shaped 404 naming what is served, rather than a bare not-found.

Quickstart

Streaming is the same call with stream=True; the chunk that carries usage has no choices, so skip it:
Coding agents that execute their own tools on your machine — Cursor’s or GitHub Copilot’s agent mode, Claude Code, Codex — cannot use this endpoint: it refuses client-side tools, because the request already runs a server-side agent.

Supported parameters

The endpoint accepts OpenAI’s Chat Completions body with the following exceptions. Anything marked Not supported is answered with a 400 whose message says why, rather than being silently dropped. Unknown fields are rejected rather than ignored, so a 200 means every field you sent was honoured.
Sampling parameters are applied where the backend serving the model takes them. The OpenAI reasoning families (o-series, GPT-5.x) accept neither temperature/top_p nor stop, seed and the penalties, so sending one to those models is a 400 that names the parameter and the model, rather than a 200 that silently ignored it; Claude refuses a temperature above 1 with a 400; and reasoning_effort is folded onto the nearest rung of a backend’s own scale (xhigh and max both reach Anthropic as max).

Images

An image_url part takes a data: URI or an http(s) URL, and only on a user message. One request may carry at most 10 images, 20 MB each and 40 MB in total. Inlined data: URIs also count towards the request size limit, so send larger images as URLs. The internal storage:// scheme is refused — reach knowledge base assets through knowledge instead.

Structured output

Which of text, json_object and json_schema a model supports depends on the backend serving it, which the model id does not reveal — capabilities.response_formats on its model entry lists them:
  • Served by OpenAI — json_schema and json_object
  • Served by Anthropic — json_schema only; there is no free-form JSON mode
  • Served by Bedrock or Google — neither; these have no way to receive a schema
Rather than answer with prose where you expected parseable JSON, a model that cannot be held to the format you asked for is refused with a 400.

Finish reasons

finish_reason is stop for a complete answer, length when the output reached the token cap and is worth continuing, and content_filter when the provider withheld it. It is never tool_calls: tools run server-side, and the caller only ever receives the final answer.

Prompt caching

Prompt caching is on by default and needs nothing in the request. When a request begins with the same tokens as a recent request to the same model, the provider can reuse that prefix instead of processing it again, which mainly shortens the wait on long prompts and long conversations. How the prefix gets cached depends on the backend serving the model: To get the most out of it, keep the start of your requests stable: the system or developer message first, earlier turns unchanged and in order, and whatever changes from one request to the next — the new question, per-request data — at the end. Rewriting an earlier message changes the prefix from that point on. usage.prompt_tokens_details.cached_tokens reports how many of the prompt_tokens were read from the cache, on a non-streamed response and on the final usage chunk of a stream sent with stream_options.include_usage. It is 0 when nothing was cached, including on backends that do not report cache reads. OpenAI’s prompt_cache_key, prompt_cache_options and prompt_cache_retention are refused with a 400: caching is managed for you, and a key or retention setting could not be honoured on every backend.

nuwacom extensions

knowledge is the only field this endpoint adds to OpenAI’s body. The OpenAI SDKs have no argument for it, so it goes in extra_body (Python) or the provider’s extra-body option.
Five reference types: asset is an uploaded file in the space’s knowledge base, content is a document authored in nuwacom, folder is expanded into the assets and contents it holds, space (no id) is the whole space’s knowledge base, and web (no id) is web search.

Grounding the answer

Retrieval is opt-in: space cannot be combined with asset, content or folder references; web combines with any of them. Retrieval and web search run as agent tools, so a model without tool support refuses knowledge with a 400 — its unsupported_parameters lists knowledge. knowledge is the whole retrieval scope, whatever the space’s skills say: a skill that needs the knowledge base, or one with documents attached to it, only searches what knowledge allows. To give such a skill its documents, send { "type": "space" } or pin them. Web search is opt-in, like retrieval. Add { "type": "web" } to knowledge and the agent may search the web when the question needs it; leave it out and no query leaves nuwacom. The agent decides whether a search is needed rather than searching on every request. This holds whatever the space’s skills say: a skill that needs web search only searches when the request includes web.
OpenAI’s web_search_options is refused with a 400 that points here, including when a framework sends it for you (Pydantic AI’s WebSearchTool does). Agents have their own API — see the Agents API.

Limits

A non-streamed request sends nothing until the whole run has finished, retrieval included. For long, grounded answers prefer stream: true: tokens arrive as they are produced, and the connection is not left idle at the gateway.

Errors

Every failure is answered in OpenAI’s envelope, whatever the status, so an SDK raises the exception it would against OpenAI itself:
A streamed request has already sent its 200 and headers before the first model call, so a failure mid-stream arrives as a data: {"error": {...}} frame — the same envelope, same vocabulary — immediately before data: [DONE].