S
supero.docs
Developers/API Reference/AI Agent API

AI Agent API

Talk to your domain's data in natural language: the AI Data Agent turns your uploaded schemas into callable tools, runs a reasoning loop over them, and returns structured results — as a single response or a live SSE stream.

Overview

The AI Agent API lets your application (or your own users, through your UI) chat with an agent that has authenticated access to your domain's data. The agent automatically gets a tool for every schema you have uploaded — search, get, create, update, delete — decides which ones to call based on the message it receives, executes them under your caller's JWT, and returns a natural-language response plus a structured record of what it did. Everything runs at https://api.supero.dev/ai/v1/*. There are four endpoint groups: Chat (send a message, get a response), Tools (list and directly invoke the generated tools without going through the agent at all), Sessions (manage multi-turn conversation history), and Confirmations (approve or reject a write the agent has paused on).
bash
curl -X POST https://api.supero.dev/ai/v1/chat \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Show me all customers with tier '\''enterprise'\''",
    "mode": "auto"
  }'

⚠️ Bearer JWT only

Unlike the CRUD data API, the AI Agent API does not accept an X-API-Key header. Every route requires Authorization: Bearer <jwt>, obtained from POST /api/v1/auth/login — see Authentication. A request with a missing or malformed Authorization header is rejected before anything else runs.

ℹ️ The agent carries your caller's authority and adds none of its own

Every tool the agent runs is executed with the JWT you sent, on the same access-controlled paths your other API calls use: tenancy scope, object-level access policy, and hidden or read-only field rules all apply unchanged. It holds no credential of its own, so there is no mode in which it can read or write more than the token you supplied already permits. See AI Data Agent for the full model.

Chat: POST /ai/v1/chat

Send a message and get the agent's response. The agent has access to your domain's schema-generated tools and performs CRUD on your behalf, authorized by your JWT — it can only see and change what your caller's role already permits.
FieldTypeRequiredNotes
messagestringyes1–10,000 characters
session_idstringnoContinues an existing conversation. A new session is generated if omitted.
providerstringnoAdvanced: pin this turn to a specific upstream AI provider. Omit it to use your domain's default.
max_iterationsintegerno1–10, default 5. Caps how many tool-call rounds the agent can take in one turn before it must respond.
streambooleannoDefault false. Set true for a Server-Sent Events response instead of a single JSON body — see Streaming below.
modestringnoauto (default) | single | loop — see Routing modes below.

Routing modes

modeBehaviorWrite confirmations
autoDefault. Uses the multi-step agentic loop when it is available in your deployment, falling back to a single-shot pass if it is not. The fallback is silent.Yes, when the loop runs the turn
singleAlways uses a single reasoning pass: the agent can still call tools, but does not plan across multiple turns.No — tools execute as soon as the model calls them
loopForces the multi-step agentic loop. Returns 503 if the loop engine is not available rather than silently falling back.Yes, always

⚠️ mode: single executes writes without an approval step

Confirmation gates are a property of the agentic-loop path. In mode single there is no gate: an update_{type} or delete_{type} the model decides to call runs immediately. Because mode auto falls back to the single-pass path when the loop engine is unavailable, an integration that depends on gates should send mode: loop, which fails with a 503 instead of degrading quietly.

Non-streaming response

This is the shape returned on the default path (mode auto routed to the loop, or mode loop):
json
{
  "response": "I found 3 enterprise customers:\n\n1. Acme Corp (UUID: abc-123)\n2. TechGiant Inc (UUID: def-456)\n3. Global Systems (UUID: ghi-789)",
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "tool_calls": [
    {
      "name": "search_customer",
      "arguments": { "filters": { "tier": "enterprise" } },
      "result": { "success": true, "count": 3 }
    }
  ],
  "usage": { "input_tokens": 612, "output_tokens": 84, "total_tokens": 696 },
  "error": null,
  "correlation_id": "a1b2c3d4"
}
FieldMeaning
responseThe agent's reply text. If the turn paused for approval before taking an action, this instead describes what it wants to do and names the gate_id to confirm with.
session_idThe conversation this turn belongs to — pass it back on the next call to continue.
tool_callsArray of { name, arguments, result } for every tool the agent invoked this turn, or null if it answered without calling any.
usageToken counts for this turn's AI calls: input_tokens, output_tokens, total_tokens. May be null.
filesPresent for tools that produce downloadable output (e.g. an export or generated document).
errorPopulated when something went wrong; response still carries a human-readable explanation.
correlation_idTrace id for this request — include it when contacting support about a specific call.

