Every deployed agent is callable through the platform's /v1 surface — the
same invoke chain the Chat playground uses. Interactive docs: /api/docs.
Auth: X-Api-Key header. Create a key in the console (Chat → API KEYS) or:
curl -s -X POST localhost:8000/api/apikeys -H 'Content-Type: application/json' \
-d '{"name": "integration"}'
# → {"id": "…", "prefix": "lp_live_ab12…", "key": "lp_live_<full-key-shown-once>"}Keys are stored hashed (sha256) — the full key is shown exactly once. 密钥仅创建时展示一次,后端只保存哈希。
curl -s -X POST localhost:8000/v1/agents/<AGENT_ID>/invoke \
-H "X-Api-Key: $LP_KEY" -H 'Content-Type: application/json' \
-d '{"prompt": "What is 2+2?", "session_id": null}'
# → {"agent":"…","text":"4","session_id":"…","latency_ms":1234}curl -N -s -X POST localhost:8000/v1/agents/<AGENT_ID>/invoke-stream \
-H "X-Api-Key: $LP_KEY" -H 'Content-Type: application/json' \
-d '{"prompt": "Tell me a two-sentence story."}'
# event: meta → {"session_id": "…", "mode": "stream"}
# event: delta → {"text": "Once"} … (incremental chunks)
# event: done → {"latency_ms": 2100}Pass the returned session_id on the next call to continue the conversation
(session context + AgentCore Memory ride on it).
import requests
BASE, KEY, AGENT = "http://localhost:8000", "lp_live_…", "<AGENT_ID>"
# sync
r = requests.post(
f"{BASE}/v1/agents/{AGENT}/invoke",
headers={"X-Api-Key": KEY},
json={"prompt": "How many vacation days does EMP-1024 have left?"},
timeout=120,
)
print(r.json()["text"])
# streaming (SSE)
with requests.post(
f"{BASE}/v1/agents/{AGENT}/invoke-stream",
headers={"X-Api-Key": KEY},
json={"prompt": "Summarize our HR policy in one line."},
stream=True, timeout=300,
) as stream:
for line in stream.iter_lines(decode_unicode=True):
if line.startswith("data:"):
print(line[5:].strip())Errors use the platform envelope {code, message, detail} — e.g.
auth.missing_api_key (401), agent.not_active (409), agent.not_found (404).
An AWS-side failure the platform did not map to a service code of its own
(kb.not_found, memory.unavailable, …) is still returned as an envelope, never
as a bare 500 Internal Server Error or as botocore's
An error occurred (…) when calling the … operation: text. The global
ClientError handler in app/core/errors.py maps the AWS error code:
| AWS error code | HTTP | code |
|---|---|---|
ResourceNotFoundException |
404 | aws.not_found |
ValidationException |
400 | aws.validation |
AccessDeniedException, UnauthorizedException |
403 | aws.access_denied |
ThrottlingException, TooManyRequestsException, ServiceQuotaExceededException |
429 | aws.throttled |
ConflictException, ResourceInUseException |
409 | aws.conflict |
message is the AWS message with the botocore prefix stripped; detail is
{"aws_error_code": "<AWS code>", "operation": "<boto operation>"}. Any other
AWS error code (e.g. InternalServerException) remains an unhandled 500 with the
traceback in the backend log. A failed cross-account role assumption keeps its own
answer: 502 workspace.assume_role_failed. /v1 shares the handler and returns
the same status and code, but its message is a generic per-code sentence
(AWS resource not found, AWS rejected the request as invalid, AWS access denied, AWS is throttling this request, AWS resource conflict) and detail
carries only aws_error_code — the raw AWS text names the deployment's role ARN,
instance id and operation, which stay on the console side of the API-key boundary.
These /api routes back the authenticated console. They are not part of the
public /v1 agent invocation contract.
| Method | Path | Result |
|---|---|---|
GET |
/api/governance/gateways |
Live MCP Gateway inventory |
GET |
/api/governance/gateways/{id} |
Targets, actions, Registry, Engine, IAM, and attachability detail |
POST/DELETE |
/api/governance/gateways/{id}/manage |
Add/remove only Launchpad management tags |
GET |
/api/governance/gateways/{id}/registry-preview |
Gateway-level record diff and legacy matches |
POST |
/api/governance/gateways/{id}/registry-import |
Create/reuse/update and submit; never approve |
POST |
/api/governance/gateways/{id}/retire-legacy-records |
Explicit retirement after Gateway record approval |
POST |
/api/governance/gateways/{id}/engine |
Create/adopt and attach an Engine in selected mode (ENFORCE default) |
GET/POST |
/api/governance/gateways/{id}/policies |
List or create LOG_ONLY policies |
PUT |
/api/governance/gateways/{id}/policies/{policy_id} |
Update LOG_ONLY or create an ACTIVE-policy candidate |
POST |
/api/governance/gateways/{id}/policies/{policy_id}/promote |
Evidence-gated activation/cutover |
POST |
/api/governance/gateways/{id}/policies/{policy_id}/rollback |
Audited snapshot/candidate rollback |
POST |
/api/governance/gateways/{id}/mode |
Gateway LOG_ONLY/ENFORCE transition |
POST |
/api/governance/gateways/{id}/generations |
Start NL → Cedar generation for review only |
GET |
/api/governance/gateways/{id}/generations/{generation_id} |
Poll generation status and read draft assets |
GET |
/api/governance/gateways/{id}/decisions |
AWS decision projection or explicit unavailable state |
GET |
/api/governance/gateways/{id}/audit |
Immutable local change journal |
GET |
/api/governance/operations/{operation_id} |
Async operation status |
Policy and Gateway mutations return 202:
{"operation": {"id": "...", "status": "pending", "operation": "policy_create"}}Generation start returns
{"operation": …, "generation_id": …, "status": …}; a generated asset is only
a draft for the editor and never activates a policy.
Poll the operation route until succeeded, failed, partial, or
interrupted. interrupted means a restart could not prove the AWS effect and
the operation must be retried explicitly — the backend never replays it.
Mutation requests carry the live timestamps and confirmations that apply to the
operation:
{
"expected_gateway_updated_at": "2026-07-16T09:00:00+00:00",
"expected_policy_updated_at": "2026-07-16T09:01:00+00:00",
"acknowledged_gateway_ids": ["gw-a", "gw-b"],
"confirmation_name": "finance-gateway",
"override_reason": null
}Common conflict codes are governance.gateway_not_managed,
governance.concurrent_change, governance.shared_engine_changed,
governance.iam_preflight_failed, governance.evidence_required,
governance.policy_engine_deleted, and
governance.registry_record_not_approved.
When a Gateway still references a Policy Engine that was deleted out-of-band,
reads report the reference with policy_engine.missing = true and
status = "DELETED" instead of failing, policy mutations answer
409 governance.policy_engine_deleted, and POST .../engine treats the
reference as unattached: it creates a new Engine, attaches it in the selected
mode, and records the replaced ARN on the operation.
/api/memory/* backs the read-only Memory console (console 05) over the shared
launchpad_memory singleton. Every route is a read: there is no endpoint that
writes events, deletes records, triggers extraction, or changes the memory
resource. See architecture.md.
| Method | Path | Result |
|---|---|---|
GET |
/api/memory/overview |
Resource config, long-term strategies, bounded actor count, sibling memories |
GET |
/api/memory/actors |
Actors with the compound <agent_id>__<human> id decoded and the agent name resolved |
GET |
/api/memory/sessions?actor_id= |
Sessions for one actor, joined to the ChatSession ledger when the console wrote them |
GET |
/api/memory/events?actor_id=&session_id= |
Short-term events; conversational payloads carry role + full text, blobs only a byte count |
GET |
/api/memory/namespaces?actor_id= |
Strategy namespace templates with {actorId} substituted, plus a resolvable flag |
GET |
/api/memory/records?actor_id=&strategy_id= or ?namespace= |
Long-term records for the resolved namespace |
POST |
/api/memory/records/search |
Semantic retrieval ({query, actor_id, strategy_id?, namespace?, top_k}) with relevance scores |
GET |
/api/memory/extraction-jobs |
Failed (retry-eligible) extraction jobs, filterable by actor_id/session_id/strategy_id/status — not surfaced in the console; AWS's status enum is FAILED only, so a healthy resource returns an empty list |
Every list route accepts and returns next_token (AWS pages at 100 items) and
accepts max_results (clamped to 100) — nothing is capped silently. Namespace
resolution order on /records and /records/search: an explicit namespace
wins, otherwise it derives from actor_id (+ optional strategy_id).
Error codes: memory.not_configured (409, bootstrap has not run — except
/overview, which instead returns {"configured": false, …} so the page can
render a setup state), memory.namespace_required (400, no namespace could be
derived), memory.unavailable (502, the underlying AWS call failed).
/api/eval/online/* manages AgentCore online evaluation configs — continuous,
sampled scoring of live sessions. AWS is the source of truth; the ledger keeps
identifiers only. Every config in the workspace account is listed and classified
by owner: agent (created here for an agent), experiment (exp_*/can_*
arms owned by experiments — read-only), external (anything else).
| Method | Path | Result |
|---|---|---|
GET |
/api/eval/online |
{configs, total} — all configs, newest first, with owner, both statuses, failure_reason, evaluators, sampling, timeout, matched_agent (external rows whose log group matches a workspace agent), duplicate_enabled (two ENABLED agent configs on one agent), results_log_group |
POST |
/api/eval/online |
Create for an active agent: {agent_id, mode: scores|insights (scores), evaluators[1..10] (scores mode), insights[1..3] ⊆ Builtin.Insight.FailureAnalysis|UserIntent|ExecutionSummary + clustering_frequencies[0..3] ⊆ DAILY|WEEKLY|MONTHLY (insights mode), sampling_percentage 0.01–100 (omit → 10 scores / 100 insights), session_timeout_minutes 1–1440 (15), filters[0..5], description?, enable_on_create (true)} → 201 row (status starts CREATING). Mixing kinds → 422 online_eval.mode_conflict; rows carry mode (derived: insights non-empty) |
GET |
/api/eval/online/{config_id} |
Full detail incl. filters, data_source, execution_role_arn |
PATCH |
/api/eval/online/{config_id} |
owner=agent only: any of description, sampling_percentage, session_timeout_minutes, filters plus the mode's own analysis field — evaluators (scores) or insights / clustering_frequencies (insights; complete lists, [] frequencies clears clustering); the other kind → 422 online_eval.mode_conflict, mode is immutable. The backend re-sends the complete rule (AWS replaces it as a unit) |
POST |
/api/eval/online/{config_id}/pause · /resume |
Flip executionStatus (agent + external) |
DELETE |
/api/eval/online/{config_id} |
Delete on AWS + drop the ledger row (agent + external); the results log group is left in place and named in the response |
GET |
/api/eval/online/{config_id}/results?range=1h|6h|24h|7d |
Logs Insights over the results log group: evaluators[{evaluator_id, level, mean, count, sessions, labels}], series{evaluator: [{bucket, mean, count}]}, recent[≤50] with judge explanation, errors{count, first_message}; empty collections while nothing has been evaluated yet |
GET |
/api/eval/online/{config_id}/reports |
Insights reports = batch evaluations sourced from the config: {config_id, mode, reports[{batch_id, name, status, run_status, created_at, updated_at, insights, sessions{completed, failed, in_progress, total}, origin: aws_scheduled|console, run_id, error}], aws_unavailable} newest first (aws_unavailable: true when ListBatchEvaluations failed — console rows only) — console runs from the ledger (EvalRun.dataset_name == "online:<config_id>") merged with AWS-scheduled batches attributed by GetBatchEvaluation.dataSourceConfig.onlineEvaluationConfigSource.onlineEvaluationConfigArn (only source-less summaries are candidates; one Get each, cached per batch id). Any owner may read |
POST |
/api/eval/online/{config_id}/reports |
RUN REPORT NOW {range: 1h|6h|24h|7d (24h)} → 202 {run_id, status, queue_position}: agent-owned insights configs only (403 / 422 otherwise); an EvalRun(mode=insights, dataset_name="online:<config_id>") through the bounded run queue whose batch uses onlineEvaluationConfigSource — it covers only the sessions the config sampled in the window and inherits the config's insights (AWS rejects explicit evaluators/insights on that source) |
GET |
/api/eval/online/{config_id}/reports/{batch_id} |
{batch_id, name, status, created_at, updated_at, time_range, sessions, insights{failures, userIntents, executionSummaries}, error_details} (parse_insights trees, same as a Runs-page insights run); 404 online_eval.report_not_found when the batch is not sourced from this config |
Filter shape: {key: "[a-zA-Z0-9._-]+", operator: Equals|NotEquals|GreaterThan|LessThan| GreaterThanOrEqual|LessThanOrEqual|Contains|NotContains, value: {stringValue|doubleValue|booleanValue}}
(exactly one typed value).
Error codes: online_eval.no_telemetry (400, the agent has no telemetry log group
yet — run one session first), online_eval.evaluator_unsupported (400, trajectory
matcher / unknown built-in / custom judge that needs ground truth),
online_eval.read_only (403, action not allowed for that owner),
online_eval.not_found (404), online_eval.conflict (409, name collision after one
retry), online_eval.workspace_not_bootstrapped (400), online_eval.invalid_filter
/ online_eval.bad_range (422).
Results appear only after a session is idle for session_timeout_minutes; custom
evaluators referenced by an ENABLED config are locked by AWS (no edit/delete).
Online scores also surface where sessions are looked at:
| Method | Path | Result |
|---|---|---|
GET |
/api/observability/sessions/{session_id} |
The session detail carries online_scores: {configs[{config_id, config_name, owner, agent{id,name}?, records[{time, evaluator_id, level, score, label, explanation, trace_id}]}], total, unavailable, configs_exist} — every config's result records for that session (agent-owned blocks first), read with one prefix SOURCE logGroups(namePrefix: ['/aws/bedrock-agentcore/evaluations/results/']) query. Fail-soft: a results-query failure sets unavailable: true and never removes traces or transcript; configs_exist is whether the workspace has an agent-owned config (the UI hides the block when neither results nor configs exist) |
GET |
/api/overview/online-quality |
ONLINE QUALITY · 24h tile: {range: "24h", mean, scores, sessions, agents, configs, evaluators[{evaluator_id, mean, count, polarity}], cached} — count-weighted mean over every (evaluator, agent-owned config) pair with lower-is-better evaluators inverted (1 − mean), so the tile always reads higher-is-better; evaluators[].mean stays raw; configs counts the workspace's agent-owned configs (ledger) and agents the agents that scored, so "configured, nothing judged yet" is distinguishable from "no config". 120 s per-workspace cache with single-flight, force=true bypasses; a workspace without agent-owned configs answers the empty payload without any AWS call |
/api/auth/* gates the console and /api/users/* manages the accounts behind
it. Neither surface touches AWS. See
architecture.md.
| Method | Path | Auth | Result |
|---|---|---|---|
GET |
/api/auth/status |
open | {auth_required, authenticated, registration_enabled, registration_requires_approval, username, role, email, account_expires_at} — identity fields are null until authenticated |
POST |
/api/auth/login |
open | Sets the launchpad_session cookie (12h, clamped to the account validity) and echoes the identity |
POST |
/api/auth/register |
open | 201 — creates a member account; by default status=pending with expires_at=null until an admin approves it, then valid for auth_registration_valid_days (default 7) |
POST |
/api/auth/logout |
session | Clears the cookie |
GET |
/api/users?q=&status=all|pending|active|expired|disabled&limit=&offset= |
admin | Paged account list with derived state / days_remaining |
GET |
/api/users/stats |
admin | Totals including the pending approval queue, expiring_soon (≤3 days), 7-day registration/sign-in counts, a 14-day registration series, top email domains |
PATCH |
/api/users/{id} |
admin | Any of status (pending|active|disabled; active on a pending account approves it and starts its window), role, extend_days, expires_at (null = never expires), password (null = generate and return once) |
DELETE |
/api/users/{id} |
admin | Removes the account |
Registration error codes: auth.registration_disabled (400, gate off or
registration disabled), auth.invalid_username / auth.invalid_email /
auth.email_domain_blocked / auth.weak_password (400),
auth.username_taken / auth.email_taken (409).
Sign-in error codes: auth.invalid_credentials (401), plus
auth.account_pending / auth.account_disabled / auth.account_expired (401)
once the submitted credentials themselves are correct.
Session and role errors: auth.required (401 — missing, tampered, or expired
cookie, and also an account that has since been disabled, expired, or deleted),
auth.forbidden (403 — member session on /api/users*), users.not_found
(404).