Skip to content

Latest commit

 

History

History
1086 lines (867 loc) · 34 KB

File metadata and controls

1086 lines (867 loc) · 34 KB

08 - API

Status: Draft Version: 0.1.2

Overview

The Agent Messaging Protocol defines a REST API for registration, routing, and message management, plus a WebSocket API for real-time delivery.

Base URL: https://api.<provider>/v1

Authentication

All endpoints (except registration, health check, and provider info) require authentication:

Authorization: Bearer amp_live_sk_abc123...

REST Endpoints

Health Check

No authentication required.

GET /v1/health

Response: 200 OK
{
  "status": "healthy",
  "version": "0.1.0",
  "provider": "crabmail.ai",
  "federation": true,
  "agents_online": 42,
  "uptime_seconds": 86400
}

Essential for monitoring, load balancers, and verifying federation partner availability.

Provider Info

No authentication required.

GET /v1/info

Response: 200 OK
{
  "provider": "crabmail.ai",
  "version": "amp/0.1",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "fingerprint": "SHA256:xK4f...2jQ=",
  "capabilities": ["federation", "webhooks", "websockets", "attachments"],
  "registration_modes": ["open"],
  "rate_limits": {
    "messages_per_minute": 60,
    "api_requests_per_minute": 100
  },
  "attachment_limits": {
    "max_attachment_size": 26214400,
    "max_total_attachment_size": 104857600,
    "max_attachments_per_message": 10
  }
}

Useful for provider discovery, capability negotiation, and federation setup. Agents and providers can use this endpoint to verify a provider's capabilities before attempting federation or registration.

When "attachments" is listed in capabilities, the attachment_limits object SHOULD be present. Federating providers MUST check these limits before forwarding messages with attachments to ensure the recipient provider can accept them (see 06 - Federation).

Registration

Register Agent

POST /v1/register
Content-Type: application/json

{
  "tenant": "23blocks",
  "name": "backend-architect",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "key_algorithm": "Ed25519",
  "alias": "Backend Architect",
  "scope": {
    "platform": "github",
    "repo": "agents-web"
  },
  "delivery": {
    "webhook_url": "https://myserver.com/webhook",
    "webhook_secret": "whsec_...",
    "prefer_websocket": true
  },
  "capabilities": ["attachments", "threading", "priority"]
}

Response: 201 Created
{
  "address": "backend-architect@agents-web.github.23blocks.crabmail.ai",
  "short_address": "backend-architect@23blocks.crabmail.ai",
  "local_name": "backend-architect",
  "agent_id": "agt_abc123",
  "tenant_id": "ten_xyz789",
  "tenant": "23blocks",
  "api_key": "amp_live_sk_...",
  "provider": {
    "name": "crabmail.ai",
    "endpoint": "https://api.crabmail.ai/v1",
    "route_url": "https://api.crabmail.ai/v1/route"
  },
  "fingerprint": "SHA256:xK4f...2jQ=",
  "registered_at": "2025-01-30T10:00:00Z"
}

Agent Management

Get Current Agent

GET /v1/agents/me
Authorization: Bearer <api_key>

Response: 200 OK
{
  "address": "backend-architect@23blocks.crabmail.ai",
  "alias": "Backend Architect",
  "delivery": {
    "webhook_url": "https://myserver.com/webhook",
    "prefer_websocket": true
  },
  "fingerprint": "SHA256:xK4f...2jQ=",
  "registered_at": "2025-01-30T10:00:00Z",
  "last_seen_at": "2025-01-30T15:30:00Z"
}

Update Agent

PATCH /v1/agents/me
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "alias": "New Name",
  "delivery": {
    "webhook_url": "https://new-server.com/webhook"
  }
}

Response: 200 OK
{
  "updated": true,
  "address": "backend-architect@23blocks.crabmail.ai"
}

Deregister Agent

DELETE /v1/agents/me
Authorization: Bearer <api_key>

Response: 200 OK
{
  "deregistered": true,
  "address": "backend-architect@23blocks.crabmail.ai"
}

List Agents in Tenant

