← Docs · 日本語

AgenTrux API Reference

API reference for endpoints exposed via the A2A (Agent-to-Agent) protocol.

Base URL: https://api.agentrux.com


Table of Contents

  1. Authentication Flow
  2. Credentials
  3. Discovery — /.well-known + /a2a
  4. Auth — /auth
  5. Data Plane — /topics
  6. Console — /console
  7. Error Responses
  8. Webhook Notifications

Authentication Flow

1. Redeem an Activation Code (once)
   POST /auth/redeem-activation-code  { "code": "act_..." }
        → { client_id: "crd_...", client_secret: "aks_...", script_id, issued_at }

2. Get an access token (per script run)
   POST /oauth/token  (form: grant_type=client_credentials&client_id=crd_...&client_secret=aks_...)
        → { access_token: "aat_..." (JWT), expires_in: 600, ... }

3. Send / Receive Events
   POST /topics/{id}/events   (send — Authorization: Bearer aat_...)
   GET  /topics/{id}/events   (receive / poll)
   GET  /topics/{id}/events/stream  (SSE)

4. Refresh (authorization_code / device_code grants only)
   POST /oauth/token  (form: grant_type=refresh_token&refresh_token=art_...)
   (client_credentials does NOT issue a refresh token — just call /oauth/token again.)

5. Cross-Account share (Console-only, Phase Z 2026-05-22)
   Topic owner: POST /console/aliases/{ali_id}/share-trust  {grantee_email}
   Grantee:    POST /console/grants  {topic_id, script_id, action}
   (Anonymous invite tokens were removed; receiver must be a registered user.)

Credentials

Name Prefix Lifetime Description
Activation Code act_ One-time Redeem for a Script credential via POST /auth/redeem-activation-code. Do not store
Script Credential ID (client_id) crd_ Permanent Your client_id. Store it (sent to POST /oauth/token)
Client Secret aks_ Permanent Your password. Shown once at redeem; store securely
Access Token (JWT) aat_ (RS256 JWT) Short-lived (~10 min) Used in Authorization: Bearer header
Refresh Token art_ One-time (rotation) Issued only by authorization_code / device_code grants (not client_credentials)

Note (Phase Z, 2026-05-22): inv_ anonymous invite tokens are no longer issued. Cross-account access is established via Console (admin direct picker by grantee email). See Cross-Account Share.


Discovery

GET /a2a

Health check. No authentication required.

Response 200

{
  "status": "ok",
  "version": "0.1.0",
  "timestamp": "2026-03-11T12:00:00+00:00"
}

GET /.well-known/agent-card.json

A2A Agent Card (JSON). No authentication required. An agent can autonomously use AgenTrux by reading this card alone.


Auth

Authentication and token endpoints. None require a JWT (credentials are in the body).

POST /auth/redeem-activation-code

Redeem a one-time Activation Code (act_) for a permanent Script credential.

Request

{
  "code": "act_Abc123..."
}
Field Type Required Description
code string Yes The act_ activation code (single-use)

Response 200

{
  "client_id": "crd_550e8400-e29b-41d4-a716-446655440000",
  "client_secret": "aks_xxxxxxxx",
  "script_id": "scr_...",
  "issued_at": "2026-03-11T12:00:00+00:00"
}

client_secret (aks_) is returned exactly once.

Error HTTP Condition
NOT_FOUND 404 Code does not exist or has expired
CONFLICT 409 Code already consumed

Important: Store client_id (crd_) and client_secret (aks_) permanently. Redeem is one-time; issue a fresh act_ in the Console if lost.


POST /oauth/token

Obtain a Bearer access token. Form-encoded (application/x-www-form-urlencoded), OAuth 2.1.

Request (grant_type=client_credentials)

grant_type=client_credentials&client_id=crd_...&client_secret=aks_...
Field Type Required Description
grant_type string Yes client_credentials
client_id string Yes Script credential ID (crd_)
client_secret string Yes Script credential secret (aks_)

Response 200

{
  "access_token": "aat_eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 600,
  "scope": "topic:top_<uuid>:read topic:top_<uuid>:write"
}

grant_type=client_credentials does not return a refresh_token; call /oauth/token again when the aat_ expires (~10 min).

JWT Claims

The issued JWT contains the following claims:

Claim Description
sub Script ID (UUID)
account_id Account ID (billing principal, value acc_…)
alias_id Alias ID (application-layer principal that owns the script, value ali_…)
scope Granted topic scopes
Error HTTP Condition
invalid_client 401 Invalid client_id (crd_) or client_secret (aks_)
SUSPENDED 403 Script, Alias, or account is suspended

