# Application API

[Documentation](../public-index.md) / Reference / Application API

Your application talks to Pointsman; Pointsman authenticates its virtual key, applies
its controls and calls an eligible model. You supply provider credentials to the
gateway, not to every application. This page explains the application-facing HTTP
contract with examples you can adapt after the [quickstart](../tutorial/README.md).

The launch scope is **Chat Completions**. This is not support for every endpoint or
field in another provider's API. Read the [compatibility matrix](compatibility.md)
for provider differences and explicit refusals. The examples here are illustrative;
this documentation update did not execute live-provider requests.

## Base URL and authentication

For the local quickstart, use `http://localhost:8080`. Use your deployment's HTTPS
address outside that local setup. Every endpoint below uses a Pointsman virtual key:

```http
Authorization: Bearer <your-pointsman-virtual-key>
```

Set these variables in a private terminal for the curl examples. Replace both
placeholders before running; use a model or group your key is permitted to access.

```bash
export POINTSMAN_URL='http://localhost:8080'
export POINTSMAN_KEY='<your-pointsman-virtual-key>'
export POINTSMAN_MODEL='<allowed-model-or-group>'
```

Do not use your provider key or dashboard password as the bearer token. Admin calls
use a separate authentication surface described in the [admin API](admin-api.md).
The examples use `curl` and `jq`; `jq` constructs JSON safely from shell variables.

## Endpoints at a glance