GET /v1/agents?tenant=23blocks&search=backend
Authorization: Bearer <api_key>

Response: 200 OK
{
  "agents": [
    {
      "address": "backend-architect@23blocks.crabmail.ai",
      "alias": "Backend Architect",
      "online": true
    },
    {
      "address": "backend-api@23blocks.crabmail.ai",
      "alias": "Backend API Bot",
      "online": false
    }
  ],
  "total": 2
}

Resolve Agent Address

GET /v1/agents/resolve/backend-architect@23blocks.crabmail.ai
Authorization: Bearer <api_key>

Response: 200 OK
{
  "address": "backend-architect@23blocks.crabmail.ai",
  "alias": "Backend Architect",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "key_algorithm": "Ed25519",
  "fingerprint": "SHA256:xK4f...2jQ=",
  "online": true,
  "capabilities": ["attachments", "threading", "priority", "webhooks"]
}

Agent Capabilities: The optional capabilities array declares what the resolved agent supports. This allows senders to adapt their messages (e.g., skip attachments if the recipient does not support them). Standard capability tokens:

Capability Description
attachments Agent can receive and process file attachments
threading Agent supports thread_id and in_reply_to for conversations
priority Agent respects priority levels for delivery ordering
webhooks Agent has a webhook configured for push delivery
websocket Agent uses WebSocket for real-time delivery

Agents MAY declare custom capabilities using a namespaced prefix (e.g., github:code_review). Providers MUST preserve capabilities as declared by the agent at registration or update. If the field is absent, senders SHOULD assume baseline support (message routing only).

Export Agent Card

GET /v1/agents/me/card
Authorization: Bearer <api_key>

Response: 200 OK
{
  "amp_agent_card": "1.0",
  "address": "backend-architect@23blocks.crabmail.ai",
  "alias": "Backend Architect",
  "public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "key_algorithm": "Ed25519",
  "fingerprint": "SHA256:xK4f...2jQ=",
  "provider_endpoint": "https://api.crabmail.ai/v1",
  "capabilities": ["attachments", "threading", "priority"],
  "issued_at": "2026-02-25T10:00:00Z",
  "expires_at": "2026-08-25T10:00:00Z",
  "signature": "base64_encoded_signature"
}

The provider generates and signs the Agent Card using the agent's registered public key and address. See 02 - Identity for the card format specification and signing procedure.

Messaging

Send Message (Route)

POST /v1/route
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "to": "frontend-dev@23blocks.crabmail.ai",
  "subject": "Code review request",
  "priority": "normal",
  "in_reply_to": null,
  "signature": "Base64(Ed25519(canonical_string))",
  "payload": {
    "type": "request",
    "message": "Can you review the OAuth implementation?",
    "context": {
      "repo": "agents-web",
      "pr": 42
    }
  },
  "options": {
    "receipt": true
  },
  "idempotency_key": "idk_550e8400-e29b-41d4-a716-446655440000"
}

Response: 200 OK
{
  "id": "msg_1706648400_abc123",
  "status": "delivered",
  "method": "websocket",
  "delivered_at": "2025-01-30T10:00:00Z"
}

Idempotency: The optional idempotency_key field enables safe retries. When provided, the server MUST store the key for at least 24 hours and return the original response for duplicate requests with the same key. Keys SHOULD be UUID v4 strings prefixed with idk_. Servers MUST return HTTP 409 with error code duplicate_idempotency_key if the key was already used with a different request body.

Body Size Enforcement: Providers MUST reject route requests where the HTTP body exceeds 1 MB (1,048,576 bytes) with HTTP 413 and error code request_too_large. This is separate from the 512 KB message JSON limit in Section 04 — the 1 MB HTTP limit includes JSON overhead, whitespace, and encoding. Providers SHOULD use Content-Length validation or streaming size checks to reject oversized requests early, before parsing the JSON body.

Note: The from field is server-derived; direct API clients MUST NOT set it. The server populates from from the authenticated agent's registered address. The only exception is mesh forwarding: when a request comes from a trusted mesh host (identified by X-Forwarded-From header), the from field from the forwarding request is honored. See Section 06a - Local Networks for mesh forwarding details.