POST /oauth/token (grant_type=refresh_token)

Rotate a refresh token for a new access token. Refresh tokens are issued only by the authorization_code and device_code grants — client_credentials has no refresh leg (just call /oauth/token again). The old refresh token is immediately invalidated.

Request (form-encoded)

grant_type=refresh_token&refresh_token=art_xxxxxxxx&client_id=...

Response 200

{
  "access_token": "aat_eyJhbGciOi...",
  "refresh_token": "art_yyyyyyyy",
  "token_type": "Bearer",
  "expires_in": 600
}
Error HTTP Condition
UNAUTHORIZED 401 Invalid or already-used refresh token

Important: Always save the new refresh_token. The old one is immediately invalidated.


Cross-Account Share (Console-only, Phase Z 2026-05-22)

The legacy anonymous inv_ tokens were removed. For inviting a registered user into a group, use POST /console/group-invites/redeem (the invitee redeems in their own Console session). Cross-account topic/script access is established through two Console actions, both requiring a signed-in session (no anonymous-receiver flow):

  1. Topic owner calls POST /console/aliases/{ali_id}/share-trust with { "grantee_email": "..." }. The grantee must already be a registered AgenTrux user.
  2. Grantee then calls POST /console/grants with { topic_id, script_id, action } in their own Console session.

For Script lending (delegated execution), use POST /console/scripts/{scr_id}/share with { grantee_email } directly — the script_delegations row is created server-side.


Data Plane

Event send/receive on topics. All endpoints require JWT authentication.

Authorization: Bearer <access_token>

JWT scope: topic:{topic_id}:read or topic:{topic_id}:write


GET /topics

List every topic this script can access, with human-readable names. Used by workflow plugins (MCP, Agent SDK, etc.) to populate a topic selector without going through the Console (Kratos) API.

Auth: any valid script JWT. The response is built from the JWT scope claim — no extra DB authorization step.

Request

GET /topics
Authorization: Bearer <access_token>

Response 200

{
  "items": [
    {
      "topic_id": "top_019d5ec8-dc41-7723-9761-6a40eacc858a",
      "name": "oc-command",
      "display_name": "OpenClaw Command",
      "retention_seconds": 86400,
      "actions": ["read", "write"]
    }
  ]
}
Field Type Description
topic_id UUID Topic identifier
name string Topic name
display_name string Human-readable name (currently always mirrors name)
retention_seconds int Retention in seconds
actions string[] Granted actions (subset of ["read", "write"])

Results are sorted by (name, topic_id) for stable positional ordering. A script granted both read and write on the same topic appears as one entry with actions: ["read", "write"]. Soft-deleted topics are dropped silently so a stale grant does not break the dropdown.


GET /console/grants

List the grants visible in the current Console session, joined with topic metadata. Intended for admin / diagnostic UIs that need to answer "what permissions do I have, who gave them to me, and what are the per-grant limits" — workflow plugins should prefer GET /topics for dropdowns.

Auth: Console session (Kratos). This is a Console-plane endpoint, not a script-JWT data-plane one.

Request

GET /console/grants
Cookie: <console session>

Response 200

{
  "items": [
    {
      "grant_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
      "topic_id": "top_019d5ec8-dc41-7723-9761-6a40eacc858a",
      "topic_name": "oc-command",
      "topic_display_name": "OpenClaw Command",
      "action": "read",
      "grantor_alias_id": "...",
      "description": "OpenClaw command bus",
      "rate_limit_per_min": 60,
      "daily_limit": 10000,
      "created_at": "2026-04-08T17:52:48+00:00"
    }
  ]
}
Field Type Description
grant_id UUID Grant identifier
topic_id UUID Topic the grant covers
topic_name string Topic name (joined from topic_repo)
topic_display_name string Topic display name
action string "read" or "write"
grantor_alias_id UUID Alias that issued the grant
description string Free-form description
rate_limit_per_min int | null Grant-level per-minute cap (null = unlimited)
daily_limit int | null Grant-level per-day cap (null = unlimited)
created_at ISO 8601 When the grant was created

One entry per active (topic, action) pair. Wildcard (topic_id = "*") grant rows are not returned — build_scope_with_grants already explodes them per-topic at JWT issuance, and the wildcard row itself is not directly callable. Soft-deleted grants, soft-deleted topics, and grants outside the JWT scope are dropped. Sorted by (topic_name, action, grant_id).


