API quickstart

Build company search and chat into your own app. Your server proves who the person is, gets a short chat session, and your app uses that session to ask questions.

How access works

  1. Your server keeps an API key: a Client ID and a secret.
  2. For each signed-in person, your server signs a single-use assertion with the secret.
  3. It exchanges the assertion for a short-lived chat session.
  4. 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

  1. In the web app, open Settings, then API keys, and select Create API key.
  2. Enter a client name, such as “Mobile app backend”, and choose an agent. You can’t change the agent later.
  3. 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.

Sign a client assertion 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);
}
ClaimValue
issYour Client ID.
appYour Client ID again.
audAlways ask:chat_session_exchange.
purposeAlways chat_session_exchange.
client_idA name for the app making the request, such as web or ios. Send the same value as client.id when you start the session.
orgYour organization ID.
subYour stable ID for the signed-in person.
emailOptional. The person’s email.
nameOptional. The person’s display name.
rolesOptional. Up to 32 role names.
employeeOptional. The person’s employee id and email.
jtiA unique ID. Each assertion works once.
iat, expIssue 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.

POST /v1/chat/clients/{clientId}/sessions curl
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" }
  }'
Response JSON
{
  "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.

POST /v1/chat curl
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" }
  }'
Example stream SSE
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.format to markdown for text and citations only, or orbit-ui for 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-id response header holds the conversation’s thread ID.
  • To stop an answer, cancel the request.

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.

POST /v1/orgs/{orgId}/agents/{agentId}/search curl
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.

StatusMeaning
401The assertion or session is missing, invalid, or expired.
403The person can’t use this agent or source.
409The assertion was already used, or the thread is busy.
429Rate limit reached. Check retryAfterMs when present.
503The service is not available. Try again later.