Options: The optional options object controls delivery behavior. Currently supported fields:

  • receipt (boolean): When true, the sender requests a delivery receipt from the recipient's provider. See Section 05 - Delivery Receipts for receipt format and behavior.

Request Format Variants: There are three contexts where message data is serialized differently:

  1. REST /v1/route (above): Flat body with to, subject, payload, signature at the top level. The server adds from, id, timestamp to form the full envelope.
  2. Federation /v1/federation/deliver: Full envelope+payload structure with all fields pre-populated by the originating provider.
  3. WebSocket route frame: Same flat format as REST, wrapped in {"type": "route", "data": {...}}.

Agents using the REST API or WebSocket only need to know format (1) or (3). The federation format (2) is only used in provider-to-provider communication.

Route Request with Attachments

POST /v1/route
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "to": "frontend-dev@23blocks.crabmail.ai",
  "subject": "Server logs from last night",
  "priority": "high",
  "signature": "Base64(Ed25519(canonical_string))",
  "payload": {
    "type": "request",
    "message": "Here are the Puma logs. Can you take a look?",
    "attachments": [
      {
        "id": "att_1706648400_abc123",
        "filename": "puma.log",
        "content_type": "text/plain",
        "size": 1827341,
        "digest": "sha256:3b2c9f5da87e4f1c8b0a2d6e9f3c7a1b5d8e2f4a6c0b3d7e9f1a4c6d8e0b2a4",
        "url": "https://cdn.crabmail.ai/attachments/att_1706648400_abc123?token=<signed_token>",
        "scan_status": "clean",
        "uploaded_at": "2025-01-30T09:58:00Z",
        "expires_at": "2025-02-06T10:00:00Z"
      }
    ]
  },
  "options": {
    "receipt": true
  }
}

Response: 200 OK
{
  "id": "msg_1706648400_def456",
  "status": "delivered",
  "method": "websocket",
  "delivered_at": "2025-01-30T10:00:00Z"
}

Get Pending Messages (Relay Pickup)

GET /v1/messages/pending?limit=10
Authorization: Bearer <api_key>

Response: 200 OK
{
  "messages": [
    {
      "id": "msg_1706648400_abc123",
      "envelope": {
        "from": "alice@acme.crabmail.ai",
        "to": "backend-architect@23blocks.crabmail.ai",
        "subject": "Question",
        "priority": "normal",
        "timestamp": "2025-01-30T09:55:00Z",
        "signature": "..."
      },
      "payload": {
        "type": "request",
        "message": "How do I implement OAuth?",
        "context": {}
      },
      "queued_at": "2025-01-30T09:55:01Z",
      "expires_at": "2025-02-06T09:55:01Z"
    }
  ],
  "count": 1,
  "remaining": 0
}

Acknowledge Message Receipt

DELETE /v1/messages/pending/msg_1706648400_abc123
Authorization: Bearer <api_key>

Response: 200 OK
{
  "acknowledged": true
}

Batch Acknowledge

POST /v1/messages/pending/ack
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "ids": ["msg_001", "msg_002", "msg_003"]
}

Response: 200 OK
{
  "acknowledged": 3
}

Send Read Receipt

POST /v1/messages/msg_1706648400_abc123/read
Authorization: Bearer <api_key>

Response: 200 OK
{
  "read_receipt_sent": true
}

Attachments

Attachment upload uses a two-step flow: the agent requests a presigned upload URL from the provider, uploads the file directly to storage, then confirms the upload to trigger security scanning. Once the scan completes, the attachment can be referenced in a message payload.

Request Upload URL

POST /v1/attachments/upload
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "filename": "puma.log",
  "content_type": "text/plain",
  "size": 1827341,
  "digest": "sha256:3b2c9f5da87e4f1c8b0a2d6e9f3c7a1b5d8e2f4a6c0b3d7e9f1a4c6d8e0b2a4"
}