ℹ️ What differs in mode: single

A single-pass turn returns the same envelope but populates a different subset: it additionally echoes domain and project from your JWT, and it does not set usage. correlation_id is set on the error path. The response model also declares project_name, tenant_uuid, tenant_name and username; no path populates them, so treat them as absent rather than building on them. Write your client to read fields it needs and tolerate nulls, not to assume a fixed set.

Streaming responses

Set stream: true to get a Server-Sent Events response instead of waiting for the full turn to complete. Each event is a line of the form data: <json>\n\n; parse the JSON and switch on its type field.
bash
curl -N -X POST https://api.supero.dev/ai/v1/chat \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "message": "Create a task called Follow up with Acme",
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "stream": true
  }'
event typeShapeMeaning
content{ type, text }A chunk of the agent's reply text. Concatenate chunks in order to build the full response.
tool_call{ type, name, arguments }The agent is invoking a tool.
tool_result{ type, name, result }That tool finished; result carries success, and, when relevant, count / items / error.
file{ type, filename, mime_type, file_data, size }A downloadable artifact (export, generated document) is ready.
error{ type, message }Something went wrong. The stream ends after this event.
done{ type, session_id }The turn is complete — no further events follow. Store session_id for the next message.
text
data: {"type": "content", "text": "I'll create that task now."}

data: {"type": "tool_call", "name": "create_task", "arguments": {"title": "Follow up with Acme"}}

data: {"type": "tool_result", "name": "create_task", "result": {"success": true, "count": 1, "items": [{"uuid": "9c1f2a3b-..."}], "error": null}}

data: {"type": "content", "text": "Done — I created the task."}

data: {"type": "done", "session_id": "550e8400-e29b-41d4-a716-446655440000"}

💡 Mapping events to UI callbacks

If you are driving a chat UI, map each event's type straight to a handler: content appends text, tool_call/tool_result drive a "working on it" indicator, file offers a download, error surfaces a toast, and done closes out the turn and stores the returned session_id for the next message. A turn that pauses for approval emits a confirmation event instead of done — see Confirmation gates below.

Filter grammar

The agent does not write SQL. It answers a question by choosing one of your generated tools and emitting a structured filter as that tool's arguments, using the grammar below. The same grammar is what you pass yourself when you invoke a tool directly, which makes this a deterministic query API as well as the agent's internal language.
A filters value is either a flat map of field to value (plain equality), or a map of field to a single-operator object:
json
// 1) Flat equality
{ "country": "IN", "tier": "enterprise" }

// 2) Operator form
{ "amount": { "gt": 120 } }
OperatorArgumentMatches
eq / nea valueEqual / not equal.
gt / gte / lt / ltea valueOrdered comparison, for numbers and ISO-8601 dates.
in / not_inan arrayValue is (or is not) one of the listed values.
betweena two-element arrayInclusive range: ["2026-02-01", "2026-02-28"].
containsa stringSubstring match.
starts_with / ends_witha stringPrefix / suffix match.
regexa pattern stringRegular-expression match.
existstrue or falseThe field is set / is not set.

Combining conditions

json
{
  "op": "and",
  "conditions": [
    { "country": "IN" },
    { "created_at": { "gte": "2026-01-01" } },
    { "status": { "in": ["SCHEDULED", "PENDING"] } }
  ]
}

Sorting and paging

Alongside filters, every search_{type} tool accepts:
ArgumentTypeNotes
sortarray[{ "field": "<name>", "order": "asc" | "desc" }] — applied in order.
limitintegerDefault 50, maximum 1000. A larger value is clamped, not rejected.
offsetintegerDefault 0. Page through results beyond the limit.
json
{
  "filters": {
    "op": "and",
    "conditions": [
      { "visit_date": { "between": ["2026-02-01", "2026-02-28"] } },
      { "amount": { "gt": 120 } }
    ]
  },
  "sort": [{ "field": "visit_date", "order": "desc" }],
  "limit": 50,
  "offset": 0
}

