When to use the native API

The OpenAI surface reaches two capabilities. The native endpoints reach all seven, and return the numbers you route on.

The OpenAI-compatible surface exists for one reason: you already have an OpenAI client. It maps /v1/chat/completions onto extract for structured output, and onto classify plus extract for function calling. The other five capabilities have no chat equivalent.

Move to the native endpoints when you need a probability, a character offset, a batch, or a capability the facade cannot reach.

What the OpenAI surface cannot return

You needNative fieldAvailable over chat/completions
Truth probability for a statementprobability on yes-noNo
Label distribution over your taxonomyscores, confidence on classifyNo
A position on an ordered scalescore, level on rateNo
Character offsets of an answer spanstart, end on answerNo
Every span per type, with offsetsentities[] on entitiesNo
A value check against the source textmatches, found[] on verifyNo
Up to 32 texts in one requesttexts on every capabilityNo
A filled JSON Schemadata on extractYes, as a JSON string

The chat facade returns one assistant message. Structured extraction arrives as a JSON string you must parse. Function calling arrives as one tool call with a stringified arguments object. Neither carries a confidence number, so you cannot build the confidence routing that the native fields make trivial.

The facade never chats. Plain chat, response_format: {"type":"text"} and response_format: {"type":"json_object"} all return 400 unsupported_request. That is deliberate. The streaming and differences page lists every rejected request.

Migration table

Chat/completions usageNative callWhat you gain
response_format.json_schemaPOST /v1/decision-machine-1/extractA parsed data object, texts batching, no JSON string to decode
tools with two or more entriesPOST /v1/decision-machine-1/classifyscores per tool, confidence, and one inference call instead of two
tools with one entry, or a named tool_choicePOST /v1/decision-machine-1/extractThe same arguments, without the tool-call envelope
A prompt asking “is this X, yes or no?”POST /v1/decision-machine-1/yes-noprobability, and up to 32 statements per call
A prompt asking for a 1-to-5 ratingPOST /v1/decision-machine-1/ratescore, level, confidence, scores[]
A prompt asking “where does it say X?”POST /v1/decision-machine-1/answerAn extractive span plus start and end
A prompt listing names, dates or amountsPOST /v1/decision-machine-1/entitiesEvery span, typed, with offsets and probabilities
A prompt checking a field against a documentPOST /v1/decision-machine-1/verifymatches, probability and found[] for review queues

Fewer calls per request

Tool selection over two or more tools runs classify first to pick the tool, then extract to fill its arguments. That is two inference calls. Calling classify or extract directly runs one.

Measured on 2026-09-16 against production:

CallObserved latency
chat tools, 2 tools (classify + extract)1.16 s
chat forced tool_choice (extract only)0.43 s
chat json_schema0.63 s
native extract, 9 properties0.72 s
native classify, 3 labels1.18 s

Batching removes round trips. The chat facade takes one message set per request. Every native endpoint takes texts with up to 32 items. yes-no and answer also take up to 32 statements or questions.

Statements and questions cost almost nothing extra, because they go into one inference call. Each item in texts costs its own inference call. Batching covers the ordering rules.

Every capability response reports usage in the x-input-chars and x-input-tokens headers. x-input-chars is the number of input characters. x-input-tokens is the input tokens billed for this call. Cost scales with everything you send, text plus labels, questions, or schema, at $0.04 per million input tokens and $0 per output token. See Pricing.

The same job, both ways

A chat client extracting a reservation:

OpenAI SDK
from openai import OpenAI
client = OpenAI(base_url="https://api.milliseconds.ai/v1", api_key="any-string")
completion = client.chat.completions.create(
model="decision-machine-1",
messages=[{"role": "user", "content": "Book Nobu for 4 on Friday."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "reservation",
"schema": {
"type": "object",
"properties": {
"restaurant": {"type": "string"},
"party_size": {"type": "integer"},
"day": {"type": "string"},
},
},
},
},
)
print(completion.choices[0].message.content)

That returns the JSON as a string:

{"restaurant":"Nobu","party_size":4,"day":"Friday"}

The native call returns the same result as an object, and accepts a batch:

curl -s https://api.milliseconds.ai/v1/decision-machine-1/extract \
-H 'content-type: application/json' \
-d '{
"texts": ["Book Nobu for 4 on Friday.", "Table for 2 at Kura on Sunday."],
"schema": {
"type": "object",
"properties": {
"restaurant": {"type": "string"},
"party_size": {"type": "integer"},
"day": {"type": "string"}
}
}
}'
{
"results": [
{"data": {"restaurant": "Nobu", "party_size": 4, "day": "Friday"}},
{"data": {"restaurant": "Kura", "party_size": 2, "day": "Sunday"}}
]
}

Two texts, one request, one results array in input order. The chat surface needs two round trips for the same work.

Keep the OpenAI surface when

  • An agent framework or SDK speaks OpenAI and you cannot change it.
  • You route tools by description and one tool call per turn is enough.
  • You want a drop-in swap behind an existing base_url, with no client changes.

The facade and the native endpoints run the same models. The facade adds a translation layer, not a different decision.

You can mix the two. Keep the OpenAI client for tool calling inside your agent loop, and call verify or yes-no natively for the guardrails around it.

Migration checklist

1

Name the decision

Write the question your prompt asks in one sentence. Match it to a capability in Choosing a capability.

2

Replace the endpoint

Drop the OpenAI client. Send JSON to https://api.milliseconds.ai/v1/decision-machine-1/<capability>. No API key is required during the launch period.

3

Route on the numbers

Read probability and confidence, not just the label. Set a threshold per action with Thresholds and confidence routing.

4

Batch what you can

Collapse loops into texts, statements or questions, up to 32 items each.

Next