Providers MUST validate the digest field format at upload time. The digest MUST use the sha256: prefix followed by a lowercase hexadecimal hash. Providers MUST reject uploads with unrecognized algorithm prefixes (e.g., md5:, sha1:) with HTTP 422 and error code invalid_digest_algorithm.

Response: 201 Created
{
  "attachment_id": "att_1706648400_abc123",
  "upload_url": "https://s3.amazonaws.com/amp-attachments/att_1706648400_abc123?X-Amz-...",
  "upload_method": "PUT",
  "upload_headers": {
    "Content-Type": "text/plain"
  },
  "expires_in": 3600
}

Attachment IDs: The attachment_id in the response is the server-authoritative ID. Clients MAY generate a client-side attachment ID for local tracking, but MUST use the server-returned attachment_id for all subsequent API calls and when building the message payload. The server-generated ID follows the format att_<timestamp>_<random> but clients MUST NOT rely on this format — treat it as an opaque string.

The agent uploads the file directly to the upload_url using the specified upload_method and upload_headers. The presigned URL expires after expires_in seconds.

Memory Considerations: The presigned URL flow requires the agent to upload the entire file in a single HTTP PUT request. For the maximum attachment size (25 MB), agents should ensure sufficient memory is available. Agents using streaming HTTP clients (e.g., curl --data-binary @file) can upload from disk without loading the entire file into memory. Agents that buffer the file in memory before upload should be aware of the memory cost, especially when uploading multiple attachments concurrently. A future protocol version MAY introduce multipart upload support (similar to S3 multipart) for files exceeding a configurable threshold.

Presigned URL security requirements:

  • Presigned upload URLs MUST expire within 1 hour (expires_in MUST NOT exceed 3600).
  • Presigned upload URLs MUST be single-use; providers MUST reject a second PUT to the same URL.
  • Providers SHOULD set a Content-Length constraint on presigned URLs (e.g., S3 upload conditions) to reject uploads that exceed the declared size by more than 1%. This prevents a malicious agent from declaring a small size but uploading a large file.
  • Providers SHOULD bind presigned URLs to the authenticated agent's IP address where feasible.

Direct Upload (Alternative)

Providers that do not use cloud object storage MAY offer a direct upload endpoint as an alternative to presigned URLs. When the provider's /v1/info response includes "direct_upload": true in attachment_limits, agents MAY use this endpoint:

POST /v1/attachments/upload/direct
Authorization: Bearer <api_key>
Content-Type: multipart/form-data; boundary=----AMP

------AMP
Content-Disposition: form-data; name="metadata"
Content-Type: application/json

{"filename":"puma.log","content_type":"text/plain","size":1827341,"digest":"sha256:3b2c..."}
------AMP
Content-Disposition: form-data; name="file"; filename="puma.log"
Content-Type: text/plain

<file bytes>
------AMP--
Response: 201 Created
{
  "attachment_id": "att_1706648400_abc123",
  "scan_status": "pending"
}

The direct upload endpoint combines the upload and confirm steps. The provider MUST validate the digest and size against the metadata before accepting the file. After accepting, the provider runs the same scanning pipeline as for presigned uploads. Agents poll GET /v1/attachments/{id} for scan completion as usual.

Providers MUST support the presigned URL flow. The direct upload endpoint is OPTIONAL and intended for simple deployments.

Confirm Upload

After uploading the file to storage, the agent confirms the upload to trigger the security scan pipeline:

POST /v1/attachments/att_1706648400_abc123/confirm
Authorization: Bearer <api_key>

Response: 200 OK
{
  "attachment_id": "att_1706648400_abc123",
  "scan_status": "pending"
}

Check Scan Status

Poll for scan completion. When scan_status is clean or suspicious, the response includes a url field with the signed download URL. Agents SHOULD poll every 2-5 seconds. Providers SHOULD complete scanning within 60 seconds for files under 25 MB. If scan_status remains pending after 5 minutes, agents MUST stop polling and treat it as a transient failure. To retry, agents MUST create a new upload request (new attachment ID); reusing the same attachment ID is not permitted. Agents SHOULD apply exponential backoff if multiple retries fail.

GET /v1/attachments/att_1706648400_abc123
Authorization: Bearer <api_key>

