REST API Reference

The NornWeave REST API provides full control over inboxes, threads, and messages.

Authentication

API key authentication is not yet enforced. All endpoints are currently accessible without credentials. The API_KEY environment variable is reserved for a future release. You can omit the Authorization header for now.

Base URL

http://localhost:8000/v1

Inboxes

Create an Inbox

Create a new virtual inbox for your AI agent.

POST /v1/inboxes

Request Body:

{
  "name": "Support Agent",
  "email_username": "support"
}
FieldTypeRequiredDescription
namestringYesDisplay name for the inbox
email_usernamestringYesLocal part of the email address

Response:

{
  "id": "ibx_abc123",
  "name": "Support Agent",
  "email_address": "support@mail.yourdomain.com",
  "created_at": "2025-01-31T12:00:00Z"
}

Example:

curl -X POST http://localhost:8000/v1/inboxes \
  -H "Content-Type: application/json" \
  -d '{"name": "Support Agent", "email_username": "support"}'
If using Mailgun or SendGrid with auto-route setup enabled, NornWeave will automatically create the routing rule in your provider.

Get an Inbox

GET /v1/inboxes/{inbox_id}

Response:

{
  "id": "ibx_abc123",
  "name": "Support Agent",
  "email_address": "support@mail.yourdomain.com",
  "created_at": "2025-01-31T12:00:00Z"
}

Delete an Inbox

DELETE /v1/inboxes/{inbox_id}

Response: 204 No Content


Threads

Threads are the core unit for LLM context. They group related messages into a conversation format.

Get a Thread

GET /v1/threads/{thread_id}

Response:

{
  "id": "th_123",
  "inbox_id": "ibx_abc123",
  "subject": "Re: Pricing Question",
  "last_message_at": "2025-01-31T10:05:00Z",
  "summary": "Customer asked about pricing. Support replied with $20/month starting price.",
  "messages": [
    {
      "id": "msg_001",
      "role": "user",
      "author": "bob@gmail.com",
      "content": "Hi, how much does your service cost?",
      "timestamp": "2025-01-31T10:00:00Z"
    },
    {
      "id": "msg_002",
      "role": "assistant",
      "author": "support@mail.yourdomain.com",
      "content": "Thanks for reaching out! Our pricing starts at $20/month.",
      "timestamp": "2025-01-31T10:05:00Z"
    }
  ]
}
FieldDescription
roleuser for inbound messages, assistant for outbound
contentClean Markdown content (HTML converted, cruft removed)
summaryLLM-generated thread summary (null when summarization is disabled or not yet generated)
The role field maps directly to LLM chat formats, making it easy to use thread content as conversation history.

Messages

Send a Message

Send a new email or reply to an existing thread.

POST /v1/messages

Request Body:

{
  "inbox_id": "ibx_abc123",
  "to": ["client@gmail.com"],
  "subject": "Hello from NornWeave",
  "body": "This is **Markdown** content that will be converted to HTML.",
  "reply_to_thread_id": "th_123"
}
FieldTypeRequiredDescription
inbox_idstringYesInbox to send from
toarrayYesRecipient email addresses
subjectstringYesEmail subject
bodystringYesMarkdown content
reply_to_thread_idstringNoThread ID for replies

Response:

{
  "id": "msg_003",
  "thread_id": "th_123",
  "inbox_id": "ibx_abc123",
  "direction": "OUTBOUND",
  "to": ["client@gmail.com"],
  "subject": "Hello from NornWeave",
  "content_clean": "This is **Markdown** content that will be converted to HTML.",
  "created_at": "2025-01-31T12:00:00Z"
}

When reply_to_thread_id is provided, NornWeave automatically:

  • Sets proper In-Reply-To and References headers
  • Adds the message to the existing thread
  • Maintains conversation threading in email clients

List Messages

List and search messages with flexible filters.

GET /v1/messages

Query Parameters:

