reference
API Reference
Complete HTTP API reference for all Nerfmail endpoints.
Base URL: https://api.nerfmail.com
All endpoints return JSON. Errors use the format { "error": { "code": "...", "message": "...", "details": {} } }.
Authentication
Admin Endpoints
Authorization: Bearer {ADMIN_TOKEN}Mailbox Endpoints
Authorization: Bearer nrfm_{keyId}.{secret}API keys are scoped to a single mailbox. The token format is nrfm_ prefix + key ID + . + base64url secret.
Account Management
Create Account
POST /v1/accountsCreates an account with a primary mailbox.
| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | Yes | URL-safe identifier (lowercase, alphanumeric, hyphens) |
displayName |
string | Yes | Human-readable account name |
Headers: Idempotency-Key (recommended)
Response (201):
{
"account": { "id", "slug", "display_name", "status", "created_at" },
"primary_mailbox": { "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" }
}Get Account
GET /v1/accounts/{accountId}Response (200):
{ "account": { "id", "slug", "display_name", "status", "created_at" } }List Mailboxes
GET /v1/accounts/{accountId}/mailboxesResponse (200):
{
"account": { "id", "slug", "display_name", "status" },
"mailboxes": [{ "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" }]
}Create Mailbox
POST /v1/accounts/{accountId}/mailboxes| Field | Type | Required | Description |
|---|---|---|---|
slug |
string | Yes | Mailbox slug (cannot be main or primary) |
Creates an agent mailbox with a default send_message action.
Response (201):
{ "mailbox": { "id", "slug", "kind", "email_address", "protocol_host", "status", "created_at" } }Get Mailbox
GET /v1/mailboxes/{mailboxId}Response (200):
{ "mailbox": { "id", "email_address", "protocol_host", "kind", "status", "created_at" } }Disable / Enable Mailbox
POST /v1/mailboxes/{mailboxId}/disable
POST /v1/mailboxes/{mailboxId}/enableResponse (200):
{ "mailbox": { "id", "status": "disabled" } }API Keys
Issue API Key
POST /v1/mailboxes/{mailboxId}/api-keys| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Human-readable key name |
scope |
string | Yes | Must be "mailbox" |
Response (201):
{
"api_key": { "id", "name", "scope", "status" },
"token": "nrfm_keyId.base64urlSecret"
}The token is shown once.
Revoke API Key
POST /v1/mailboxes/{mailboxId}/api-keys/{keyId}/revokeResponse (200):
{ "api_key": { "id", "name", "scope", "status": "revoked" } }Messages
List Messages
GET /v1/mailboxes/{mailboxId}/messages| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
limit |
integer | 50 | Items per page (1–100) |
query |
string | — | Search subject text |
status |
string | — | Filter by message status |
direction |
string | — | inbound or outbound |
transport |
string | — | smtp or agent-json |
triage_label |
string | — | Filter by triage label |
risk_level |
string | — | Filter by risk level |
Response (200):
{
"items": [{ "id", "thread_id", "transport", "direction", "from_address", "to_address", "subject", "snippet", "status", "risk_level", "triage_label", "created_at" }],
"page": 1,
"limit": 50,
"total": 100,
"total_pages": 2
}Get Message
GET /v1/messages/{messageId}Returns the full message including body, headers, attachments, and protocol metadata.
Response (200):
{
"message": {
"id", "mailbox_id", "thread_id", "transport", "direction",
"from_address", "to_address", "subject", "snippet", "status",
"risk_level", "triage_label", "message_id_header", "created_at",
"text_body": "...",
"html_body": "...",
"headers": [{ "key": "From", "value": "..." }],
"suspicious_signals": ["Unknown sender"],
"attachments": [{ "id", "filename", "mime_type", "size" }]
}
}Send Email
POST /v1/mailboxes/{mailboxId}/send| Field | Type | Required | Description |
|---|---|---|---|
to |
string[] | Yes | Recipient addresses |
cc |
string[] | No | CC addresses |
bcc |
string[] | No | BCC addresses |
subject |
string | Yes | Email subject |
textBody |
string | No | Plain text body |
htmlBody |
string | No | HTML body |
At least one of textBody or htmlBody should be provided.
Response (201):
{
"message": { "id", "status": "sent", "..." },
"provider_message_id": "...",
"reply_to": "acme+reply-token@nerfmail.com"
}Reply to Message
POST /v1/messages/{messageId}/reply| Field | Type | Required | Description |
|---|---|---|---|
textBody |
string | No | Plain text body |
htmlBody |
string | No | HTML body |
cc |
string[] | No | CC addresses |
bcc |
string[] | No | BCC addresses |
Response (201): Same format as Send Email.
Archive / Review Message
POST /v1/messages/{messageId}/archive
POST /v1/messages/{messageId}/reviewResponse (200):
{ "message_id": "uuid", "status": "archived" }Delivery Events
GET /v1/messages/{messageId}/deliveryResponse (200):
{ "events": [{ "id", "status", "detail", "created_at" }] }Raw Email Download
GET /v1/messages/{messageId}/rawReturns the original .eml file with Content-Type: message/rfc822.
Attachment Download
GET /v1/messages/{messageId}/attachments/{attachmentId}Returns the attachment with its original Content-Type.
Threads
List Threads
GET /v1/mailboxes/{mailboxId}/threads| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | Page number |
limit |
integer | 50 | Items per page (1–100) |
query |
string | — | Search thread subject |
status |
string | — | open or archived |
Response (200):
{
"items": [{ "id", "subject", "status", "last_message_at", "created_at" }],
"page": 1,
"limit": 50,
"total": 10,
"total_pages": 1
}Drafts
Create Draft
POST /v1/mailboxes/{mailboxId}/drafts| Field | Type | Required | Description |
|---|---|---|---|
to |
string[] | No | Recipient addresses |
cc |
string[] | No | CC addresses |
bcc |
string[] | No | BCC addresses |
subject |
string | No | Email subject |
textBody |
string | No | Plain text body |
htmlBody |
string | No | HTML body |
Response (201):
{ "draft": { "id", "to", "cc", "bcc", "subject", "text_body", "html_body", "created_at" } }List Drafts
GET /v1/mailboxes/{mailboxId}/draftsGet / Update / Delete Draft
GET /v1/drafts/{draftId}
PUT /v1/drafts/{draftId}
DELETE /v1/drafts/{draftId}Send Draft
POST /v1/drafts/{draftId}/sendLabels
Create Label
POST /v1/mailboxes/{mailboxId}/labels| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Label name |
color |
string | No | Display colour |
List Labels
GET /v1/mailboxes/{mailboxId}/labelsDelete Label
DELETE /v1/labels/{labelId}Apply / Remove Labels
POST /v1/messages/{messageId}/labels/{labelId}
DELETE /v1/messages/{messageId}/labels/{labelId}
POST /v1/threads/{threadId}/labels/{labelId}
DELETE /v1/threads/{threadId}/labels/{labelId}Search & Intelligence
Search Messages
POST /v1/mailboxes/{mailboxId}/search| Field | Type | Required | Description |
|---|---|---|---|
query |
string | Yes | Search query |
limit |
integer | No | Max results |
Message Summary
GET /v1/messages/{messageId}/summary
POST /v1/messages/{messageId}/summaryGET returns the cached summary. POST generates one.
Data Extraction
GET /v1/messages/{messageId}/extractions
POST /v1/messages/{messageId}/extractionsThread Digest
GET /v1/threads/{threadId}/digest
POST /v1/threads/{threadId}/digestSender Intelligence
GET /v1/sender-profiles/{profileId}/intelligence
POST /v1/sender-profiles/{profileId}/intelligenceAutopilot Policy
GET /v1/mailboxes/{mailboxId}/autopilot
PUT /v1/mailboxes/{mailboxId}/autopilot| Field | Type | Required | Description |
|---|---|---|---|
mode |
string | No | observe or enforce |
rules |
array | No | Array of rule objects |
Mailbox Policy
GET /v1/mailboxes/{mailboxId}/policy
PUT /v1/mailboxes/{mailboxId}/policy| Field | Type | Required | Description |
|---|---|---|---|
allow_outbound |
boolean | No | Allow outbound sending |
allow_auto_reply |
boolean | No | Allow agent auto-replies |
max_outbound_per_day |
integer | No | Send rate limit |
Mailbox Stats & Metrics
GET /v1/mailboxes/{mailboxId}/stats
GET /v1/mailboxes/{mailboxId}/metricsSuppressions
GET /v1/accounts/{accountId}/suppressions
POST /v1/accounts/{accountId}/suppressions
DELETE /v1/accounts/{accountId}/suppressions?address=...Sender Profiles
POST /v1/accounts/{accountId}/sender-profiles
GET /v1/accounts/{accountId}/sender-profiles| Parameter | Type | Description |
|---|---|---|
address |
string | Search by address |
disposition |
string | Filter: neutral, trusted, blocked |
Webhooks
POST /v1/mailboxes/{mailboxId}/webhooks
GET /v1/mailboxes/{mailboxId}/webhooks
GET /v1/webhooks/{endpointId}/deliveries
POST /v1/webhooks/{endpointId}/deliveries/{deliveryId}/replayActions
POST /v1/mailboxes/{mailboxId}/actions
GET /v1/mailboxes/{mailboxId}/actions
PUT /v1/mailboxes/{mailboxId}/actions/{actionName}
DELETE /v1/mailboxes/{mailboxId}/actions/{actionName}Agent Protocol Endpoints
These endpoints use the mailbox's protocol_host (e.g., assistant.acme.mail.nerfmail.com).
GET /.well-known/agent.json — Discovery document (public)
POST /.agent/inbox — Send a protocol message
GET /.agent/inbox/{messageId} — Check message status
POST /.agent/inbox/{messageId}/respond — Respond to a message
GET /.agent/inbox/{messageId}/responses — Response historyOpenClaw Integration
Manage OpenClaw agent bindings and view routing event logs. All endpoints require admin auth.
Binding CRUD
POST /v1/mailboxes/{mailboxId}/openclaw-binding — Create or update binding
GET /v1/mailboxes/{mailboxId}/openclaw-binding — Get binding for mailbox
DELETE /v1/mailboxes/{mailboxId}/openclaw-binding — Remove bindingAdmin Views
GET /v1/admin/openclaw-bindings — List all bindings
GET /v1/admin/openclaw-events — Recent event log (?limit=50&mailboxId=uuid)Create/Update Binding Body
| Field | Type | Default | Description |
|---|---|---|---|
agentId |
string | required | OpenClaw agent identifier |
openclawBaseUrl |
string | required | Base URL for OpenClaw API |
permissionTier |
string | "draft" |
read-only, draft, send, admin-lite, admin |
policyMode |
string | "draft-first" |
draft-first, auto-send, observe |
personaLabel |
string | "default" |
Label used in agent prompts (e.g., "support", "ops") |
enabled |
boolean | true |
Whether the binding is active |
Utility
Health Check
GET /healthPublic, no authentication required.
OpenAPI Spec
GET /openapi.jsonReturns a complete OpenAPI 3.1.0 specification. Public, no authentication required.
Idempotency
Most mutating endpoints support the Idempotency-Key header:
| Scenario | Result |
|---|---|
| New key | Request processed normally |
| Same key, same request | Cached response returned with x-idempotent-replay: true |
| Same key, different request | 409 Conflict |
Error Codes
| HTTP | Code | Description |
|---|---|---|
| 400 | validation_error |
Request body failed validation |
| 400 | invalid_json |
Malformed JSON |
| 400 | schema_validation_failed |
Protocol parameters failed action schema |
| 400 | unknown_action |
Action doesn't exist or is disabled |
| 400 | reply_not_supported |
Can't reply to this message type |
| 400 | invalid_reply_token |
Reply token not found or wrong mailbox |
| 401 | missing_authorization |
No Authorization header |
| 401 | invalid_authorization |
Malformed token |
| 401 | invalid_token |
Token doesn't match |
| 403 | forbidden |
Valid token, wrong mailbox |
| 403 | outbound_disabled |
Mailbox policy disallows sending |
| 404 | not_found |
Resource not found |
| 409 | address_suppressed |
Recipient is on suppression list |
| 409 | idempotency_key_reused |
Same key, different request |
| 410 | expired_reply_token |
Reply token past 30-day expiry |
| 429 | rate_limit_exceeded |
Rate limit hit |
| 500 | internal_error |
Server error |