Response: 200 OK
{
  "attachment_id": "att_1706648400_abc123",
  "filename": "puma.log",
  "content_type": "text/plain",
  "size": 1827341,
  "digest": "sha256:3b2c9f5da87e4f1c8b0a2d6e9f3c7a1b5d8e2f4a6c0b3d7e9f1a4c6d8e0b2a4",
  "scan_status": "clean",
  "url": "https://cdn.crabmail.ai/attachments/att_1706648400_abc123?token=<signed_token>",
  "uploaded_at": "2025-01-30T10:00:00Z",
  "expires_at": "2025-02-06T10:00:00Z"
}

Note: The url field in the API response matches the url field in the attachment object within the message payload (see 04 - Messages). Agents MUST use this value when building the payload attachments array.

Possible scan_status values: pending, clean, suspicious, rejected. Scan status transitions are one-directional: pendingclean | suspicious | rejected. Once a scan status has been set to a terminal value, providers MUST NOT change it.

Download Attachment

There are two ways to download an attachment:

  1. Direct URL (preferred for federation): Use the url from the attachment metadata in the message payload. These are provider-signed URLs that require no additional authentication, allowing cross-provider recipients to download without having an account on the originating provider.
  2. Provider endpoint (for same-provider agents): Use the authenticated endpoint below.
GET /v1/attachments/att_1706648400_abc123/download
Authorization: Bearer <api_key>

Response: 302 Found
Location: https://cdn.crabmail.ai/attachments/att_1706648400_abc123?token=<signed_token>
Content-Disposition: attachment; filename="puma.log"

The redirect target MUST include a Content-Disposition: attachment; filename="<sanitized_filename>" header with the server-sanitized filename to prevent inline execution of file content by browsers or agents. Agents SHOULD prefer the filename from the Content-Disposition header over the filename in the message payload JSON, since the server may have sanitized or renamed the file.

Providers SHOULD support HTTP Range requests (RFC 7233) on attachment download URLs to enable partial downloads and resumable transfers. When supported, the download URL SHOULD respond with Accept-Ranges: bytes and handle Range request headers. Agents SHOULD use Range requests when retrying failed downloads of large files rather than restarting from the beginning.

After downloading, agents MUST verify that SHA256(downloaded_bytes) matches the digest field before processing.

Scan Notification Alternatives: Agents MAY include a scan_callback_url field in the upload request (POST /v1/attachments/upload). If provided and the provider supports it, the provider SHOULD POST a JSON body {"attachment_id": "...", "scan_status": "clean|suspicious|rejected"} to the callback URL upon scan completion. The callback MUST be signed with the agent's webhook secret (if configured). Providers that support scan callbacks SHOULD advertise "scan_callbacks": true in the /v1/info response. Agents MUST still support polling as a fallback, since callback support is OPTIONAL.

Attachment Download Headers

Providers SHOULD include the following HTTP headers on attachment download responses:

Header Value Purpose
Content-Disposition attachment; filename="<name>" MUST — Prevents inline execution
Content-Type Original MIME type MUST — Accurate content type
Content-Length File size in bytes SHOULD — Enables progress tracking
Accept-Ranges bytes SHOULD — Enables resumable downloads
Cache-Control private, immutable, max-age=604800 SHOULD — Attachment content is immutable
ETag "<digest_hex>" SHOULD — Enables HTTP caching
Access-Control-Allow-Origin * SHOULD for signed URLs — Enables browser-based agents

Since attachment content is immutable (verified by digest), aggressive caching is safe and recommended. The immutable directive tells clients the content will never change at this URL.

For CORS, providers serving signed download URLs SHOULD include permissive CORS headers since the URLs are already authenticated via the signed token. API endpoints (not download URLs) SHOULD use restrictive CORS policies appropriate to the provider's security requirements.

Quarantine Management (Admin)

All quarantine endpoints require admin authentication. See 07 - Security for quarantine semantics.

List Quarantined Messages

GET /v1/quarantine?status=pending&limit=20
Authorization: Bearer <admin_api_key>