ParameterTypeRequiredDescription
inbox_idstringOne of theseFilter by inbox
thread_idstringOne of theseFilter by thread
qstringNoText search (subject, body, sender, attachment filenames)
limitnumberNoMax results (default: 50)
offsetnumberNoOffset for pagination
At least one of inbox_id or thread_id must be provided.

Response:

{
  "items": [
    {
      "id": "msg_001",
      "thread_id": "th_123",
      "inbox_id": "ibx_abc123",
      "direction": "inbound",
      "provider_message_id": "<abc123@mail.gmail.com>",
      "subject": "Pricing Question",
      "from_address": "bob@gmail.com",
      "to_addresses": ["support@mail.yourdomain.com"],
      "cc_addresses": null,
      "bcc_addresses": null,
      "reply_to_addresses": null,
      "text": "Hi, how much does your service cost?",
      "html": "<p>Hi, how much does your service cost?</p>",
      "content_clean": "Hi, how much does your service cost?",
      "timestamp": "2025-01-31T10:00:00Z",
      "labels": [],
      "preview": "Hi, how much does your service cost?",
      "size": 1234,
      "in_reply_to": null,
      "references": null,
      "metadata": {},
      "created_at": "2025-01-31T10:00:00Z"
    }
  ],
  "count": 1,
  "total": 1
}

Example:

# List all messages in an inbox
curl "http://localhost:8000/v1/messages?inbox_id=ibx_abc123"

# Search for messages containing "invoice"
curl "http://localhost:8000/v1/messages?inbox_id=ibx_abc123&q=invoice"

# List messages in a specific thread
curl "http://localhost:8000/v1/messages?thread_id=th_123"

Get a Message

GET /v1/messages/{message_id}

Response:

{
  "id": "msg_001",
  "thread_id": "th_123",
  "inbox_id": "ibx_abc123",
  "direction": "inbound",
  "provider_message_id": "<abc123@mail.gmail.com>",
  "subject": "Pricing Question",
  "from_address": "bob@gmail.com",
  "to_addresses": ["support@mail.yourdomain.com"],
  "cc_addresses": null,
  "bcc_addresses": null,
  "reply_to_addresses": null,
  "text": "Hi, how much does your service cost?",
  "html": "<p>Hi, how much does your service cost?</p>",
  "content_clean": "Hi, how much does your service cost?",
  "timestamp": "2025-01-31T10:00:00Z",
  "labels": [],
  "preview": "Hi, how much does your service cost?",
  "size": 1234,
  "in_reply_to": null,
  "references": null,
  "metadata": {},
  "created_at": "2025-01-31T10:00:00Z"
}

Send a Message with Attachments

Send an email with file attachments.

POST /v1/messages

Request Body:

{
  "inbox_id": "ibx_abc123",
  "to": ["client@gmail.com"],
  "subject": "Contract for Review",
  "body": "Please review the attached contract.",
  "attachments": [
    {
      "filename": "contract.pdf",
      "content_type": "application/pdf",
      "content": "JVBERi0xLjQKJeLjz9M... (base64-encoded content)"
    }
  ]
}
FieldTypeRequiredDescription
inbox_idstringYesInbox to send from
toarrayYesRecipient email addresses
subjectstringYesEmail subject
bodystringYesMarkdown content
attachmentsarrayNoList of attachments
attachments[].filenamestringYesOriginal filename
attachments[].content_typestringYesMIME type (e.g., application/pdf)
attachments[].contentstringYesBase64-encoded file content

Response:

{
  "id": "msg_003",
  "thread_id": "th_123",
  "inbox_id": "ibx_abc123",
  "direction": "OUTBOUND",
  "to": ["client@gmail.com"],
  "subject": "Contract for Review",
  "content_clean": "Please review the attached contract.",
  "attachments": [
    {
      "id": "att_789",
      "filename": "contract.pdf",
      "content_type": "application/pdf",
      "size": 102400
    }
  ],
  "created_at": "2025-01-31T12:00:00Z"
}

Attachments

List Attachments

List attachments, filtered by message, thread, or inbox.

GET /v1/attachments

Query Parameters:

ParameterTypeRequiredDescription
message_idstringOne of theseFilter by message
thread_idstringOne of theseFilter by thread
inbox_idstringOne of theseFilter by inbox
limitnumberNoMax results (default: 100)
offsetnumberNoOffset for pagination
Exactly one of message_id, thread_id, or inbox_id must be provided.

Response:

{
  "items": [
    {
      "id": "att_789",
      "message_id": "msg_001",
      "filename": "document.pdf",
      "content_type": "application/pdf",
      "size": 102400,
      "created_at": "2025-01-31T10:00:00Z"
    }
  ],
  "count": 1
}

Get Attachment Metadata

GET /v1/attachments/{attachment_id}

Response:

{
  "id": "att_789",
  "message_id": "msg_001",
  "filename": "document.pdf",
  "content_type": "application/pdf",
  "size": 102400,
  "disposition": "attachment",
  "storage_backend": "local",
  "content_hash": "sha256:abc123...",
  "download_url": "/v1/attachments/att_789/content?token=...&expires=...",
  "created_at": "2025-01-31T10:00:00Z"
}
For S3 and GCS storage backends, download_url will be a presigned URL directly to the cloud storage. For local and database storage, it’s a signed URL to the NornWeave API.

Download Attachment Content

GET /v1/attachments/{attachment_id}/content

Query Parameters:

ParameterTypeRequiredDescription
formatstringNobinary (default) or base64
tokenstringConditionalSigned URL token (for local/db storage)
expiresnumberConditionalToken expiry timestamp

Response (format=binary):

Returns raw binary content with appropriate Content-Type and Content-Disposition headers.

Response (format=base64):

{
  "content": "JVBERi0xLjQKJeLjz9M...",
  "content_type": "application/pdf",
  "filename": "document.pdf"
}

Example:

# Download as binary
curl -o document.pdf \
  "http://localhost:8000/v1/attachments/att_789/content?token=abc&expires=1234567890"

# Download as base64 JSON
curl "http://localhost:8000/v1/attachments/att_789/content?format=base64&token=abc&expires=1234567890"

Search

Search for messages using the flexible message list endpoint.

GET /v1/messages?inbox_id={inbox_id}&q={query}

Query Parameters:

ParameterTypeRequiredDescription
inbox_idstringOne of theseFilter by inbox
thread_idstringOne of theseFilter by thread
qstringYesSearch query
limitnumberNoMax results (default: 50)
offsetnumberNoOffset for pagination

The search looks across:

  • Email subject
  • Email body (text)
  • Sender address (from_address)
  • Attachment filenames

Response:

{
  "items": [
    {
      "id": "msg_001",
      "thread_id": "th_123",
      "inbox_id": "ibx_abc123",
      "subject": "Pricing Question",
      "from_address": "bob@gmail.com",
      "text": "Hi, how much does your service cost?",
      "content_clean": "Hi, how much does your service cost?",
      "created_at": "2025-01-31T10:00:00Z"
    }
  ],
  "count": 1,
  "total": 1
}

Example:

# Search in an inbox
curl "http://localhost:8000/v1/messages?inbox_id=ibx_abc123&q=pricing"

# Search within a thread
curl "http://localhost:8000/v1/messages?thread_id=th_123&q=contract"

# Search with pagination
curl "http://localhost:8000/v1/messages?inbox_id=ibx_abc123&q=invoice&limit=10&offset=20"
Search uses SQL ILIKE for case-insensitive text matching. Results include the total count for pagination support.

Webhooks

These endpoints receive inbound email from your provider. Configure them in your provider’s dashboard.

Mailgun

POST /webhooks/mailgun

SendGrid

POST /webhooks/sendgrid

AWS SES

POST /webhooks/ses

See Provider Guides for setup instructions.


Error Responses

All errors follow a consistent format:

{
  "error": {
    "code": "not_found",
    "message": "Thread not found",
    "details": {}
  }
}
Status CodeDescription
400Bad request (invalid parameters)
404Resource not found
429Rate limited
500Internal server error