| Method and path | Result |
|---|---|
| `POST /v1/chat/completions` | One JSON completion or an SSE stream. |
| `GET /v1/models` | The caller's available models, usable groups and allowed aliases. |
| `GET /v1/models/{id}` | One available entry, or 404 if it is unknown or unavailable to the caller. |
| `POST /v1/feedback` | Accept feedback about an eligible outcome-learning decision. |
| `POST /v1/quality/assessments` | Record typed quality evidence about a quality-guarded response, citing its `X-Quality-Receipt` (experimental; [how-to](../how-to/quality-guarded-routing.md#4-submit-evidence)). Returns what was stored (`201` created, `200` duplicate, `409` conflicting revision); never claims a routing effect. |

Responses, embeddings, audio, batch and rerank APIs are not implemented. Tool
pass-through does not execute a tool or authorize the tool's eventual actions.

## Create a Chat Completion

`POST /v1/chat/completions` accepts `Content-Type: application/json`.

```bash
jq -n --arg model "$POINTSMAN_MODEL" '{
  model: $model,
  messages: [
    {role: "system", content: "Explain technical concepts in plain language."},
    {role: "user", content: "What does an AI gateway do?"}
  ],
  max_tokens: 128,
  temperature: 0,
  stream: false
}' | curl --fail-with-body --silent --show-error \
  --dump-header response-headers.txt \
  "$POINTSMAN_URL/v1/chat/completions" \
  -H "Authorization: Bearer $POINTSMAN_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @-
```

Requests to a live provider may incur charges. A representative response looks like
this; IDs, text, model identifiers and token counts vary:

```json
{
  "id": "chatcmpl-example",
  "object": "chat.completion",
  "created": 1800000000,
  "model": "support-chat",
  "choices": [
    {
      "index": 0,
      "message": {"role": "assistant", "content": "An AI gateway manages your application's calls to AI models."},
      "finish_reason": "stop"
    }
  ],
  "usage": {"prompt_tokens": 25, "completion_tokens": 18, "total_tokens": 43}
}
```

| Response field | How to use it |
|---|---|
| `id`, `object`, `created` | Completion identifier, object kind and Unix timestamp in seconds. Use `X-Request-ID` for gateway investigation. |
| `model` | The response's request-facing model name; it can be a group. Inspect served-model evidence separately. |
| `choices[0].message` | Assistant message. For supported tool requests it can contain tool calls instead of ordinary text. |
| `choices[0].finish_reason` | Why generation ended; distinguish a normal stop, length exhaustion and tool calls where supported. |
| `usage` | Token accounting when available. Provider usage and estimates have different certainty, especially after interruption. |
| `service_tier` | Optional provider-reported processing tier when returned. |
| `guardrail_results` | Optional gateway extension when the configured response reporting includes it. |

On synchronous provider-served responses, `X-Served-Provider` and `X-Provider-Model`
help identify what answered. Cache hits and live streams have different header
availability; see the [header reference](response-headers.md). Do not assume an
absent header has a default value.

### Request fields and boundaries

| Field | Meaning and important boundary |
|---|---|
| `model` | Concrete model, alias or routing group. Prefer an explicit value in a first integration; omission depends on configured resolution/defaults. |
| `messages` | Required nonempty conversation. Use lowercase supported roles and the content shape for that role. Text is supported; image input is refused in the v1 exact-tokenizer profile. |
| `stream` | Omitted/false gives JSON; true gives SSE. |
| `max_tokens` / `max_completion_tokens` | Positive output cap. They are aliases; conflicting values are refused. An omitted cap is supplied and bounded by gateway/model/context limits. |
| `temperature`, `top_p`, `stop` | Sampling and stop controls, subject to selected model and adapter support. An omitted temperature is not automatically zero. |
| `seed`, `presence_penalty`, `frequency_penalty` | Adapter-dependent controls. Check the adapter matrix before relying on them. |
| `logit_bias`, `logprobs`, `top_logprobs` | OpenAI-compatible adapter controls. `top_logprobs` requires `logprobs: true`; logprob requests bypass cache/coalescing. |
| `tools`, `tool_choice`, `parallel_tool_calls`, function `strict` | OpenAI-compatible pass-through; Anthropic and Ollama adapters currently refuse these tool requests. |
| `service_tier` | Processing-tier pass-through where supported, not a routing strategy. |
| `n` | Omitted or 1. Multiple choices are not supported. |
| `response_format`, `reasoning_effort`, `thinking` | Refused. Do not infer structured-output or reasoning-control support from the API's shape. A model row may fix an effort the operator chose; the client cannot change it. |
| `user`, `metadata`, `store`, `prompt_cache_key`, `safety_identifier`, `stream_options` | Tolerated and dropped. Acceptance does not mean the provider received them or that they were stored. |

The [compatibility matrix](compatibility.md#request-controls) includes the remaining
explicitly refused controls. The [adapter matrix](../design/README.md)
explains forwarding differences. Unknown fields are not a general pass-through
contract. Context checks use the configured tokenizer mode and model metadata;
chat framing and tool scaffolding still limit provider-exact counting claims. In
exact-tokenizer mode the count is taken after model resolution, with the encoding of
the model or group that will serve, so an unknown `model` behaves as the key's resolution
policy says and an omitted `model` is resolved by the blank-model path (key default, tenant
default, semantic group, mode tier, cheapest allowed), the same as in estimate mode, unless the
key or the gateway is set to refuse it (`400 model_required`).

### Request headers

Use `X-Request-ID` to supply a correlation ID (1–128 characters from letters,
digits, `.`, `_` and `-`), or let the gateway generate one. Use
`X-Cache-Control: no-cache` when deliberately bypassing both cache tiers, for example
while checking provider connectivity. This can increase provider calls and charges.

Other cache, guardrail and routing-related headers are described in the
[header reference](response-headers.md) and the feature guides. These headers do not
bypass a virtual key's permissions or spending controls.

## Stream a completion

Use the same endpoint with `stream: true`. `curl --no-buffer` displays data as it
arrives instead of buffering output:

```bash
jq -n --arg model "$POINTSMAN_MODEL" '{
  model: $model,
  messages: [{role: "user", content: "Explain a gateway in three short sentences."}],
  max_tokens: 128,
  stream: true
}' | curl --fail-with-body --silent --show-error --no-buffer \
  "$POINTSMAN_URL/v1/chat/completions" \
  -H "Authorization: Bearer $POINTSMAN_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @-
```

Consume SSE records, not arbitrary TCP chunks. A text event has a
`chat.completion.chunk` object with `choices[].delta`; append content deltas to build
the answer. Handle empty deltas, finish information and usage separately. A normal
stream terminates with `data: [DONE]`.

```text
data: {"id":"chatcmpl-example","object":"chat.completion.chunk","created":1800000000,"model":"support-chat","choices":[{"index":0,"delta":{"content":"An AI gateway"},"finish_reason":null}]}

data: [DONE]
```

This abbreviated illustration omits intervening chunks. Tool calls are emitted as
an assembled chunk when complete, not progressive argument fragments. The gateway
owns upstream usage requests; sending `stream_options` does not change that. For a provider
configured as a registry row, upstream stream usage is requested only when the operator set the row's
`streamUsage` quirk; otherwise the stream carries no usage chunk.

Check both the initial HTTP status and error payloads arriving after the stream
starts. HTTP 200 cannot become a different status after headers have been sent.
An interrupted stream may contain partial output and estimated usage. Avoid blindly
retrying it as if no provider work occurred.

## Discover available models

```bash
curl --fail-with-body --silent --show-error \
  "$POINTSMAN_URL/v1/models" \
  -H "Authorization: Bearer $POINTSMAN_KEY" | jq .
```

The response is a list object with `data` entries. A concrete model uses
`object: "model"`; a routing group uses `object: "model-group"`. Entries have `id`,
`object`, `created` and `owned_by`. Optional metadata includes `family`, `tier`,
`cost_per_1k`, `modality`, `context`, `params`, `tags`, `aliases`, `deprecated` and
`alias_for`. Omitted metadata means it was not supplied, not that a capability or
limit is unlimited. `cost_per_1k` is configured input pricing metadata; it is not a
complete input/output price quote or live provider invoice.

Retrieve a single entry by URL-encoding its identifier:

```bash
MODEL_PATH=$(jq -rn --arg id "$POINTSMAN_MODEL" '$id | @uri')
curl --fail-with-body --silent --show-error \
  "$POINTSMAN_URL/v1/models/$MODEL_PATH" \
  -H "Authorization: Bearer $POINTSMAN_KEY"
```

Discovery is scoped to the caller. A 404 on this endpoint does not distinguish an
unknown identifier from one the key cannot access. Listing a model is not proof
that the provider is healthy or that every optional request control is supported.

## Send outcome feedback

`POST /v1/feedback` is for the outcome-learning router. A weighted-random or direct
model request does not become trainable just because you send a rating.

For an eligible decision, save `X-Decision-Receipt` from the completion response
and send it as `decision_id`. Prefer this signed receipt to the bare `X-Decision-Id`
when using multiple gateway instances. Use the same virtual key that made the call.

```bash
export POINTSMAN_DECISION='<X-Decision-Receipt-from-the-response>'
jq -n --arg decision "$POINTSMAN_DECISION" '{
  decision_id: $decision,
  rating: 1,
  revision: 0
}' | curl --fail-with-body --silent --show-error \
  "$POINTSMAN_URL/v1/feedback" \
  -H "Authorization: Bearer $POINTSMAN_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @-
```

| Field | Contract |
|---|---|
| `decision_id` | Required nonblank signed receipt or eligible decision ID. Ownership and age are checked. |
| `rating` | Integer: 1 for positive or −1 for negative. Zero is refused. Required unless `retract` is true, and refused with it. |
| `revision` | Optional integer, defaults to 0; range 0 through 9,007,199,254,740,991. Reuse a revision on a retry; increase it for an edited rating or a retraction. |
| `retract` | Optional boolean. `true` withdraws the decision's rating (send it with a higher `revision` and no `rating`); the decision returns to its unrated reward. A retraction without a `revision` of at least 1 is refused with 400. Withdrawing the only quality of a `rate`-composition decision is recorded and changes nothing, because such a decision has no unrated reward to return to. Not available for decisions made before the learning semantics changed (17 October 2026). |

A rating revises the decision it names; it does not count as an extra observation.

A valid submission normally returns `202 {"accepted":true}`. **This acknowledges
submission, not proof that a policy changed.** Unknown, expired or ineligible
provenance can be ignored without revealing whether someone else's decision exists.
A disabled feedback service can return 503; feedback rate limits can return 429 with
`Retry-After`. Use your actual task result as feedback, such as whether an extraction
passed validation. [How learning uses feedback](../how-to/turn-on-outcome-learning.md).

## Errors and retries

Most gateway errors use an `error` object with `message` and `type`; `code` and
`param` are included where provided. Parse defensively rather than requiring every
field. For example, an unsupported control can identify both the field and the
`unsupported_request_control` code.

| Status or situation | What to investigate |
|---|---|
| 400 | Invalid shape, unsupported control, invalid token cap or an incompatible request. Correct it before retrying. |
| 401 / 403 | Key validity, environment, authorization or scope. Dashboard credentials are not application credentials. |
| 402 | Spending restrictions. Inspect the error and effective limits; retries alone do not create budget. |
| 404 on model lookup | Unknown or unavailable model. Recheck discovery with the same key. |
| 429 | Rate/capacity refusal. Honor `Retry-After` when present and reduce concurrency or request rate. |
| 503 `budget_recovering` | The budget this request is charged to is held until its balance is established: the counter store is starting or activating a new budget generation (transient: retry with backoff, usually clears in seconds), or the operator must attest it after a counter-store loss or a declared database restore (retries will NOT clear it; only the operator's recovery does). The refusal happens before any provider call. Bound your retries; do not retry indefinitely. |
| 5xx | Inspect the error code, request ID and dependency state. Budget recovery and upstream outages require different responses. |
| Error after SSE begins | Preserve partial output and error details; initial HTTP 200 is not a successful completion. |

These categories are not an exhaustive error-code catalogue. Use bounded retries
with backoff for transient failures, and consider whether the upstream may already
have processed the request. This page does not promise exactly-once completion or
idempotent billing for a retried chat request.

When reporting an issue, include a redacted request shape, gateway version, request
ID, status/error code and whether it streamed or hit cache. Exclude credentials and
sensitive prompt content. [Request investigation](../how-to/diagnose-a-request.md).

## Implementation references

The contract above was checked against the current
chat request,
chat response,
model entry,
feedback request
and the controllers. Runtime/provider verification remains part of release qualification.