ℹ️ Conventions

Dates and datetimes are ISO-8601. String comparisons are case-insensitive. The tool description handed to the model lists each entity's filterable fields with their types, and for enum fields the exact set of allowed values, so the agent filters on values that exist rather than inventing them — you can see the same information for yourself in GET /ai/v1/tools.

⚠️ filters is for record selection only

Grouping and aggregation keys (group_by, match, metrics, pipeline, aggregations, field, fields) are not part of this grammar and are rejected if passed to a search_{type} tool, with an error naming the tool you should use instead. Aggregate questions belong to the analytics tools — see Tools API below.

Confirmation gates in loop mode

When a turn runs through the agentic loop — mode loop, or mode auto where the loop is available — the agent pauses before an update_{type} or delete_{type} and waits for your explicit approval instead of executing. create_{type} is not gated and runs immediately. In mode single there are no gates at all; see Routing modes above.

How the pending gate reaches you

TransportWhere the gate appearsHuman-readable text field
SSE streamA confirmation event carrying gate_id, tool_name, tool_arguments.prompt_message
Non-streaming chatHTTP 200. response describes the pending action and names the gate_id; there is no separate status field on this body.— (inside response)
GET /ai/v1/loop/session/{id}status is waiting_confirmation and pending_confirmation holds gate_id, tool_name, tool_arguments, affected_records, expires_at.prompt

⚠️ One field, two names

The gate's human-readable description is called prompt_message on the SSE event and prompt on the REST session object. They are the same text. Read whichever your transport gives you rather than assuming one name.

Resolving a gate

bash
curl -X POST https://api.supero.dev/ai/v1/loop/confirm \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "gate_id": "gate_9c1f2a3b",
    "approved": true
  }'
FieldTypeRequiredNotes
session_idstringyesThe session holding the pending gate.
gate_idstringyesFrom the confirmation event or pending_confirmation.
approvedbooleanyesfalse rejects the pending call and resumes the session without executing it.
modified_argumentsobjectnoApprove with edits: run the pending tool with these arguments instead of the ones the model proposed.
user_messagestringnoOptional note carried into the resumed turn — useful for "yes, but only the ones from March".
The response is the resumed turn: session_id, status, response, turn_number, iterations_used, tool_calls, and confirmation (populated only if the resumed turn immediately paused on another gate).

Loop session routes

MethodPathPurpose
GET/ai/v1/loop/session/{session_id}Session status, turn count, metrics, and any pending_confirmation.
POST/ai/v1/loop/session/{session_id}/closeClose the session and drop any pending gate and working context. Idempotent — safe to call more than once.

ℹ️ Gates expire after five minutes

A pending confirmation is valid for five minutes from the moment the turn pauses. After that, confirming it no longer resumes the turn — the user asks again. Treat an expired gate_id as a normal outcome, not a transient error to retry, and do not build a workflow that leaves a gate unanswered across sessions.

Tools API

Every schema in your domain automatically gets 5 tools: search_{schema}, get_{schema}, create_{schema}, update_{schema}, and delete_{schema}. The Tools API lets you list them and, when you want to skip the reasoning step entirely, invoke one directly.

The filter grammar

A search tool takes the platform filter grammar directly, which is what makes tool invocation usable as a deterministic query API rather than only as something the model drives. The same operators are available to you as to the agent.
OperatorMeaning
eq / neEqual, not equal.
gt / gte / lt / lteNumeric and date comparison.
in / not_inMembership in a list of values.
betweenInclusive range — takes two values.
contains / starts_with / ends_withSubstring matching on a string field.
regexPattern match on a string field.
existsWhether the field is present at all.
json
{
  "filters": {
    "op": "and",
    "conditions": [
      { "field": "status",     "op": "in",  "value": ["open", "in_progress"] },
      { "field": "created_at", "op": "gte", "value": "2026-08-01" }
    ]
  },
  "sort": [{ "field": "created_at", "order": "desc" }],
  "limit": 50,
  "offset": 0
}