POST /topics/{topic_id}/events

Send an event to a topic.

Auth: topic:{topic_id}:write

Request

{
  "event_type": "sensor.reading",
  "payload": {"temperature": 23.5, "unit": "celsius"},
  "metadata": {"source": "edge-01"}
}

Provide either payload (inline path) or payload_object_id (object-ref path), not both.

Field Type Required Description
event_type string No Event type label (max 128 chars)
payload any JSON value Conditional Inline JSON payload (required for the inline path)
payload_object_id string (pob_) Conditional Reference to a pre-uploaded payload object (required for the object-ref path)
metadata object No Free-form metadata object

Response 200

{
  "event_id": "evt_...",
  "topic_id": "top_...",
  "stored_at": "2026-03-11T12:00:00+00:00",
  "ttl_expires_at": "2026-03-12T12:00:00+00:00",
  "payload_kind": "inline",
  "inline_size_bytes": 48,
  "next_read_cursor": "..."
}

For the object-ref path, payload_kind is "object_ref" and the response includes payload_object_id (pob_...) and size_bytes instead of inline_size_bytes.

Error HTTP Condition
UNAUTHORIZED 401 Invalid or expired JWT
FORBIDDEN 403 No write scope
NOT_FOUND 404 Topic does not exist
PAYLOAD_TOO_LARGE 413 Payload exceeds size limit
RATE_LIMITED 429 Publish rate limit exceeded

GET /topics/{topic_id}/events

Retrieve events with cursor-based pagination.

Auth: topic:{topic_id}:read

Query Parameters

Parameter Type Default Description
after cursor null Fetch events strictly after this cursor
before cursor null Fetch events strictly before this cursor
limit int (>0, capped at 1000) 100 Number of events to fetch
order string asc Sort order: asc or desc
since RFC3339 null Only events at/after this timestamp
until RFC3339 null Only events before this timestamp

Response 200

{
  "events": [
    {
      "event_id": "evt_...",
      "event_type": "sensor.reading",
      "stored_at": "2026-03-11T12:00:00+00:00",
      "ttl_expires_at": "2026-03-12T12:00:00+00:00",
      "payload_kind": "inline",
      "payload": {"temperature": 23.5},
      "producer_script_id": "scr_...",
      "cursor": "..."
    }
  ],
  "next": {
    "after": "<opaque-cursor>",
    "before": null,
    "has_more": true,
    "url": "/topics/top_.../events?after=...&limit=100",
    "frontier": "<opaque-cursor>"
  },
  "topic": {
    "topic_id": "top_...",
    "oldest_available_evt_id": "evt_..."
  }
}

Events are returned under the top-level events array. Pagination uses the opaque cursor token in next.after (not the evt_id string): pass it as the after query param, or follow next.url directly, to fetch the next page. The next object also carries before (opaque cursor for the reverse direction, or null), has_more (boolean), and frontier (the current watermark cursor, for tailing new events). The topic object reports the topic_id and oldest_available_evt_id (or null if the topic is empty). Each event also carries its own cursor.

Error HTTP Condition
UNAUTHORIZED 401 Invalid JWT
FORBIDDEN 403 No read scope
NOT_FOUND 404 Topic does not exist
PAYMENT_REQUIRED 402 Monthly egress quota exhausted (details.metric = "egress_bytes")
RATE_LIMITED 429 Rate limit exceeded

Reads (this endpoint, GET /events/{event_id}, and GET /payloads/{object_id}) return 402 PAYMENT_REQUIRED with details.metric = "egress_bytes" once the monthly egress quota is exhausted.


GET /topics/{topic_id}/events/stream

Receive hint notifications for new events via SSE (Server-Sent Events).

Auth: topic:{topic_id}:read

Query Parameters / Headers

Parameter Type Description
Last-Event-ID string Resume position: the opaque cursor from a prior id: field (sent as the Last-Event-ID request header)

Response 200 text/event-stream

event: hint
id: <opaque-cursor>
data: {"topic_id":"top_...","event_id":"evt_...","ts":"2026-01-01T00:00:00Z","producer_script_id":"scr_..."}

: keepalive

GET /topics/{topic_id}/events/{event_id}

Get a single event by ID.

Auth: topic:{topic_id}:read

Response 200

