Guide
Models and Text API
Request and response conventions for querying available models and text chat (including streaming)
Models and Text API
The model catalog and text chat both use the same platform API key. For shared conventions (base URL, authentication, rate limiting, retries), see Authentication and Common Conventions.
Query the model list
/v1/modelsAuthentication
Authorization: Bearer <API_KEY>.
Request parameters
There is no request body; simply include the API key in the request header.
Request example
This step requires no request body; just include the API key in the request header.
The returned data[].id is the model ID you will use for all subsequent calls.
Request
curl "https://locopalai.com/v1/models" \
-H "Authorization: Bearer $LOCOPAL_API_KEY"Response parameters
| Parameter | Type | Description |
|---|---|---|
object | string | Always list |
success | boolean | true when the query succeeds |
data | array | List of models the current key can call |
data[].id | string | Model ID; use it verbatim when submitting requests |
data[].object | string | Always model |
data[].created | integer | Catalog record time (Unix seconds) |
data[].owned_by | string | Model provider identifier, for reference only |
data[].supported_endpoint_types | string[] | Endpoint types available for this model |
objectandsuccessare top-level fields and are not insidedata[].objectis alwayslist, andsuccessistrueon success. Determine whether the request succeeded by the HTTP status code;successis only supplementary.
Response body
{
"data": [
{
"id": "doubao-seedance-2.0",
"object": "model",
"created": 1626777600,
"owned_by": "doubaovideo",
"supported_endpoint_types": ["openai"]
},
{
"id": "claude-sonnet-4-5-20250929",
"object": "model",
"created": 1626777600,
"owned_by": "claude",
"supported_endpoint_types": ["anthropic", "openai"]
}
],
"object": "list",
"success": true
}A response of "data": [] means the call group that the current key belongs to has no available models; first check the group configuration or contact platform support, and do not retry with the display name shown on the page. The model catalog changes with authorization and platform availability, so refresh it once when the service starts, when the user selects a model, or when you receive a "model unavailable" error.
Error response
| HTTP | Response | Description |
|---|---|---|
401 | {"error":{"code":"","message":"Invalid token (request id: ...)","type":"new_api_error"}} | API key missing, incorrect, or expired |
500 | {"error":{"message":"...","type":"new_api_error"}} | Catalog query failed; retain the request identifier and retry |
Text chat
/v1/chat/completionsAuthentication
Authorization: Bearer <API_KEY>.
Request body · application/json
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | Yes | Text model ID returned by /v1/models |
messages | array | Yes | Messages ordered by time |
messages[].role | string | Yes | system, user, or assistant |
messages[].content | string | Yes | Content of the current message |
stream | boolean | No | When true, responses are streamed via SSE and end when [DONE] is read |
temperature | number | No | Whether it is supported depends on the actual capabilities of the current model |
max_tokens | integer | No | Whether it is supported depends on the actual capabilities of the current model |
Request example
model must be the id returned by /v1/models, not the display name shown on the page.
When stream is set to true, the response is returned segment by segment over SSE and ends when [DONE] is read.
The right side shows equivalent examples in three languages; switch tabs to compare.
curl -X POST "https://locopalai.com/v1/chat/completions" \
-H "Authorization: Bearer $LOCOPAL_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"model": "从 /v1/models 选择的文本模型 ID",
"messages": [{ "role": "user", "content": "只回复两个字:你好" }],
"stream": false
}
JSONResponse parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Identifier for this response, used for troubleshooting and reconciliation |
object | string | chat.completion for non-streaming responses |
created | integer | Response time (Unix seconds) |
model | string | Model ID used for this request |
choices | array | List of results |
choices[].index | integer | Result index |
choices[].message.role | string | Always assistant |
choices[].message.content | string | Generated text |
choices[].finish_reason | string | Reason for finishing; length means truncated by length |
usage.prompt_tokens | integer | Input usage |
usage.completion_tokens | integer | Output usage |
usage.total_tokens | integer | Total usage |
choices[].message.refusal | string / null | Explanation when the model refuses content; usually null for normal answers |
choices[].message.annotations | array | Additional annotations from the model; may be empty |
usage also contains detailed usage fields for this request's billing (cache hits, input/output breakdown, etc.); rely on the actual response. Some channels also return extension fields such as latency_checkpoint, routing, and system_fingerprint; clients should ignore fields they do not recognize rather than parsing against a fixed full set of fields.
Response body
{
"id": "msg_011CevNtsfivFKb6YP3WcL7A",
"model": "claude-haiku-4-5-20251001",
"object": "chat.completion",
"created": 1789071575,
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "你好" },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 20587,
"completion_tokens": 6,
"total_tokens": 20593
}
}Streaming response (add "stream": true to the request body; the response is text/event-stream):
data: {"choices":[{"delta":{"content":"这是"}}]}
data: {"choices":[{"delta":{"content":"模型输出"}}]}
data: [DONE]Clients must keep reading until [DONE]; streaming responses must not be cached or concatenated into duplicated content by intermediary layers. The final data chunk may contain only usage, in which case choices may be empty.
Reasoning models may return empty content
Some reasoning models spend the output quota on hidden reasoning first. Observed: when requesting gpt-5-nano with a very small max_tokens, the HTTP response is 200, but message.content is empty, finish_reason is length, and usage.completion_tokens_details.reasoning_tokens equals the output limit. In this case, increase the quota or switch to a non-reasoning text model; do not treat an empty string as a model malfunction or API failure.
Server-side example
const response = await fetch('https://locopalai.com/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.LOCOPAL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: '从 /v1/models 选择的模型 ID',
messages: [{ role: 'user', content: '写一条产品简介。' }],
}),
})
if (!response.ok) throw new Error(await response.text())
const data = await response.json()Never put the API key in browser code; frontend requests should go to your own backend first, and the backend forwards them.
Error response
{
"error": {
"code": "model_not_found",
"message": "No available channel for model xxx under group xxx (distributor) (request id: ...)",
"type": "new_api_error"
}
}| HTTP | Response / Cause | Handling |
|---|---|---|
400 | {"error":{"code":"","message":"Model name not specified, model name cannot be empty (request id: ...)","type":"new_api_error"}} | Request body is missing model |
401 | Invalid token | API key missing, incorrect, or expired |
503 | {"error":{"code":"model_not_found","message":"No available channel for model ..."}} | The current group has no available channel for this model; choose a model of the same kind or contact platform support |
429 | Request rate limited | Retry with backoff |
For the complete explanation, see Error Codes and Troubleshooting.