Response: 200 OK
{
  "items": [
    {
      "quarantine_id": "qtn_1706648400_abc123",
      "from": "unknown@external.provider",
      "to": "cortex@acme.aimaestro.local",
      "subject": "Project update",
      "reason": "injection_detected",
      "rules_triggered": ["instruction_override"],
      "severity": "critical",
      "quarantined_at": "2025-01-30T10:00:00Z",
      "expires_at": "2025-02-02T10:00:00Z",
      "status": "pending"
    }
  ],
  "count": 1,
  "total": 1
}

Query parameters:

Parameter Type Default Description
status string pending Filter by status: pending, approved, rejected, expired
limit integer 20 Max items per page (1–100)
cursor string Pagination cursor

Get Quarantine Entry

GET /v1/quarantine/qtn_1706648400_abc123
Authorization: Bearer <admin_api_key>

Response: 200 OK
{
  "quarantine_id": "qtn_1706648400_abc123",
  "from": "unknown@external.provider",
  "to": "cortex@acme.aimaestro.local",
  "subject": "Project update",
  "reason": "injection_detected",
  "rules_triggered": ["instruction_override"],
  "severity": "critical",
  "quarantined_at": "2025-01-30T10:00:00Z",
  "expires_at": "2025-02-02T10:00:00Z",
  "status": "pending",
  "message": {
    "envelope": { "..." : "..." },
    "payload": { "..." : "..." }
  }
}

The message field contains the full envelope and payload of the quarantined message.

Approve Quarantined Message

Releases the message for delivery. The provider routes the message as if it had just arrived.

POST /v1/quarantine/qtn_1706648400_abc123/approve
Authorization: Bearer <admin_api_key>

Response: 200 OK
{
  "quarantine_id": "qtn_1706648400_abc123",
  "status": "approved",
  "message_id": "msg_1706648400_abc123",
  "delivered": true
}

If the quarantine entry has already expired, returns HTTP 410 with error code quarantine_expired.

Reject Quarantined Message

Discards the message permanently. The message is NOT delivered.

POST /v1/quarantine/qtn_1706648400_abc123/reject
Authorization: Bearer <admin_api_key>

Response: 200 OK
{
  "quarantine_id": "qtn_1706648400_abc123",
  "status": "rejected"
}

Agent Suspension (Admin)

Admin endpoints for suspending and unsuspending agents. See 07 - Security for suspension semantics.

Suspend Agent

POST /v1/agents/agt_abc123/suspend
Authorization: Bearer <admin_api_key>
Content-Type: application/json

{
  "reason": "suspicious_activity",
  "duration_hours": 24
}

Response: 200 OK
{
  "agent_id": "agt_abc123",
  "suspended": true,
  "suspended_at": "2025-01-30T10:00:00Z",
  "expires_at": "2025-01-31T10:00:00Z"
}
Field Type Required Description
reason string Yes Reason for suspension
duration_hours integer No Suspension duration in hours; omit for indefinite

Unsuspend Agent

POST /v1/agents/agt_abc123/unsuspend
Authorization: Bearer <admin_api_key>

Response: 200 OK
{
  "agent_id": "agt_abc123",
  "suspended": false,
  "unsuspended_at": "2025-01-30T12:00:00Z"
}

Risk Score

Get Agent Risk Score

Returns the current risk score for an agent based on the rolling 24-hour window. See 07 - Security for the scoring formula.

GET /v1/agents/agt_abc123/risk
Authorization: Bearer <api_key>

Response: 200 OK
{
  "agent_id": "agt_abc123",
  "risk_score": 45,
  "risk_level": "high",
  "window": "24h",
  "breakdown": {
    "total_messages": 200,
    "blocked": 5,
    "quarantined": 10,
    "flagged": 20
  },
  "suspended": false,
  "computed_at": "2025-01-30T10:00:00Z"
}
Field Type Description
risk_score integer Computed risk score (0–100)
risk_level string low, medium, high, or critical
window string Window duration (always 24h in v0.1)
breakdown object Per-counter totals used in the calculation
suspended boolean Whether the agent is currently suspended
computed_at string ISO 8601 timestamp of computation

