Api contract
API Contract
Section titled “API Contract”This document defines the external API contract for the background agents platform. It specifies the endpoints, request/response schemas, error format, authentication, pagination, and rate limiting. Current channels (CLI and webhook integrations) interact with the platform through this API, mediated by the input gateway.
This is a design-level specification, not an OpenAPI file. Implementation may generate an OpenAPI spec from the CDK API Gateway definition; this document is the source of truth for the contract.
At a glance
Section titled “At a glance”- Use this doc for: endpoint paths, payload shapes, auth requirements, and error codes.
- Current channels: CLI and webhook integrations.
- Not in scope here: internal orchestration internals (see ORCHESTRATOR.md).
Relationship to other docs:
- INPUT_GATEWAY.md — describes the gateway’s role (normalize, validate, dispatch) and the conceptual internal message/notification schemas.
- ORCHESTRATOR.md — defines the task state machine, data model, and lifecycle that this API exposes.
- SECURITY.md — authentication and authorization model.
Base URL and versioning
Section titled “Base URL and versioning”| Environment | Base URL |
|---|---|
| Production | https://{api-id}.execute-api.{region}.amazonaws.com/v1 |
| Custom domain | https://api.{customer-domain}/v1 |
API versioning uses a path prefix (/v1). Breaking changes increment the version (/v2). Non-breaking additions (new optional fields, new endpoints) do not require a version bump.
Authentication
Section titled “Authentication”All endpoints require authentication. The API supports multiple authentication methods depending on the channel:
| Channel | Auth method | Header | Endpoint scope |
|---|---|---|---|
| CLI / REST API | Cognito JWT (ID token) | Authorization: Bearer <token> | All /tasks and /webhooks management endpoints |
| Webhook | HMAC-SHA256 signature | X-Webhook-Id + X-Webhook-Signature: sha256=<hex> | POST /v1/webhooks/tasks only |
The gateway extracts the platform user ID (user_id) from the authenticated identity (Cognito sub for JWT, or webhook record lookup for HMAC) and attaches it to all internal messages. Downstream services never see raw tokens or secrets.
Common conventions
Section titled “Common conventions”Request format
Section titled “Request format”- Content type:
application/json - Character encoding: UTF-8
- Maximum request body size: 1 MB (configurable)
Response format
Section titled “Response format”All successful responses return:
{ "data": { ... }}List endpoints return:
{ "data": [ ... ], "pagination": { "next_token": "...", "has_more": true }}Error format
Section titled “Error format”All errors return a consistent structure:
{ "error": { "code": "TASK_NOT_FOUND", "message": "Task abc-123 not found.", "request_id": "req-uuid-here" }}| Field | Type | Description |
|---|---|---|
code | String | Machine-readable error code (see Error codes section). |
message | String | Human-readable description. |
request_id | String | Unique request ID for tracing and support. Also returned in the X-Request-Id response header. |
Standard response headers
Section titled “Standard response headers”| Header | Description |
|---|---|
X-Request-Id | Unique request ID (ULID). Present on all responses. |
X-RateLimit-Limit | Requests allowed per window (see Rate limiting). |
X-RateLimit-Remaining | Requests remaining in current window. |
X-RateLimit-Reset | Unix timestamp when the window resets. |
Idempotency
Section titled “Idempotency”Clients may include an Idempotency-Key header on POST requests. If a request with the same key was already processed (within a 24-hour TTL), the API returns the original response without creating a duplicate resource. See ORCHESTRATOR.md — Admission control for the implementation.
Endpoints
Section titled “Endpoints”Create task
Section titled “Create task”Creates a new task. The orchestrator runs admission control, context hydration, and starts the agent session.
POST /v1/tasksRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
repo | String | Yes | GitHub repository in owner/repo format. |
issue_number | Number | No | GitHub issue number. If provided, the issue title, body, and comments are fetched during context hydration. |
task_description | String | No | Free-text task description. At least one of issue_number or task_description must be provided. |
max_turns | Number | No | Maximum agent turns (1–500). Controls how many reasoning/tool-call iterations the agent can perform. Defaults to 100 if omitted. |
max_budget_usd | Number | No | Maximum cost budget in USD (0.01–100). When reached, the agent stops regardless of remaining turns. If omitted, no budget limit is applied (turn limit and session timeout still apply). |
attachments | Array | No | Multi-modal attachments (images, files). See Attachments schema below. |
Attachments schema:
{ "attachments": [ { "type": "image", "content_type": "image/png", "data": "<base64-encoded>", "filename": "screenshot.png" }, { "type": "url", "url": "https://example.com/spec.pdf" } ]}| Field | Type | Required | Description |
|---|---|---|---|
type | String | Yes | image, file, or url. |
content_type | String | No | MIME type (for inline data). |
data | String | No | Base64-encoded content (for inline uploads). Max 10 MB per attachment after decoding. |
url | String | No | URL to fetch (for URL-based attachments). |
filename | String | No | Original filename (for display and logging). |
Request headers:
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer token. |
Idempotency-Key | No | Client-supplied idempotency key (string, max 128 chars). |
Response: 201 Created
{ "data": { "task_id": "01HYX...", "status": "SUBMITTED", "repo": "org/myapp", "issue_number": 42, "branch_name": "bgagent/01HYX.../fix-auth-bug", "created_at": "2025-03-15T10:30:00Z" }}Error responses:
| Status | Code | Condition |
|---|---|---|
400 | VALIDATION_ERROR | Missing required fields, invalid repo format, no task description or issue, invalid max_turns (not an integer or outside 1–500 range), invalid max_budget_usd (not a number or outside 0.01–100 range). |
401 | UNAUTHORIZED | Missing or invalid auth token. |
409 | DUPLICATE_TASK | Idempotency key matches an existing task (returns the existing task in data). |
422 | REPO_NOT_ONBOARDED | Repository is not registered with the platform. Repos are onboarded via CDK deployment (Blueprint construct), not via a runtime API. See REPO_ONBOARDING.md. |
429 | RATE_LIMIT_EXCEEDED | User exceeded the per-user rate limit. |
Get task
Section titled “Get task”Returns the full details of a single task. Users can only access their own tasks.
GET /v1/tasks/{task_id}Path parameters:
| Parameter | Type | Description |
|---|---|---|
task_id | String | Task identifier (ULID). |
Response: 200 OK
{ "data": { "task_id": "01HYX...", "status": "RUNNING", "repo": "org/myapp", "issue_number": 42, "task_description": "Fix the authentication bug in the login flow", "branch_name": "bgagent/01HYX.../fix-auth-bug", "session_id": "sess-uuid", "pr_url": null, "error_message": null, "created_at": "2025-03-15T10:30:00Z", "updated_at": "2025-03-15T10:31:15Z", "started_at": "2025-03-15T10:31:10Z", "completed_at": null, "duration_s": null, "cost_usd": null, "build_passed": null, "max_turns": 100, "max_budget_usd": null }}| Field | Type | Description |
|---|---|---|
max_turns | Number or null | Maximum agent turns for this task. Always present in the response — reflects the effective value (user-specified or platform default of 100). |
max_budget_usd | Number or null | Maximum cost budget in USD for this task. Null if no budget limit was specified. |
Error responses:
| Status | Code | Condition |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid auth token. |
403 | FORBIDDEN | Task belongs to a different user. |
404 | TASK_NOT_FOUND | Task does not exist. |
List tasks
Section titled “List tasks”Returns tasks for the authenticated user, with optional filters. Paginated.
GET /v1/tasksQuery parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
status | String | No | (all) | Filter by status: SUBMITTED, HYDRATING, RUNNING, FINALIZING, COMPLETED, FAILED, CANCELLED, TIMED_OUT. Comma-separated for multiple (e.g. RUNNING,HYDRATING). |
repo | String | No | (all) | Filter by repository (owner/repo). |
limit | Number | No | 20 | Page size (1–100). |
next_token | String | No | (none) | Pagination token from a previous response. |
Response: 200 OK
{ "data": [ { "task_id": "01HYX...", "status": "RUNNING", "repo": "org/myapp", "issue_number": 42, "task_description": "Fix the authentication bug...", "branch_name": "bgagent/01HYX.../fix-auth-bug", "pr_url": null, "created_at": "2025-03-15T10:30:00Z", "updated_at": "2025-03-15T10:31:15Z" } ], "pagination": { "next_token": "eyJsYXN0...", "has_more": true }}The list response returns a summary (subset of fields). Use GET /v1/tasks/{task_id} for full details.
Error responses:
| Status | Code | Condition |
|---|---|---|
400 | VALIDATION_ERROR | Invalid status value, invalid limit, invalid next_token. |
401 | UNAUTHORIZED | Missing or invalid auth token. |
Cancel task
Section titled “Cancel task”Cancels a running task. See ORCHESTRATOR.md — Cancellation behavior by state for what happens in each state.
DELETE /v1/tasks/{task_id}Path parameters:
| Parameter | Type | Description |
|---|---|---|
task_id | String | Task identifier (ULID). |
Response: 200 OK
{ "data": { "task_id": "01HYX...", "status": "CANCELLED", "cancelled_at": "2025-03-15T11:00:00Z" }}Error responses:
| Status | Code | Condition |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid auth token. |
403 | FORBIDDEN | Task belongs to a different user. |
404 | TASK_NOT_FOUND | Task does not exist. |
409 | TASK_ALREADY_TERMINAL | Task is already in a terminal state (COMPLETED, FAILED, CANCELLED, TIMED_OUT). |
Get task events
Section titled “Get task events”Returns the audit trail for a task (state transitions, key events). Useful for debugging.
GET /v1/tasks/{task_id}/eventsPath parameters:
| Parameter | Type | Description |
|---|---|---|
task_id | String | Task identifier (ULID). |
Query parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
limit | Number | No | 50 | Page size (1–100). |
next_token | String | No | (none) | Pagination token. |
Response: 200 OK
{ "data": [ { "event_id": "01HYX...", "event_type": "task_created", "timestamp": "2025-03-15T10:30:00Z", "metadata": {} }, { "event_id": "01HYX...", "event_type": "admission_passed", "timestamp": "2025-03-15T10:30:01Z", "metadata": { "queue_position": 0 } }, { "event_id": "01HYX...", "event_type": "session_started", "timestamp": "2025-03-15T10:31:10Z", "metadata": { "session_id": "sess-uuid" } } ], "pagination": { "next_token": null, "has_more": false }}Event types (see OBSERVABILITY.md for the full list):
Fixed event types: task_created, admission_passed, admission_rejected, hydration_started, hydration_complete, session_started, session_ended, pr_created, task_completed, task_failed, task_cancelled, task_timed_out
Step-level event types (from the blueprint framework): The orchestrator emits events for each pipeline step following the pattern {step_name}_{started|completed|failed}. For built-in steps these overlap with the fixed types above (e.g. hydration_started). For custom Lambda steps (see REPO_ONBOARDING.md), the step name is user-defined (e.g. sast-scan_started, sast-scan_completed, prepare-environment_failed). Step event metadata includes StepOutput.metadata from the step execution.
Error responses:
| Status | Code | Condition |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid auth token. |
403 | FORBIDDEN | Task belongs to a different user. |
404 | TASK_NOT_FOUND | Task does not exist. |
Webhook integration
Section titled “Webhook integration”External systems (CI pipelines, GitHub Actions, custom automation) can create tasks via HMAC-authenticated webhook requests. Webhook integrations are managed through Cognito-authenticated endpoints; task submission uses a separate endpoint with HMAC-SHA256 authentication.
Webhook management endpoints
Section titled “Webhook management endpoints”These endpoints are protected by Cognito JWT (same as the task endpoints).
Create webhook
Section titled “Create webhook”Creates a new webhook integration and returns the shared secret (shown only once).
POST /v1/webhooksRequest body:
| Field | Type | Required | Description |
|---|---|---|---|
name | String | Yes | Human-readable name for the integration (1-64 chars, alphanumeric, spaces, hyphens, underscores). Must start and end with an alphanumeric character. |
Response: 201 Created
{ "data": { "webhook_id": "01HYX...", "name": "My CI Pipeline", "secret": "<webhook-secret-64-hex-characters>", "created_at": "2025-03-15T10:30:00Z" }}The secret is a 32-byte random value (64 hex characters). Store it securely — it cannot be retrieved after this response. The secret is stored in AWS Secrets Manager under the name bgagent/webhook/{webhook_id}.
Error responses:
| Status | Code | Condition |
|---|---|---|
400 | VALIDATION_ERROR | Missing or invalid webhook name. |
401 | UNAUTHORIZED | Missing or invalid auth token. |
List webhooks
Section titled “List webhooks”Returns the authenticated user’s webhook integrations. Paginated.
GET /v1/webhooksQuery parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
include_revoked | String | No | false | Set to true to include revoked webhooks. |
limit | Number | No | 20 | Page size (1-100). |
next_token | String | No | (none) | Pagination token from a previous response. |
Response: 200 OK
{ "data": [ { "webhook_id": "01HYX...", "name": "My CI Pipeline", "status": "active", "created_at": "2025-03-15T10:30:00Z", "updated_at": "2025-03-15T10:30:00Z", "revoked_at": null } ], "pagination": { "next_token": null, "has_more": false }}Error responses:
| Status | Code | Condition |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid auth token. |
Revoke webhook
Section titled “Revoke webhook”Soft-revokes a webhook integration. The webhook can no longer authenticate requests. The secret is scheduled for deletion with a 7-day recovery window. The revoked webhook record is automatically deleted from DynamoDB after 30 days (configurable via webhookRetentionDays). After deletion, GET /v1/webhooks will no longer return the record.
DELETE /v1/webhooks/{webhook_id}Path parameters:
| Parameter | Type | Description |
|---|---|---|
webhook_id | String | Webhook identifier (ULID). |
Response: 200 OK
{ "data": { "webhook_id": "01HYX...", "name": "My CI Pipeline", "status": "revoked", "created_at": "2025-03-15T10:30:00Z", "updated_at": "2025-03-15T12:00:00Z", "revoked_at": "2025-03-15T12:00:00Z" }}Error responses:
| Status | Code | Condition |
|---|---|---|
401 | UNAUTHORIZED | Missing or invalid auth token. |
404 | WEBHOOK_NOT_FOUND | Webhook does not exist, or belongs to a different user. |
409 | WEBHOOK_ALREADY_REVOKED | Webhook is already revoked. |
Webhook task creation
Section titled “Webhook task creation”Creates a task via webhook. Uses HMAC-SHA256 authentication instead of Cognito JWT. The task is owned by the Cognito user who created the webhook integration.
POST /v1/webhooks/tasksRequest body: Same as POST /v1/tasks (see Create task).
Required headers:
| Header | Required | Description |
|---|---|---|
X-Webhook-Id | Yes | Webhook integration ID. |
X-Webhook-Signature | Yes | sha256=<hex-hmac> — HMAC-SHA256 of the raw request body using the webhook secret. |
Idempotency-Key | No | Client-supplied idempotency key (same semantics as POST /v1/tasks). |
Authentication flow (two-phase):
- A Lambda REQUEST authorizer extracts the
X-Webhook-Idheader and verifies that bothX-Webhook-IdandX-Webhook-Signatureare present. - Looks up the webhook record in DynamoDB; verifies
status: active. - On success, returns an Allow policy with
context: { userId, webhookId }. On failure, returns Deny. - The webhook handler fetches the shared secret from Secrets Manager (cached in-memory with 5-minute TTL).
- Computes
HMAC-SHA256(secret, raw_request_body)and compares with the provided signature using constant-time comparison (crypto.timingSafeEqual). - On success, creates the task. On failure, returns
401 Unauthorized.
HMAC verification is performed by the handler (not the authorizer) because API Gateway REST API v1 does not pass the request body to Lambda REQUEST authorizers. Authorizer result caching is disabled (resultsCacheTtl: 0) because each request has a unique signature.
Response: 201 Created — Same as POST /v1/tasks.
Error responses:
| Status | Code | Condition |
|---|---|---|
400 | VALIDATION_ERROR | Missing required fields, invalid repo format, no task description or issue, invalid max_turns, invalid max_budget_usd. |
401 | UNAUTHORIZED | Missing webhook headers, webhook not found, revoked, or invalid signature. |
409 | DUPLICATE_TASK | Idempotency key matches an existing task. |
Channel metadata: Tasks created via webhook record channel_source: 'webhook' and channel_metadata including webhook_id, source_ip, user_agent, and api_request_id for audit purposes.
Rate limiting
Section titled “Rate limiting”Rate limits are enforced per authenticated user.
| Limit | Value | Scope | Response |
|---|---|---|---|
| Request rate | 60 requests/minute | Per user, across all endpoints | 429 Too Many Requests |
| Task creation rate | 10 tasks/hour | Per user, POST /v1/tasks only | 429 with code RATE_LIMIT_EXCEEDED |
| Concurrent tasks | Configurable (default: 3–5) | Per user, running tasks | New tasks above the limit are rejected with 409 CONCURRENCY_LIMIT_EXCEEDED. See ORCHESTRATOR.md — Admission control. |
Rate limit status is communicated via response headers (see Standard response headers).
Error codes
Section titled “Error codes”| Code | HTTP Status | Description |
|---|---|---|
VALIDATION_ERROR | 400 | Request body or query parameters are invalid. |
UNAUTHORIZED | 401 | Missing, expired, or invalid authentication. |
FORBIDDEN | 403 | Authenticated but not authorized (e.g. accessing another user’s task). |
TASK_NOT_FOUND | 404 | Task ID does not exist. |
DUPLICATE_TASK | 409 | Idempotency key matches an existing task. |
TASK_ALREADY_TERMINAL | 409 | Cannot cancel a task that is already in a terminal state. |
WEBHOOK_NOT_FOUND | 404 | Webhook does not exist or belongs to a different user. |
WEBHOOK_ALREADY_REVOKED | 409 | Webhook is already revoked. |
REPO_NOT_ONBOARDED | 422 | Repository is not registered with the platform. Repos are onboarded via CDK deployment, not via a runtime API. There are no /v1/repos endpoints. |
INVALID_STEP_SEQUENCE | 500 | The blueprint’s step sequence is invalid (missing required steps or incorrect ordering). This indicates a CDK configuration error that slipped past synth-time validation. Visible via GET /v1/tasks/{id} as error_code. See REPO_ONBOARDING.md. |
RATE_LIMIT_EXCEEDED | 429 | User exceeded rate limit. |
INTERNAL_ERROR | 500 | Unexpected server error. Includes request_id for support. |
SERVICE_UNAVAILABLE | 503 | Downstream dependency unavailable (e.g. DynamoDB, AgentCore). Retry with backoff. |
Pagination
Section titled “Pagination”List endpoints use token-based pagination (not offset-based). This is consistent with DynamoDB’s ExclusiveStartKey pattern.
- The response includes
pagination.next_token(opaque string) andpagination.has_more(boolean). - To fetch the next page, pass
next_tokenas a query parameter. - Tokens are short-lived (valid for the duration of a session, not persisted). Do not store or cache them.
- Results are ordered by
created_atdescending (newest first) unless otherwise specified.
Implementation notes
Section titled “Implementation notes”API Gateway configuration
Section titled “API Gateway configuration”The API is implemented as an Amazon API Gateway REST API (or HTTP API) with Lambda integrations:
| Endpoint | Lambda handler | Auth | Description |
|---|---|---|---|
POST /v1/tasks | createTaskHandler | Cognito | Validates, creates task record, triggers orchestrator. |
GET /v1/tasks | listTasksHandler | Cognito | Queries DynamoDB UserStatusIndex GSI. |
GET /v1/tasks/{task_id} | getTaskHandler | Cognito | Reads task from DynamoDB, enforces ownership. |
DELETE /v1/tasks/{task_id} | cancelTaskHandler | Cognito | Updates task status, signals orchestrator to cancel. |
GET /v1/tasks/{task_id}/events | getTaskEventsHandler | Cognito | Queries DynamoDB TaskEvents table. |
POST /v1/webhooks | createWebhookHandler | Cognito | Creates webhook integration, generates SM secret. |
GET /v1/webhooks | listWebhooksHandler | Cognito | Queries user’s webhooks from DynamoDB UserIndex GSI. |
DELETE /v1/webhooks/{webhook_id} | deleteWebhookHandler | Cognito | Soft-revokes webhook, schedules SM secret deletion. |
POST /v1/webhooks/tasks | webhookCreateTaskHandler | HMAC | Creates task via webhook (shared core with createTaskHandler). |
| — | webhookAuthorizerFn | — | REQUEST authorizer: verifies webhook exists and is active. |
Authorization model
Section titled “Authorization model”- All endpoints enforce user ownership: a user can only access tasks where
task.user_idmatches the authenticated user’s platform ID. Webhooks enforce ownership at the management layer — only the webhook creator can list, view, or revoke it. - For Cognito-authenticated endpoints, the
user_idis extracted from the JWT claims (sub) and passed to handlers via the request context. - For webhook-authenticated endpoints, the
user_idis extracted from the webhook record by the Lambda REQUEST authorizer and injected into the authorizer context (event.requestContext.authorizer.userId). - Handlers never trust client-supplied user IDs.
Relationship to internal message schema
Section titled “Relationship to internal message schema”The API request/response schemas defined here are the external contract. The input gateway normalizes API requests into the internal message schema (see INPUT_GATEWAY.md) before dispatching to the task pipeline. The internal schema may include additional fields (e.g. channel_metadata, normalized_at) that are not exposed in the API.