AAgentGround

AgentGround API · v4.2

Ship an agent integration without guessing.

Create and test agents in the studio, publish an immutable version, then call it from your server with an agent-scoped key. Your application sends messages over HTTP and receives replies inline, over SSE, or through signed webhook events.

Keep keys server-side. Never place account tokens, provider credentials, or agent API keys in browser or mobile client code.

Five-minute quickstart

Call your published agent

  1. 1
    Release a tested draft

    In the studio, complete Setup, add trusted knowledge, pass evaluations, and publish to staging. The first release initializes production; later versions need owner promotion.

  2. 2
    Create an agent API key

    Open Release & API, create a key, and copy it immediately. Only its hash is stored.

  3. 3
    Send the first message

    Use a unique idempotency key for each logical request. Save the returned conversationId.

curl -X POST "https://YOUR_HOST/v1/agents/AGENT_ID/responses" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: visitor-42-message-1" \
  -d '{"input":"Which plan is right for a small team?","externalUserId":"visitor-42"}'

For later turns, send the returned conversationId with only the newest user message. Production conversations stay pinned to the version on which they began.

Embed in another app

Send and receive messages from your own product

One agent API key is everything an external application needs. Discover the agent instead of hard-coding identifiers, keep a conversationId next to your own user record, and set externalUserId so conversations, leads, and handoffs stay attributable to the right person in your system.

Describe the agent

GET /v1/agent returns the published name, opening message, conversationStyle, lead schema, released environments, and the exact endpoint URLs to call.

Open a conversation

POST /v1/conversations creates a version-pinned conversation and returns the opening message, so you can greet a user before they type. Sending a message without a conversationId also creates one.

Check who speaks first

When conversationStyle is agent_led, opening a conversation returns the agent's first message in output — render it and wait for the person to reply. See Agent-led conversations.

Attach your context

metadata accepts up to twenty flat key/value pairs echoed back on reads and events. Keep secrets and sensitive personal data out of it.

Catch up on history

GET /v1/conversations pages backwards with nextBefore; GET /v1/conversations/{id}/messages?after= pages forwards with nextAfter.

# 1. Discover the agent behind this key
curl "https://YOUR_HOST/v1/agent" -H "Authorization: Bearer gqa_YOUR_AGENT_KEY"

# 2. Open a conversation for one of your users
curl -X POST "https://YOUR_HOST/v1/conversations" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"externalUserId":"crm-user-8814","title":"Refund for order 4182","metadata":{"tenant":"acme"}}'

# 3. Send each new user message on that conversation
curl -X POST "https://YOUR_HOST/v1/agents/AGENT_ID/responses" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: helpdesk-msg-99213" \
  -d '{"input":"My refund has not arrived yet.","conversationId":"sess_…","externalUserId":"crm-user-8814"}'

The full walkthrough — a Node.js client, error table, and production checklist you can hand to another team — ships with the repository as docs/integration-guide.md.

Agent-led conversations

Let the agent open, ask, and gather

Most agents wait to be asked. An agent-led agent does the opposite: it speaks first, keeps the initiative, and works through an agenda — qualifying, promoting, and collecting information while the person simply replies. The Product promoter, Appointment booker, Feedback collector, Event registration, Renewal concierge, and Onboarding guide presets all use it.

The opener is a real turn

For agent_led, the opening message is persisted as the first assistant message of every conversation. The model sees it in history, so it never greets twice or repeats a question it already asked.

Visitor-led is unchanged

For visitor_led, the opening message stays a display-only greeting that never reaches the model, exactly as before.

Enforced at publish

An agent-led agent cannot be published without an opening message: publishing returns 400 OPENING_MESSAGE_REQUIRED.

Frozen per version

The style and the opener are captured in the immutable version, so a released conversation keeps the behaviour it started with.

# Open the conversation and render what the agent says first
curl -X POST "https://YOUR_HOST/v1/conversations" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"externalUserId":"visitor-42"}'