{
  "event_id": "evt_...",
  "event_type": "sensor.reading",
  "stored_at": "2026-03-11T12:00:00+00:00",
  "ttl_expires_at": "2026-03-12T12:00:00+00:00",
  "payload_kind": "inline",
  "payload": {"temperature": 23.5},
  "producer_script_id": "scr_...",
  "cursor": "..."
}

Historical backfill

There is no separate by-sequence endpoint. To backfill missed events after a network interruption, page through GET /topics/{topic_id}/events?after=<cursor> using the cursor-based read, resuming from the last cursor you successfully processed.


POST /topics/{topic_id}/payloads

Create external payload metadata and get a presigned upload URL.

Auth: topic:{topic_id}:write

Request

{
  "content_type": "application/octet-stream",
  "size_bytes": 1048576,
  "checksum_sha256": "abc123..."
}
Field Type Required Description
size_bytes int (>0) Yes Size in bytes
content_type string No MIME type
checksum_sha256 string Yes SHA-256 checksum of the object

Response 201

{
  "object_id": "...",
  "upload_url": "https://...",
  "expiration": "2026-03-12T12:00:00+00:00"
}

Upload the file via a PUT request to upload_url.

Error HTTP Condition
RATE_LIMITED 429 Presigned URL issuance is rate-limited per tier per hour (Free 10/h; Starter/Pro/Business 100/h; Enterprise unlimited). Returns a Retry-After header.

GET /topics/{topic_id}/payloads/{object_id}

Get payload metadata and a presigned download URL.

Auth: topic:{topic_id}:read

Response 200

{
  "object_id": "...",
  "content_type": "application/octet-stream",
  "size": 1048576,
  "hash": "sha256:abc123...",
  "download_url": "https://...",
  "expiration": "2026-03-12T12:00:00+00:00"
}

Console

Console endpoints are used by the web UI. They require session-based authentication (Kratos).

GET /console/connections/sse

SSE endpoint for the Composer. Streams real-time event notifications for topics that the current user's Alias owns or has access to. Used by the console Composer for live event updates. To read the actual events, use GET /console/topics/{topic_id}/events.

Auth: Session cookie (Kratos) — the user must have read access to the topic via their active Alias.

Query Parameters / Headers

Parameter Type Description
Last-Event-ID string Resume position: the opaque cursor from a prior id: field (sent as the Last-Event-ID request header)

Response 200 text/event-stream

event: hint
id: <opaque-cursor>
data: {"topic_id":"top_...","event_id":"evt_...","ts":"2026-01-01T00:00:00Z","producer_script_id":"scr_..."}

: keepalive

AccountResponse

The AccountResponse schema (returned by account-related console endpoints) includes the following fields:

Field Type Description
account_id UUID Account ID
name string Account slug (unique identifier)
display_name string | null Display name
email string | null Contact email
phone string | null Phone number
real_name string | null Legal / real name
address string | null Postal address
date_of_birth string | null Date of birth (ISO date string)
gender string | null Gender
created_at datetime Account creation timestamp

These profile fields can be updated via PATCH /console/me/profile, sending a JSON body containing the profile fields to change.


Error Responses

All endpoints use a unified error format.

{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid credentials"
  }
}
Code HTTP Description
UNAUTHORIZED 401 Invalid or expired JWT / credentials
FORBIDDEN 403 Valid JWT but insufficient scope
SUSPENDED 403 Account, Alias, or script is suspended
NOT_FOUND 404 Resource does not exist
CONFLICT 409 Token already used / grant already exists
INVALID 422 Validation error
RATE_LIMITED 429 Rate limit exceeded
PAYLOAD_TOO_LARGE 413 Payload size exceeded
SERVICE_UNAVAILABLE 503 Backend dependency is down
INTERNAL 500 Unexpected server error

Webhook Notifications

When an event is published to a topic, registered webhook URLs receive a notification (configured via the management console).

Payload

{
  "topic_id": "top_...",
  "event_id": "evt_...",
  "timestamp": 1710151200,
  "delivery": "webhook"
}

Signature Verification

The X-AgenTrux-Signature header carries the timestamp and an HMAC-SHA256 signature (Stripe-compatible format).

X-AgenTrux-Signature: t={timestamp},v1={hex_digest}

Verification steps:

  1. Parse t (unix timestamp) and v1 (hex signature) from the header
  2. Compute HMAC-SHA256(secret, "{t}." + raw_request_body) using the subscription's webhook secret as the key
  3. Compare with v1 using a constant-time comparison