Agent API key
Authorization: Bearer gqa_…For production calls to one published agent. This is the preferred integration credential.
AgentGround API · v4.2
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.
Five-minute quickstart
In the studio, complete Setup, add trusted knowledge, pass evaluations, and publish to staging. The first release initializes production; later versions need owner promotion.
Open Release & API, create a key, and copy it immediately. Only its hash is stored.
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"}'
const response = await fetch(
"https://YOUR_HOST/v1/agents/AGENT_ID/responses",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AGENTGROUND_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "visitor-42-message-1"
},
body: JSON.stringify({
input: "Which plan is right for a small team?",
externalUserId: "visitor-42"
})
}
);
if (!response.ok) throw new Error(await response.text());
const result = await response.json();
import os
import requests
response = requests.post(
"https://YOUR_HOST/v1/agents/AGENT_ID/responses",
headers={
"Authorization": f"Bearer {os.environ['AGENTGROUND_API_KEY']}",
"Idempotency-Key": "visitor-42-message-1",
},
json={
"input": "Which plan is right for a small team?",
"externalUserId": "visitor-42",
},
timeout=60,
)
response.raise_for_status()
result = response.json()
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
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.
GET /v1/agent returns the published name, opening message, conversationStyle, lead schema, released environments, and the exact endpoint URLs to call.
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.
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.
metadata accepts up to twenty flat key/value pairs echoed back on reads and events. Keep secrets and sensitive personal data out of it.
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
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.
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.
For visitor_led, the opening message stays a display-only greeting that never reaches the model, exactly as before.
An agent-led agent cannot be published without an opening message: publishing returns 400 OPENING_MESSAGE_REQUIRED.
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
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.
A new API or widget conversation was created. The payload carries the conversation, including your metadata.
A visitor or assistant message was persisted. Use this instead of polling to mirror the transcript into your own product.
The agent asked for a human, with a factual summary and priority.
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
Authorization: Bearer gqa_…For production calls to one published agent. This is the preferred integration credential.
Authorization: Bearer …Short-lived, origin-bound, and safe to return from your backend to the widget. It cannot manage the agent.
Authorization: Bearer …For authenticated studio and management endpoints. Treat it like a login session.
Authorization: Bearer …For the compatibility session/chat API and its approval workflow.
X-Admin-API-Key: …For global administration only. Keep it isolated from product integrations.
Requests and retries
Send Idempotency-Key on runtime writes. Reusing the same key with the same request returns the original result.
Every API call has a global IP quota. Authenticated calls also return X-AccountRateLimit-* or X-AgentRateLimit-*. On 429, respect Retry-After.
Log the returned X-Request-ID. You may provide your own unique value to correlate application logs.
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
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}'
response.created, zero or more response.progress events, response.output_text.done, then response.completed.
After HTTP 200 begins, failures arrive as an error event. Treat a stream without response.completed as incomplete.
Knowledge grounding
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."
}]
}
Each output message has a citations array. Streaming also includes it in response.output_text.done and the final response.
The title and excerpt are saved with the message, so a later draft edit does not silently rewrite an earlier answer’s displayed source.
A citation proves which supplied excerpt the model referenced. It does not independently certify that the source or generated claim is correct.
Manually editing an assistant message clears its citations because the saved source-to-answer relationship is no longer reliable.
Website widget
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.
// 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 });
<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>
Core concept
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
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.
The handoff is committed to SQLite before any notification is queued. A webhook outage does not lose the team work item.
Webhook delivery runs outside the agent turn, rejects redirects, times out after five seconds, and retries five times with increasing delays before becoming exhausted.
An operator must add each exact destination hostname to WEBHOOK_ALLOWED_HOSTS. HTTPS is required except for localhost development.
Store AgentGround-Delivery before processing and make handlers idempotent. Do not assume events arrive in order; compare the handoff’s updatedAt.
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
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.
POST /api/agents/{agentId}/publish snapshots the current evaluated draft and moves that version to staging. The first release also initializes production.
POST /api/agents/{agentId}/environments/production/promotions takes a versionId. Re-promote an older immutable version for an immediate rollback.
Send AgentGround-Environment: staging, or "environment":"staging", when starting an API or widget conversation. Production is the default.
The environment is resolved only when a conversation starts. Later turns keep the session's original version even after a promotion or rollback.
Connection workflow
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.
Full URLs, fixed values, credential environment names, and secrets remain in config/tools/ and server configuration.
Connectors marked always remain approval-gated at execution time. Enabling a connector does not bypass its policy.
Enabled connector names and derived scopes are frozen into each immutable release, so production does not change until promotion.
The studio cannot create an outbound connector. Operators must review its schema, allow-list the host, and install it before it appears.
Evaluation suites
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.
POST /api/agents/{agentId}/evaluations accepts name, input, and expectedKeywords.
POST /api/agents/{agentId}/evaluations/run runs cases sequentially within one shared agent deadline and returns each pass, failure, or error.
When REQUIRE_PASSED_EVALUATIONS=true, publishing returns 409 EVALUATION_GATE_FAILED until every saved case passes the current draft.
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
{
"error": {
"code": "VALIDATION_ERROR",
"message": "A human-readable explanation.",
"details": {},
"requestId": "b4b34d3e-…"
}
}
Reference
Invoke a published agent from a trusted server or an origin-bound browser widget, with optional lifecycle SSE.
Discover the agent behind a key, open conversations, list them per end user, and read transcripts with cursors.
Create accounts, start agents from demo presets, manage drafts, run evaluation suites, inspect prompt-free traces, publish versions, export leads, and manage keys.
Inspect system metrics and audits, reload configuration, and manage account access.
Use the legacy session-based chat and approval API for file-configured agents.
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.