{
  "conversation": { "id": "sess_…", "messageCount": 1 },
  "conversationStyle": "agent_led",
  "openingMessage": "Hi! I'm here to help you find the right fit… what are you hoping to use it for?",
  "output": [{ "id": "msg_…", "role": "assistant", "content": "Hi! I'm here to help you find the right fit… " }]
}

# The person replies; you keep sending only their new message
curl -X POST "https://YOUR_HOST/v1/agents/AGENT_ID/responses" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input":"Mostly for work — I edit video.","conversationId":"sess_…"}'

If you skip POST /v1/conversations and send a message straight away, the opener is still seeded as the first turn — you just will not have had a chance to display it. For agent-led agents, open the conversation first.

Webhook events

Receive agent activity without polling

A project owner registers one allow-listed HTTPS destination per agent and subscribes it to the events the integration needs. Every delivery is signed, retried with backoff, and identified by a stable delivery ID.

conversation.started

A new API or widget conversation was created. The payload carries the conversation, including your metadata.

message.created

A visitor or assistant message was persisted. Use this instead of polling to mirror the transcript into your own product.

handoff.created

The agent asked for a human, with a factual summary and priority.

handoff.updated

A handoff moved between open, in_progress, resolved, and closed.

{
  "id": "evt_01JABC",
  "type": "message.created",
  "createdAt": "2026-08-08T10:31:02.441Z",
  "data": {
    "conversationId": "sess_…",
    "agentId": "agt_…",
    "channel": "api",
    "externalUserId": "crm-user-8814",
    "message": { "id": "msg_…", "role": "assistant", "content": "…", "citations": [] }
  }
}

Existing endpoints keep their handoff-only subscription until an owner changes it, so enabling message events is always an explicit decision. Verify AgentGround-Signature and deduplicate on AgentGround-Delivery exactly as shown under Handoffs and webhooks.

Authentication

Use the narrowest credential

Agent API key

Authorization: Bearer gqa_…

For production calls to one published agent. This is the preferred integration credential.

Widget client secret

Authorization: Bearer …

Short-lived, origin-bound, and safe to return from your backend to the widget. It cannot manage the agent.

Account token

Authorization: Bearer …

For authenticated studio and management endpoints. Treat it like a login session.

Session token

Authorization: Bearer …

For the compatibility session/chat API and its approval workflow.

Admin key

X-Admin-API-Key: …

For global administration only. Keep it isolated from product integrations.

Requests and retries

Make production calls safely

Idempotency

Send Idempotency-Key on runtime writes. Reusing the same key with the same request returns the original result.

Rate limits

Every API call has a global IP quota. Authenticated calls also return X-AccountRateLimit-* or X-AgentRateLimit-*. On 429, respect Retry-After.

Request tracing

Log the returned X-Request-ID. You may provide your own unique value to correlate application logs.

Timeouts

Set a client timeout slightly above the configured end-to-end agent deadline. A deadline failure returns 504 AGENT_DEADLINE_EXCEEDED. Retry only transient failures and preserve the idempotency key.

Streaming

Receive an ordered response lifecycle

Set stream: true to receive Server-Sent Events. Each JSON data object has a monotonic sequence. The current implementation reports lifecycle progress and emits the complete answer in response.output_text.done; it does not claim token-by-token output.

curl -N "https://YOUR_HOST/v1/agents/AGENT_ID/responses" \
  -H "Authorization: Bearer gqa_YOUR_AGENT_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: visitor-42-message-2" \
  -d '{"input":"Compare the plans","conversationId":"sess_…","stream":true}'
Event order

response.created, zero or more response.progress events, response.output_text.done, then response.completed.

Stream errors

After HTTP 200 begins, failures arrive as an error event. Treat a stream without response.completed as incomplete.

Knowledge grounding

Show which approved excerpts supported an answer

AgentGround splits saved knowledge into overlapping excerpts, ranks them against the newest visitor question, and sends only the most relevant bounded set to the model. When the answer uses a supplied marker such as [S1], the response stores and returns that exact source snapshot.

{
  "role": "assistant",
  "content": "Returns are accepted within 30 days [S1].",
  "citations": [{
    "marker": "S1",
    "sourceId": "knw_…",
    "title": "Returns policy",
    "excerpt": "Unused purchases may be returned within 30 days."
  }]
}
JSON and SSE