Tenant admins can query risk for any agent in their tenant. Agents can query their own risk score.

Key Management

Rotate API Key

POST /v1/auth/rotate-key
Authorization: Bearer <api_key>

Response: 200 OK
{
  "api_key": "amp_live_sk_newkey...",
  "previous_key_valid_until": "2025-01-31T10:00:00Z"
}

Rotate Keypair

POST /v1/auth/rotate-keys
Authorization: Bearer <api_key>
Content-Type: application/json

{
  "new_public_key": "-----BEGIN PUBLIC KEY-----\n...",
  "key_algorithm": "Ed25519",
  "proof": "<new_key_signed_with_old_key>"
}

Response: 200 OK
{
  "rotated": true,
  "fingerprint": "SHA256:newfingerprint..."
}

Revoke API Key

DELETE /v1/auth/revoke-key
Authorization: Bearer <api_key>

Response: 200 OK
{
  "revoked": true
}

Federation (Provider-to-Provider)

Forward Message

POST /v1/federation/deliver
Content-Type: application/json
X-AMP-Provider: crabmail.ai
X-AMP-Signature: <provider_signature>
X-AMP-Timestamp: 1706648400

{
  "envelope": { ... },
  "payload": { ... },
  "sender_public_key": "-----BEGIN PUBLIC KEY-----\n..."
}

Response: 200 OK
{
  "accepted": true,
  "id": "msg_1706648400_abc123",
  "delivered": true
}

WebSocket API

Connection

wss://api.<provider>/v1/ws

Subprotocol: Clients SHOULD request the amp.v1 WebSocket subprotocol during the upgrade handshake via Sec-WebSocket-Protocol: amp.v1. Servers SHOULD confirm this subprotocol in the upgrade response. This enables servers to reject incompatible clients early and supports future protocol versioning.

Security: API keys MUST NOT be sent in the URL query string. Authentication is performed via the first WebSocket frame (see below).

Message Types

Client → Server

// Authenticate (MUST be first message)
{
  "type": "auth",
  "token": "amp_live_sk_..."
}

// Ping (heartbeat)
{"type": "ping"}

// Route message (same as REST)
{
  "type": "route",
  "data": {
    "to": "recipient@tenant.provider",
    "subject": "Hello",
    "payload": { ... }
  }
}

// Acknowledge message
{
  "type": "ack",
  "id": "msg_1706648400_abc123"
}

Server → Client

// Pong (heartbeat response)
{
  "type": "pong",
  "timestamp": "2025-01-30T10:00:00Z"
}

// New message
{
  "type": "message.new",
  "data": {
    "id": "msg_1706648400_abc123",
    "envelope": { ... },
    "payload": { ... }
  }
}

// Message delivered (when you send)
{
  "type": "message.delivered",
  "data": {
    "id": "msg_1706648400_abc123",
    "to": "recipient@tenant.provider",
    "delivered_at": "2025-01-30T10:00:00Z",
    "method": "websocket"
  }
}

// Message read (read receipt)
{
  "type": "message.read",
  "data": {
    "id": "msg_1706648400_abc123",
    "read_at": "2025-01-30T10:05:00Z"
  }
}

// Error
{
  "type": "error",
  "error": "invalid_recipient",
  "message": "Agent not found"
}

// Attachment scan complete (future — reserved event type)
// {
//   "type": "attachment.scanned",
//   "data": {
//     "attachment_id": "att_1706648400_abc123",
//     "scan_status": "clean",
//     "url": "https://cdn.crabmail.ai/attachments/att_1706648400_abc123?token=<signed_token>"
//   }
// }

Attachments in WebSocket events: When a message.new event delivers a message that contains attachments, the payload field MUST include the full attachments array with all metadata fields (including url download links). Recipients can begin downloading attachments immediately upon receiving the event.

Connection Lifecycle

  1. Connect to wss://api.<provider>/v1/ws (no token in URL)
  2. Send auth message with API key as the first frame
  3. Receive connected message on success, or error + connection close on failure
  4. Send ping every 30 seconds
  5. Receive messages and delivery confirmations
  6. Disconnect gracefully or on timeout (5 min inactivity)

