Server-to-server
Call /v2/chat directly with an API key from your own backend.
For when you don't need the widget — your backend sends messages on behalf
of its own end-users directly, e.g. to plug into an internal chat system, a
Slack bot, or an automated ticket pipeline. No token exchange needed: call
POST /v2/chat directly with an API key (Console → API keys & Members
→ New API key — shown exactly once, at creation).
An API key never reaches the browser
An API key is a secret that grants access to the whole chat endpoint for your organization. Call it only from your backend — never from browser-side JavaScript, never committed into frontend code.
Request
POST https://api.harnix.vn/v2/chat
Content-Type: application/json
X-API-Key: sk_xxxxxxxxxxxxxxxx
Idempotency-Key: 2f6a... (recommended, see below){
"agent_id": "b6b0f9b2-...",
"content": "What's our refund policy?",
"conversation_id": null,
"end_user_id": "user_42"
}agent_id— required, must be an active agent.content— required, 1–32,000 characters.conversation_id— leave it out to start a new conversation; pass one in to continue an existing conversation (must matchagent_id, otherwise you getvalidation_error).end_user_id— given → the conversation belongs to that end-user (upserts anidentifiedend-user with this value asexternal_id); omitted → the conversation belongs to aserviceend-user that stands in for the API key itself — every request that skipsend_user_idshares this one "person".
Response
202 Accepted — there's no answer yet, the run is still going:
{
"run_id": "0e2b...",
"message_id": "9ac1...",
"conversation_id": "77fd..."
}Get the answer by streaming this run, or by
reading the conversation back with
GET /v2/conversations/{id} once the run
finishes.
Idempotency
Send an Idempotency-Key header (255 characters max) so a retry after a
network timeout is safe — the same key on the same conversation + end-user
returns the message already created, never a second one. Details on
Idempotency & cancel.
Confirming an action
When the agent wants to call a connector tool that requires confirmation,
the run pauses instead of answering: its stream
emits an approval frame, and
GET /v2/conversations/{id} shows the same
request under pending_approval. Your backend decides with the same API
key:
POST https://api.harnix.vn/v2/runs/{run_id}/approval
X-API-Key: sk_xxxxxxxxxxxxxxxx
Content-Type: application/json
{ "outcome": "approved" }outcome is "approved" or "denied"; 202 returns the run, which
resumes right away. Over an API key there is no remember — sending it
returns 422 validation_error. While a conversation has a run waiting,
POST /v2/chat with that conversation_id returns 409 approval_pending;
decide first, then send. The rules in full are on
Idempotency & cancel.
Answering questions (ask_user)
When the agent calls the built-in ask_user tool to ask the
user questions, the run pauses in waiting_input instead of answering: its
stream emits a question frame, and
GET /v2/conversations/{id} reports the
questions under pending_question. Your backend can reply in two ways:
- Call
POST /v2/runs/{run_id}/answerwith an API key:
POST https://api.harnix.vn/v2/runs/{run_id}/answer
X-API-Key: sk_xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"answers": [
{ "selected": ["District 3"], "other": null }
]
}Returns 202 Accepted with run details, and the run resumes immediately on
the stream.
- Send a follow-up message via POST /v2/chat:
When a conversation has a run waiting for questions (waiting_input),
calling POST /v2/chat with that conversation_id is treated as the user's
free-text reply. The system returns 202 Accepted with the same waiting
run (no new run created, not blocked by concurrent run limits), appends the
message to the conversation, and resumes processing the run. Retrying with the
same Idempotency-Key returns the same run without duplicate messages. The
rules in full are on
Idempotency & cancel.
Rate limits & budgets
Each API key has its own rate ceiling (300 requests/minute by default,
adjustable by the operator) — exceeding it returns rate_limited with a
retry_after. An end-user (including the service end-user) can have at
most 3 runs in flight at once — exceeding that returns rate_limited with
scope: "active_runs". If the organization or agent has a daily token
budget, running out returns budget_exceeded — see
Limits.
Next
- Streaming — follow a run in real time.
- Conversations — list and read history back.
- Errors — the error shape and every error code.
- Runs & traces — an older run's step-by-step detail is removed per your organization's retention setting; messages and usage figures are not.