How access works
- Your server keeps an API key: a Client ID and a secret.
- For each signed-in person, your server signs a single-use assertion with the secret.
- It exchanges the assertion for a short-lived chat session.
- Your app sends the session as a bearer token. The secret never leaves your server.
All requests go to https://api.askneutron.com. Answers follow the same access rules as the rest of askneutron.
Create an API key
- In the web app, open Settings, then API keys, and select Create API key.
- Enter a client name, such as “Mobile app backend”, and choose an agent. You can’t change the agent later.
- Copy the Client ID (
neutron_client_…) and the Secret key (neutron_sk_…). The secret is shown once, so store it in your server’s secret manager.
Only organization admins can create, rotate, or revoke API keys. Each key belongs to one organization and one agent. When you rotate a key, the new secret appears once and existing secrets keep working for 24 hours. A revoked key stops working right away.
Sign an assertion
On your server, sign a JWT with HS256 using the secret. This example uses the jose library for Node.js.
import { SignJWT } from "jose";
const clientId = process.env.NEUTRON_CLIENT_ID; // neutron_client_…
const secret = new TextEncoder().encode(process.env.NEUTRON_CLIENT_SECRET); // neutron_sk_…
export async function createAssertion(user) {
return await new SignJWT({
purpose: "chat_session_exchange",
app: clientId,
client_id: "web",
org: process.env.NEUTRON_ORG_ID,
email: user.email,
name: user.name,
})
.setProtectedHeader({ alg: "HS256" })
.setIssuer(clientId)
.setAudience("ask:chat_session_exchange")
.setSubject(user.id)
.setJti(crypto.randomUUID())
.setIssuedAt()
.setExpirationTime("60s")
.sign(secret);
} | Claim | Value |
|---|---|
iss | Your Client ID. |
app | Your Client ID again. |
aud | Always ask:chat_session_exchange. |
purpose | Always chat_session_exchange. |
client_id | A name for the app making the request, such as web or ios. Send the same value as client.id when you start the session. |
org | Your organization ID. |
sub | Your stable ID for the signed-in person. |
email | Optional. The person’s email. |
name | Optional. The person’s display name. |
roles | Optional. Up to 32 role names. |
employee | Optional. The person’s employee id and email. |
jti | A unique ID. Each assertion works once. |
iat, exp | Issue and expiry times. The assertion can live for at most 60 seconds. |
The assertion may contain only these claims. For your organization ID, contact support.
Start a chat session
Exchange the assertion for a chat session. client.platform is one of web, ios, android, macos, windows, linux, or other.
curl https://api.askneutron.com/v1/chat/clients/$NEUTRON_CLIENT_ID/sessions \
-H "Authorization: Bearer $ASSERTION" \
-H "Content-Type: application/json" \
-d '{
"client": { "id": "web", "platform": "web", "version": "1.0.0" },
"session": { "locale": "en-IN", "timezone": "Asia/Kolkata" }
}' {
"token": "eyJhbGciOiJIUzI1NiIs…",
"expiresAt": "2026-10-10T09:45:00.000Z",
"context": {
"org": { "id": "…" },
"agent": { "id": "…" },
…
}
} Send token to your app. When it passes expiresAt, sign a new assertion and start a new session.
Send a message
Send the conversation as AI SDK UI messages. The answer streams back as server-sent events.
curl -N https://api.askneutron.com/v1/chat \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"parts": [{ "type": "text", "text": "What is our refund policy?" }]
}
],
"presentation": { "format": "markdown" }
}' data: {"type":"start","messageId":"…"}
data: {"type":"text-start","id":"…"}
data: {"type":"text-delta","id":"…","delta":"Refunds are available within "}
data: {"type":"source-url","sourceId":"…","url":"https://…","title":"Refund policy"}
data: {"type":"text-end","id":"…"}
data: {"type":"finish","finishReason":"stop"} - Set
presentation.formattomarkdownfor text and citations only, ororbit-uifor the richer parts the widget renders. - Handle these events:
start,text-start,text-delta,text-end,source-url,finish,error,abort. Ignore event types you don’t know. - The
x-chat-thread-idresponse header holds the conversation’s thread ID. - To stop an answer, cancel the request.
Search knowledge
Search returns matching passages without writing an answer. Use the same session token, with the organization and agent IDs from the session response’s context.
curl https://api.askneutron.com/v1/orgs/$ORG_ID/agents/$AGENT_ID/search \
-H "Authorization: Bearer $SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "query": "refund policy", "maxResults": 5 }' The body takes query, and optionally maxResults (default 10, at most 100) and scoreThreshold (0 to 1, default 0.3). The response has results and latencyMs, and only includes sources the person can access.
Errors
Errors return JSON with a stable code, such as chat_session_expired, and a displayCode, such as ERR_CHAT_SESSION_EXPIRED. Each also has a message and guidance you can show, a suggested action, and retryable.
| Status | Meaning |
|---|---|
| 401 | The assertion or session is missing, invalid, or expired. |
| 403 | The person can’t use this agent or source. |
| 409 | The assertion was already used, or the thread is busy. |
| 429 | Rate limit reached. Check retryAfterMs when present. |
| 503 | The service is not available. Try again later. |