ℹ️ limit defaults to 50 and caps at 1000

Paginate with offset. An enum field carries its allowed values into the tool description, so a caller — model or human — can see the valid set without a second lookup.
MethodPathPurpose
GET/ai/v1/toolsList every schema-generated tool available for your domain, with its parameter schema.
POST/ai/v1/tools/refreshClear the cached tool list and regenerate it. Call this after uploading or changing schemas.
POST/ai/v1/tools/{tool_name}/invokeRun one tool directly with explicit arguments — deterministic, no AI reasoning step involved.
The tools you get back are the tools generated for the schemas your caller can see. Platform-owned types (projects, tenants, user accounts, API keys, audit logs, connectors, the schema registry) are never included, and a schema declared read-only yields only its search and get tools.
bash
curl https://api.supero.dev/ai/v1/tools \
  -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "domain": "acme",
  "tools": [
    {
      "name": "search_customer",
      "description": "Search for customer records. Filters support comparison/range/set operators.",
      "parameters": {
        "type": "object",
        "properties": {
          "filters": { "type": "object" },
          "sort": { "type": "array" },
          "limit": { "type": "integer", "default": 50 },
          "offset": { "type": "integer", "default": 0 }
        }
      },
      "schema_name": "customer",
      "operation": "search",
      "namespace": "billing",
      "obj_type": "customer"
    }
  ],
  "count": 1
}
FieldMeaning
nameThe tool name the agent calls and the one you put in the invoke path. Named {operation}_{namespace}_{type}, or {operation}_{type} in the default namespace.
descriptionThe text handed to the model, including the filter grammar and this entity's filterable fields with their types and enum values.
parametersJSON Schema for the arguments. For search, that is filters, sort, limit (default 50, max 1000) and offset.
schema_name / obj_typeThe schema this tool was generated from, and the normalized object type it operates on.
namespacePresent when the schema is not in the default namespace.
parent_typePresent when the entity is a child type that must be created under a parent.

ℹ️ The agent has tools this listing does not show

GET /ai/v1/tools returns the schema-generated CRUD tools only. The agent additionally carries a fixed set of read-only analytics tools (db_stats, db_count_by, db_distinct, db_aggregate, aggregate_data, calculate_metrics), export tools (export_to_excel, export_to_csv, generate_report) and render_chart. Those are available to it on every turn, appear in tool_calls when used, but are not listed by this endpoint and are not invocable through /invoke.

Invoking a tool directly

Use this when you already know exactly which operation you want and just want the deterministic result — no message, no reasoning, no tool-selection step. Access control is identical: the call runs under your JWT, on the same paths.
bash
curl -X POST https://api.supero.dev/ai/v1/tools/search_customer/invoke \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "arguments": {
      "filters": { "tier": "enterprise", "created_at": { "gte": "2026-01-01" } },
      "sort": [{ "field": "created_at", "order": "desc" }],
      "limit": 50
    }
  }'
json
{
  "success": true,
  "result": {
    "operation": "search",
    "schema": "customer",
    "items": [ { "uuid": "abc-123", "name": "Acme Corp", "tier": "enterprise" } ],
    "count": 1,
    "total": 1
  },
  "tool_name": "search_customer",
  "error": null
}

ℹ️ A tool_name that doesn't exist

POST /ai/v1/tools/{tool_name}/invoke returns 404 if tool_name isn't in your domain's current tool list. If you just uploaded or changed a schema, call POST /ai/v1/tools/refresh first.

Sessions API

