Overview
Only the endpoints and parameters documented here are supported. All base paths are relative to the API base URL.
https://app.askcontinua.com/api/v1If your Continua AI account uses a different site URL, append /api/v1 to that site URL.
Authentication
Every request requires a Continua AI API token in the Authorization header.
Authorization: Bearer YOUR_CONTINUA_AI_API_TOKENKeep API tokens on your server. Do not expose them in browser code, mobile applications, public repositories, or client-side logs.
export CONTINUA_AI_API_KEY="YOUR_CONTINUA_AI_API_TOKEN"
export CONTINUA_AI_API_BASE_URL="https://app.askcontinua.com/api/v1"How requests work
All responses are created asynchronously.
- Upload one or more files and retain each returned
file_id. - Create a response with a prompt and at least one file or project.
- Poll the response until its status becomes
completed,failed, orcancelled. - Read the generated answer from
output_text. - To continue the conversation, create another response with
previous_response_id.
Streaming is not currently supported. Set background to true and do not send stream: true.
Quickstart
Install the client library with python -m pip install openai or npm install openai. The first response must include at least one uploaded file or a Continua AI project.
Python
import os
import time
from uuid import uuid4
from openai import OpenAI
client = OpenAI(
api_key=os.environ["CONTINUA_AI_API_KEY"],
base_url=os.environ["CONTINUA_AI_API_BASE_URL"].rstrip("/"),
)
uploaded_file = client.files.create(
file=("hello.txt", b"Hello world.", "text/plain"),
purpose="user_data",
)
response = client.responses.create(
model="deepseek-flash",
background=True,
input=[
{
"role": "user",
"content": [
{"type": "input_file", "file_id": uploaded_file.id},
{"type": "input_text", "text": "Summarize this file in one sentence."},
],
}
],
extra_headers={"Idempotency-Key": f"response-{uuid4().hex}"},
)
terminal_statuses = {"completed", "failed", "cancelled"}
while response.status not in terminal_statuses:
time.sleep(5)
response = client.responses.retrieve(response.id)
if response.status != "completed":
raise RuntimeError(f"Response ended with status={response.status}")
print(response.output_text)Node.js
import { randomUUID } from "node:crypto";
import OpenAI, { toFile } from "openai";
const client = new OpenAI({
apiKey: process.env.CONTINUA_AI_API_KEY!,
baseURL: process.env.CONTINUA_AI_API_BASE_URL!.replace(/\/$/, ""),
});
const uploadedFile = await client.files.create({
file: await toFile(Buffer.from("Hello world."), "hello.txt", { type: "text/plain" }),
purpose: "user_data",
});
let response = await client.responses.create(
{
model: "deepseek-flash",
background: true,
input: [
{
role: "user",
content: [
{ type: "input_file", file_id: uploadedFile.id },
{ type: "input_text", text: "Summarize this file in one sentence." },
],
},
],
},
{ headers: { "Idempotency-Key": `response-${randomUUID()}` } },
);
const terminalStatuses = new Set(["completed", "failed", "cancelled"]);
while (!terminalStatuses.has(response.status)) {
await new Promise((resolve) => setTimeout(resolve, 5000));
response = await client.responses.retrieve(response.id);
}
if (response.status !== "completed") {
throw new Error(`Response ended with status=${response.status}`);
}
console.log(response.output_text);Models
Use a full model identifier in every create request, or auto to let Continua choose the model.
| Model | Model identifier | Best suited for |
|---|---|---|
| Auto | auto | Continua picks the best model for the task |
| DeepSeek Flash | deepseek-flash | Fast, everyday analysis |
| DeepSeek Pro | deepseek-pro | Deeper reasoning |
| GLM Flash | glm-flash | Fast, cost-efficient work |
| GLM | glm | Balanced performance |
| GPT Luna | gpt-luna | Fast, cost-efficient work |
| GPT Terra | gpt-terra | Balanced performance |
| GPT Sol | gpt-sol | Advanced reasoning |
| GPT Astra | gpt-astra | Most capable reasoning |
| Claude Haiku | claude-haiku | Fast responses |
| Claude Sonnet | claude-sonnet | Balanced speed and depth |
| Claude Opus | claude-opus | Deep analysis |
| Claude Fable | claude-fable | Frontier reasoning |
| Gemini Flash | gemini-flash | Fast multimodal work |
| Gemini Pro | gemini-pro | Long-context work |
| Grok | grok | General-purpose reasoning |
| Kimi | kimi | Long-context analysis |
Model access may depend on your account plan. See pricing for plan information and benchmarks for published evaluations on long-document investigations.
Upload a file
Uploads one file for use in a response.
Form fields
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | One file. Send one file per upload request. |
purpose | String | Yes | Must be user_data. |
Supported filename extensions
.txt, .md, .markdown, .json, .pdf, .docx, .xlsx, .csv,
.eml, .msg, .pptx, .html, .htm, .xhtmlThe maximum file size depends on the account plan.
cURL example
curl --request POST \
"${CONTINUA_AI_API_BASE_URL}/files" \
--header "Authorization: Bearer ${CONTINUA_AI_API_KEY}" \
--form "purpose=user_data" \
--form "file=@report.pdf"Response
{
"id": "file_1234567890abcdef",
"object": "file",
"bytes": 24831,
"created_at": 1789344000,
"expires_at": 1789430400,
"filename": "report.pdf",
"purpose": "user_data",
"status": "processed"
}| Field | Type | Description |
|---|---|---|
id | String | File identifier to use in a response request. |
object | String | Always file. |
bytes | Integer | File size in bytes. |
created_at | Integer | Creation time as a Unix timestamp in seconds. |
expires_at | Integer | Deadline for using an unsubmitted upload. |
filename | String | Original filename. |
purpose | String | Always user_data. |
status | String | Always processed after a successful upload. |
Files uploaded for an API response are temporary and follow the Zero Document Retention workflow: the documents are deleted after the task completes. Each uploaded file can be used in one response creation request; upload the document again to obtain a new file identifier. Files intentionally added to a Project remain available for reuse. See Trust & Security for the complete retention policy.
Create a response
Creates an asynchronous response.
Send a unique Idempotency-Key header with every new request.
Idempotency-Key: response-7c86b96c5a4d48c58bbdb6bca75e1098Request body
{
"model": "deepseek-flash",
"background": true,
"input": [
{
"role": "user",
"content": [
{ "type": "input_file", "file_id": "file_1234567890abcdef" },
{ "type": "input_text", "text": "Summarize the main risks in this document." }
]
}
]
}Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
model | String | Yes | A full model identifier, or auto. |
background | Boolean | Recommended | Must be true when provided. Responses always run asynchronously. |
input | String or array | Yes | The user's request. |
previous_response_id | String | No | Identifier of a completed response to continue. |
report | Object | No | Optional report configuration. |
Only the parameters documented here affect a response. Omit other OpenAI Responses API parameters. In particular, streaming is unavailable.
Input rules
inputmust contain exactly one message.- The message
rolemust beuser. contentmust contain exactly one non-emptyinput_textitem.contentmay contain up to 200 uniqueinput_fileitems.- Each
input_filemust reference a valid, unexpired file identifier owned by the same account. - For the first response, provide at least one
input_fileor at least one project identifier inreport.project_ids.
For a follow-up response, input can be a string and no new file or project is required.
{
"model": "deepseek-flash",
"background": true,
"previous_response_id": "resp_1234567890abcdef",
"input": "Which of these risks should we address first?"
}Report configuration
| Parameter | Type | Required | Description |
|---|---|---|---|
project_ids | Array of strings | No | Unique Continua AI project identifiers to include as sources. |
findings_criteria | String | No | Criteria used to select relevant findings. |
composition_criteria | String | No | Instructions for organizing and presenting the response. |
guardrail_mode | String | No | Validation level: off, lite, or strict. |
Each referenced project must belong to the authenticated account and contain at least one document that has finished processing.
from uuid import uuid4
response = client.responses.create(
model="deepseek-flash",
background=True,
input="Identify the most material risks in this project.",
extra_body={
"report": {
"project_ids": ["project_1234567890abcdef"],
"findings_criteria": "Material risks supported by source evidence",
"composition_criteria": "Present a concise, prioritized report",
"guardrail_mode": "lite",
}
},
extra_headers={"Idempotency-Key": f"project-{uuid4().hex}"},
)Idempotency
An idempotency key must contain 1 to 255 visible ASCII characters. Use a new key for every new response, and reuse the same key only when retrying an identical request.
Create response payload
{
"id": "resp_1234567890abcdef",
"object": "response",
"created_at": 1789344000,
"status": "queued",
"previous_response_id": null,
"background": true,
"output_text": "",
"output": [],
"error": null
}The create response is not the final result. Save id and retrieve the response until it reaches a terminal status.
Retrieve a response
Retrieves the current state and result of a response.
curl \
"${CONTINUA_AI_API_BASE_URL}/responses/${RESPONSE_ID}" \
--header "Authorization: Bearer ${CONTINUA_AI_API_KEY}"Status values
| Status | Meaning |
|---|---|
queued | Accepted and waiting to run. |
in_progress | Currently running. |
completed | Finished successfully. The result is available. |
failed | Could not be completed. See error. |
cancelled | Cancelled before completion. |
While a response is not terminal, output_text is empty and output is normally empty. The server returns a Retry-After header; wait at least that many seconds before retrieving the response again.
Completed response
{
"id": "resp_1234567890abcdef",
"object": "response",
"created_at": 1789344000,
"status": "completed",
"previous_response_id": null,
"background": true,
"output_text": "The document identifies supplier concentration as its primary risk.",
"output": [
{
"id": "msg_1234567890abcdef",
"type": "message",
"role": "assistant",
"status": "completed",
"content": [
{
"type": "output_text",
"text": "The document identifies supplier concentration as its primary risk.",
"annotations": [],
"logprobs": []
}
]
}
],
"report": {
"findings": [
{
"finding_index": 1,
"record_id": "record_1234567890abcdef",
"claim": "The company relies on one supplier for most critical components.",
"quote": "Our primary supplier provides most critical components.",
"document": "report.pdf",
"position": "p.12",
"relevance": 0.97,
"confidence": 0.94,
"evidence": 0.91,
"materiality": 0.89
}
],
"billing": {
"billing": "charged",
"charged": 0.42,
"metered": 0.42,
"llm_cost": 0.31,
"platform_fee": 0.11
}
},
"error": null
}Response fields
| Field | Type | Description |
|---|---|---|
id | String | Response identifier. |
object | String | Always response. |
created_at | Integer | Creation time as a Unix timestamp in seconds. |
model | String | Model used for the response. |
status | String | Current response status. |
previous_response_id | String or null | Parent response identifier for a follow-up. |
background | Boolean | Always true. |
output_text | String | Complete generated answer when status is completed. |
output | Array | Generated messages in Responses API format. |
report | Object | Findings and billing details, available after completion. |
error | Object or null | Failure details when status is failed. |
Finding fields
| Field | Type | Description |
|---|---|---|
finding_index | Integer | One-based finding number. |
record_id | String | Finding record identifier. |
claim | String | Finding or conclusion supported by the source. |
quote | String | Supporting source text, when available. |
document | String | Source document or source URL, when available. |
position | String | Page or paragraph location, when available. |
relevance | Number | Relevance score from 0 to 1, when available. |
confidence | Number | Confidence score from 0 to 1, when available. |
evidence | Number | Evidence strength score from 0 to 1, when available. |
materiality | Number | Materiality score from 0 to 1, when available. |
Continue a response
To ask a follow-up question, create another response and set previous_response_id to the latest completed response identifier. Every follow-up creates a new response identifier. The parent response must be completed; a parent that is still queued or in_progress returns HTTP 409 with code response_not_complete.
Python
import time
from uuid import uuid4
follow_up = client.responses.create(
model="deepseek-flash",
background=True,
previous_response_id=response.id,
input="Give me one concise recommendation based on the findings.",
extra_headers={"Idempotency-Key": f"follow-up-{uuid4().hex}"},
)
while follow_up.status not in {"completed", "failed", "cancelled"}:
time.sleep(5)
follow_up = client.responses.retrieve(follow_up.id)
if follow_up.status != "completed":
raise RuntimeError(f"Follow-up ended with status={follow_up.status}")
print(follow_up.output_text)Node.js
import { randomUUID } from "node:crypto";
let followUp = await client.responses.create(
{
model: "deepseek-flash",
background: true,
previous_response_id: response.id,
input: "Give me one concise recommendation based on the findings.",
},
{ headers: { "Idempotency-Key": `follow-up-${randomUUID()}` } },
);
while (!["completed", "failed", "cancelled"].includes(followUp.status)) {
await new Promise((resolve) => setTimeout(resolve, 5000));
followUp = await client.responses.retrieve(followUp.id);
}
if (followUp.status !== "completed") {
throw new Error(`Follow-up ended with status=${followUp.status}`);
}
console.log(followUp.output_text);A follow-up may contain text only; you do not need to upload the original files again. Use the newest completed response identifier for each subsequent follow-up.
List responses
Returns responses for the authenticated account, newest first.
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | Integer | No | Number of responses to return, from 1 to 25. Defaults to 25. |
status | String | No | Comma-separated status values to include. |
cursor | String | No | Opaque cursor returned by the previous page. |
curl \
"${CONTINUA_AI_API_BASE_URL}/responses?limit=10&status=completed,failed" \
--header "Authorization: Bearer ${CONTINUA_AI_API_KEY}"{
"responses": [
{
"id": "resp_1234567890abcdef",
"object": "response",
"status": "completed",
"created_at": 1789344000,
"last_updated_at": 1789344060
}
],
"next_cursor": "eyJzY2hlbWEiOiIuLi4ifQ"
}When next_cursor is not null, pass it unchanged in the next request and keep limit and status unchanged while following a cursor.
Errors
Errors use a consistent JSON envelope.
{
"error": {
"message": "The response is not complete.",
"type": "invalid_request_error",
"param": "previous_response_id",
"code": "response_not_complete"
}
}Every response includes an x-request-id header. Include this value when contacting support about a failed request.
Common errors
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_model | The requested model identifier is not supported. |
| 400 | missing_file_source | A first response did not include a file or project. |
| 400 | insufficient_credits | The account does not have enough credits to create the response. |
| 400 | unsupported_parameter | The request uses an unsupported option, such as streaming. |
| 401 | invalid_api_key | The bearer token is missing, invalid, disabled, or rotated. |
| 403 | access_denied | The authenticated account cannot access the requested resource. |
| 404 | file_not_found | The file does not exist or belongs to another account. |
| 404 | project_not_found | The project does not exist or belongs to another account. |
| 404 | response_not_found | The response does not exist, is no longer available, or belongs to another account. |
| 409 | file_already_used | The uploaded file was already used in another response request. |
| 409 | response_not_complete | A follow-up references a response that is not complete. |
| 409 | idempotency_key_reused | An idempotency key was reused with different parameters. |
| 410 | file_expired | The uploaded file has expired. |
| 413 | file_too_large | The file exceeds the account's current size limit. |
| 415 | unsupported_file_type | The file extension is not supported. |
| 429 | concurrency_limit_exceeded | The account's concurrent response limit has been reached. |
| 500 | server_error | The request could not be completed because of a temporary service error. |
Retry guidance
- Poll response status no more frequently than directed by the
Retry-Afterheader. When the header is unavailable, wait at least five seconds. - For HTTP
429, wait for the duration specified byRetry-Afterbefore retrying. - For temporary HTTP
500errors, retry with exponential backoff and random jitter. - If a create request times out, retry with the same
Idempotency-Keyand the exact same request body. - Do not retry other
400,401,403,404,409,410, or415errors without correcting the request or application state.
Integration checklist
- Keep the API token on your server.
- Use the
/api/v1base URL exactly once. - Use a full model identifier, or
auto. - Include a unique idempotency key for every new response.
- Save every returned response identifier.
- Poll until a terminal status is reached and honor
Retry-After. - Handle
failedandcancelledresponses explicitly. - Upload a file again if its identifier has expired or has already been used.
- Use the latest completed response identifier for follow-up questions.
Start building with Continua AI
Create an account to generate an API token, or read the Help Center for guidance on criteria, guardrails, and Projects.