Each output message has a citations array. Streaming also includes it in response.output_text.done and the final response.

Immutable evidence

The title and excerpt are saved with the message, so a later draft edit does not silently rewrite an earlier answer’s displayed source.

Safe interpretation

A citation proves which supplied excerpt the model referenced. It does not independently certify that the source or generated claim is correct.

Editing

Manually editing an assistant message clears its citations because the saved source-to-answer relationship is no longer reliable.

Website widget

Embed chat without exposing your agent key

Your backend authenticates the visitor as appropriate, calls the widget-session endpoint with its server-side gqa_ key, and returns the short-lived response. The page loads /widget.js and points it at that backend endpoint. The resulting client secret is pinned to one published version and exact browser origin. A stable externalUserId also lets a refreshed session resume only that visitor's conversation; anonymous sessions restart when their client secret expires.

Visitor browserYour authenticated backendWidget session exchangeOrigin-bound runtime

1. Create the customer-backend endpoint

// This code runs on your server. AGENTGROUND_API_KEY never reaches the browser.
const session = await fetch(
  "https://YOUR_HOST/v1/agents/AGENT_ID/widget-sessions",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AGENTGROUND_API_KEY}`,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      allowedOrigin: "https://www.example.com",
      externalUserId: authenticatedCustomer.id
    })
  }
);
return Response.json(await session.json(), { status: session.status });

2. Add the widget to the website

<script
  src="https://YOUR_HOST/widget.js"
  data-token-endpoint="/api/agent-widget-session"
  data-title="Ask our product guide"
  data-greeting="Hello! What would you like to know?"
  data-color="#183c2f"
  async></script>
Backend responsibility. Authorize the visitor before issuing a session, protect a cookie-authenticated token endpoint against cross-site request forgery, send Cache-Control: no-store, and never log or persist the returned client secret.

Core concept

Draft privately, publish deliberately

Editable draftPrivate testStaging versionOwner-approved production

Management endpoints edit the mutable draft. Publishing snapshots its model, prompt, knowledge, connectors, and lead schema into staging. The first release initializes production; later production changes require an owner promotion. Existing conversations remain on their starting version.

Human escalation

Turn an agent escalation into durable work

Enable Human handoff on the draft and describe when escalation is appropriate. The agent can then create one active ticket per conversation with a reason, factual summary, priority, stable ID, and lifecycle status. Publishing freezes those rules for new production sessions; your team can still move tickets through open, in_progress, resolved, and closed.

Persist first

The handoff is committed to SQLite before any notification is queued. A webhook outage does not lose the team work item.

Deliver later

Webhook delivery runs outside the agent turn, rejects redirects, times out after five seconds, and retries five times with increasing delays before becoming exhausted.

Allow-list hosts

An operator must add each exact destination hostname to WEBHOOK_ALLOWED_HOSTS. HTTPS is required except for localhost development.

Expect duplicates and reordering

Store AgentGround-Delivery before processing and make handlers idempotent. Do not assume events arrive in order; compare the handoff’s updatedAt.

Verify the raw request body before parsing JSON

import { createHmac, timingSafeEqual } from "node:crypto";

const rawBody = await readRawRequestBody(request);
const parts = Object.fromEntries(
  request.headers["agentground-signature"].split(",").map(part => part.split("=", 2))
);
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(parts.t));
const expected = createHmac("sha256", process.env.AGENTGROUND_WEBHOOK_SECRET)
  .update(`${parts.t}.${rawBody}`)
  .digest("hex");
const valid = age <= 300 && expected.length === parts.v1.length &&
  timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
if (!valid) throw new Error("Invalid or stale AgentGround signature");

const event = JSON.parse(rawBody);
// Deduplicate by event.id / AgentGround-Delivery, then return 2xx quickly.

The signing secret is shown only when an endpoint is created or rotated. The signature format is t=UNIX_SECONDS,v1=HEX_HMAC over timestamp + "." + rawBody. Reject signatures older than five minutes, use a constant-time comparison, and keep clocks synchronized.

Teams and releases

Give people only the access their job needs

A project owner can add an existing AgentGround account as an editor or viewer. Editors can change drafts, test, run evaluations, and create immutable staging releases. Viewers can inspect the project without changing it. Only the owner can manage membership, production promotions, API keys, provider credentials, and webhook secrets.

Publish to staging

POST /api/agents/{agentId}/publish snapshots the current evaluated draft and moves that version to staging. The first release also initializes production.

Promote deliberately

POST /api/agents/{agentId}/environments/production/promotions takes a versionId. Re-promote an older immutable version for an immediate rollback.

Test staging

Send AgentGround-Environment: staging, or "environment":"staging", when starting an API or widget conversation. Production is the default.

Keep sessions stable

The environment is resolved only when a conversation starts. Later turns keep the session's original version even after a promotion or rollback.

Membership is direct, not emailed. The teammate must register first. An owner then adds their exact account email; removal takes effect on the next authorized request.

Connection workflow

Enable reviewed capabilities without exposing configuration

GET /api/connectors returns the safe catalog installed by the operator: name, description, scope, approval policy, method, and destination host. Editors select connector names on the draft; the server validates every name and derives scopes rather than trusting browser-supplied permissions.

Operator boundary

Full URLs, fixed values, credential environment names, and secrets remain in config/tools/ and server configuration.

Approval boundary

Connectors marked always remain approval-gated at execution time. Enabling a connector does not bypass its policy.

Versioned workflow

Enabled connector names and derived scopes are frozen into each immutable release, so production does not change until promotion.

No arbitrary hosts

The studio cannot create an outbound connector. Operators must review its schema, allow-list the host, and install it before it appears.

Evaluation suites

Gate publishing on repeatable checks

Create up to ten representative cases for an agent draft. Each case supplies a visitor message and one to eight required response keywords. Run the suite after every meaningful draft change; only results from the current draftRevision count toward readiness.

Create cases

POST /api/agents/{agentId}/evaluations accepts name, input, and expectedKeywords.

Run the suite

POST /api/agents/{agentId}/evaluations/run runs cases sequentially within one shared agent deadline and returns each pass, failure, or error.

Publish safely

When REQUIRE_PASSED_EVALUATIONS=true, publishing returns 409 EVALUATION_GATE_FAILED until every saved case passes the current draft.

Interpret carefully

Keyword checks are deterministic regression signals, not semantic or safety certification. Use realistic cases and review model output before release.

Evaluation inputs, model outputs, and case results are stored in SQLite and follow the configured conversation retention policy. Observability responses expose model, latency, status, request ID, and token counts without prompts or provider credentials.

Errors

One predictable error envelope

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "A human-readable explanation.",
    "details": {},
    "requestId": "b4b34d3e-…"
  }
}
400 Invalid request401 Missing or invalid credential403 Not allowed404 Resource not found409 State conflict429 Rate limited504 Agent deadline exceeded5xx Service or provider failure

Reference

Choose the API surface for the job

Runtime

/v1/agents/{agentId}/responses · /v1/widget/responses

Invoke a published agent from a trusted server or an origin-bound browser widget, with optional lifecycle SSE.

Integration

/v1/agent · /v1/conversations

Discover the agent behind a key, open conversations, list them per end user, and read transcripts with cursors.

Management

/api/auth · /api/projects · /api/agents · /api/presets

Create accounts, start agents from demo presets, manage drafts, run evaluation suites, inspect prompt-free traces, publish versions, export leads, and manage keys.

Administration

/api/admin

Inspect system metrics and audits, reload configuration, and manage account access.

Compatibility

/api/session · /api/chat · /api/approvals

Use the legacy session-based chat and approval API for file-configured agents.

Complete machine-readable contract

The OpenAPI 3.1 document contains every operation, security requirement, request schema, response shape, and example. Import /openapi.json into Postman, Insomnia, or a client generator, or read /openapi.yaml. Both are generated from one source, so they never disagree.

View openapi.json