A session holds a conversation's message history so the agent has context across turns. Sessions are created automatically the first time you chat with a new session_id, but you can also manage them explicitly. Sessions are isolated by domain, project, tenant, and user — one person's conversation is never visible to another user, let alone another tenant. Server-side history is retained for 24 hours.
MethodPathPurpose
GET/ai/v1/sessionsList active sessions in your domain/project/tenant scope.
POST/ai/v1/sessionsExplicitly create a new session before the first message.
GET/ai/v1/sessions/{id}Get session metadata plus its full message history.
DELETE/ai/v1/sessions/{id}Delete a session and its stored history.
bash
# Create a session
curl -X POST https://api.supero.dev/ai/v1/sessions \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Get its history
curl https://api.supero.dev/ai/v1/sessions/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Delete it
curl -X DELETE https://api.supero.dev/ai/v1/sessions/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer $ACCESS_TOKEN"
json
{
  "session_id": "550e8400-e29b-41d4-a716-446655440000",
  "domain": "acme",
  "project": "proj-uuid-123",
  "project_name": "sales",
  "tenant_uuid": "tenant-uuid-456",
  "tenant_name": "northwind",
  "messages": [
    { "role": "user", "content": "Show me enterprise customers", "timestamp": "2026-07-18T10:00:00Z" },
    { "role": "assistant", "content": "I found 3 enterprise customers...", "timestamp": "2026-07-18T10:00:02Z", "tool_calls": [ { "name": "search_customer", "arguments": { "filters": { "tier": "enterprise" } } } ] }
  ],
  "message_count": 2,
  "created_at": "2026-07-18T10:00:00Z",
  "updated_at": "2026-07-18T10:00:02Z"
}
message.role is one of user, assistant, system, or tool. Messages with an unparseable role or missing content are coerced rather than dropping the whole history read, so a slightly malformed stored message never turns a history fetch into a 500.

Errors and status codes

Errors are classified before they reach you: the AI Agent API distinguishes an upstream AI provider hiccup from your own authentication problem, and summarises a provider failure as a short retry message rather than passing its text through. For the codes shared with the rest of the platform, see Errors & Status Codes and Rate Limits.
StatusMeaning
400Bad request — e.g. your JWT doesn't carry a resolvable domain.
401Missing, malformed, or expired Authorization: Bearer token.
402Your AI agent usage allowance is exhausted for this period, where usage enforcement is enabled for your account — see Plans & Billing.
404The tool name or session id in the path doesn't exist.
422Request body failed validation (e.g. message over 10,000 characters, max_iterations out of range).
503The agent, the loop engine, or the tool executor backing this endpoint isn't available right now — retry shortly. mode: loop returns this rather than falling back to a single pass.
Most errors share a two-field-plus-details envelope:
json
{
  "error": "authentication_error",
  "message": "Authorization header with Bearer token required",
  "details": {}
}
A 402 carries a structured detail block your UI can use to prompt an upgrade rather than just showing a generic failure:
json
{
  "detail": {
    "error": "quota_exceeded",
    "upgrade_required": true,
    "meter": "agent.prompt",
    "limit": 10,
    "used": 10,
    "remaining": 0,
    "message": "10 free AI queries this month used — add the AI Agent add-on or upgrade your plan."
  }
}
meter names which allowance was exceeded — agent.prompt for chat messages. limit, used and remaining describe that allowance for the current period; read them from the response rather than hard-coding a number, since allowances differ by plan and change over time. message is safe to display as-is.

⚠️ Do not render an unexpected error string to end users

Provider failures, permission errors and timeouts are mapped to written explanations, but an error the classifier does not recognise falls through to a generic form that includes the underlying exception text. Log that text with the correlation_id, show your own message to the user, and treat the error field as diagnostic rather than as UI copy.

🚨 Non-streaming errors don't always come back as an HTTP error

For POST /ai/v1/chat specifically, a failure partway through a turn (e.g. an upstream hiccup, a permission error surfaced mid-tool-call) is often returned as HTTP 200 with error populated on the response body and a human-readable explanation in response, rather than as a 4xx/5xx — the request itself succeeded even though the agent's turn didn't. Check the error field, not just the status code.

Next steps

  • •AI Data Agent — the builder-facing guide: what the agent can do, how it turns a question into a filter, and how access control bounds it
  • •API Overview & Conventions — base URL, auth schemes, and the response envelope shared across the whole platform
  • •CRUD Endpoints — the underlying data endpoints the agent's generated tools call on your behalf
  • •Querying & Pagination — the same filtering concepts applied directly to the data API
  • •Schema Reference — how fields, references, and parent_type shape the tools this API generates for you