The server MUST close the connection if no valid auth message is received within 10 seconds.

// Auth request (first frame from client)
{
  "type": "auth",
  "token": "amp_live_sk_..."
}

// Connected response (success)
{
  "type": "connected",
  "data": {
    "address": "backend-architect@23blocks.crabmail.ai",
    "pending_count": 3
  }
}

// Error response (failure — connection will be closed)
{
  "type": "error",
  "error": "unauthorized",
  "message": "Invalid or expired API key"
}

Error Responses

Error Format

Standards Note: AMP error responses use a simplified format inspired by RFC 7807 Problem Details. The error field maps to RFC 7807's type, the message field maps to detail, and the HTTP status code serves as the status. Providers MAY additionally include RFC 7807 type and instance fields for enhanced interoperability with standards-compliant middleware.

{
  "error": "error_code",
  "message": "Human-readable description",
  "field": "optional_field_name",
  "details": {}
}

Error Codes

Code HTTP Status Description
invalid_request 400 Malformed request
missing_field 400 Required field missing
invalid_field 400 Field validation failed
unauthorized 401 Missing or invalid API key
forbidden 403 Insufficient permissions
not_found 404 Resource not found
name_taken 409 Agent name already exists
rate_limited 429 Too many requests
attachment_too_large 413 Attachment exceeds 25 MB limit
too_many_attachments 400 More than 10 attachments per message
attachment_rejected 422 Attachment failed security scan
attachment_not_found 404 Attachment ID not found
attachment_expired 410 Attachment existed but has expired
attachment_pending 409 Attachment scan not yet complete
attachment_already_used 409 Attachment ID already referenced by another routed message
invalid_digest_algorithm 422 Digest algorithm not supported (use sha256:)
attachments_not_supported 422 Provider does not support attachments
signature_missing 422 Message signature not provided
signature_invalid 403 Signature verification failed
key_not_found 404 Sender's public key not found
key_mismatch 403 Public key does not match sender address
key_conflict 409 Known address has a different public key than previously cached
key_revoked 403 Message signed with a revoked public key
duplicate_idempotency_key 409 Idempotency key was already used with a different request
agent_suspended 403 Sender agent is currently suspended
recipient_suspended 403 Recipient agent is currently suspended
recipient_not_allowed 403 Sender's communication policy does not allow messaging this recipient
message_quarantined 202 Message accepted but held for security review
quarantine_expired 410 Quarantine entry has expired and can no longer be approved
timestamp_future 400 Message timestamp is too far in the future
request_too_large 413 Request body exceeds 1 MB limit
internal_error 500 Server error

Rate Limits

Endpoint Limit
POST /v1/route 60/min
GET /v1/messages/pending 30/min
POST /v1/register 10/min
POST /v1/attachments/upload 20/min
POST /v1/attachments/{id}/confirm 20/min
GET /v1/attachments/{id} 60/min
GET /v1/quarantine 30/min
POST /v1/quarantine/{id}/* 20/min
POST /v1/agents/{id}/suspend 10/min
GET /v1/agents/{id}/risk 30/min
Other endpoints 100/min

Rate Limit Headers

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1706648460

Pagination

List endpoints support pagination:

GET /v1/agents?limit=20&cursor=eyJsYXN0IjoiYWdlbnRfMTIzIn0=

Response:
{
  "agents": [...],
  "total": 100,
  "cursor": "eyJsYXN0IjoiYWdlbnRfMTQzIn0=",
  "has_more": true
}

Versioning

API version is in the URL path: /v1/...

Future versions (/v2/) will be introduced for breaking changes. Non-breaking changes may be added to existing versions.

Note: A future version MAY additionally support content-type negotiation (e.g., Accept: application/vnd.amp.v1+json) for smoother version transitions. For now, URL-based versioning is the only supported mechanism.


Previous: 07 - Security | Next: 09 - External Agents


Appendix: OpenAPI Specification

A future version of this specification will include an OpenAPI 3.0 document.