MailHulk API (1.0.0)

Download OpenAPI specification:

Canonical HTTP contract for MailHulk /v1 resources. Success responses use {data} for single resources, {data,meta,links} for collections, and 204 with an empty body for deletes. Errors return {code,message,details,request_id}. Cross-tenant, cross-project, and cross-environment access returns 404. A device-session principal must select its project with the X-MailHulk-Project header on every route that documents it: omitting it returns 400 project_required, and naming a project the user is not a member of returns 404 not_found. The user-plane routes — /v1/me, /v1/projects, /v1/devices — bind no project and take no such header; /v1/projects is the call that tells a client which value to send. Collections are cursor-paginated newest-first by ULID: pass links.next back as cursor until it is null.

API Keys

List API keys

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Create an API key

Requested scopes must be a subset of the caller's own; * is mintable only by a * caller. Omitting scopes grants the caller's own set, which is * for a full-access caller and the widest legal grant otherwise.

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
name
required
string <= 100 characters
scopes
Array of strings
expires_at
string or null <date-time> (NullableDateTime)

A timestamp that the API may return as null. Spelled as a union rather than a nullable sibling of $ref, which OpenAPI 3.1 ignores — a generated client would then type the field non-null and break on null.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "scopes": [
    ],
  • "expires_at": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Show an API key

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke an API key

Permitted regardless of the target's scopes — destroying authority is a safety action. Deliberately asymmetric with rotate.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Rotate a key's secret

Refused with 403 scope_escalation when the target key holds a scope the caller does not — otherwise a narrow credential could rotate a broad key and be handed its secret.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Alerts

List the calling user's alerts in the bound project

Cursor-paginated, newest first. Device principals only — an API key has no user and therefore no alerts.

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
unread
boolean
channel
string
Enum: "arrivals" "failures"
type
string
Enum: "message.arrived" "inbox.expiring" "inbox.quota" "team.quota"
cursor
string
limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {
    }
}

Mark alerts read

Omitting ids marks every unread alert in the bound project read. A ULID belonging to another user or project is 422 rather than silently skipped.

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
ids
Array of strings[ items = 26 characters ]

Responses

Request samples

Content type
application/json
{
  • "ids": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Approvals

Human decisions on parked sensitive steps. Device-session principals only — an API key that could approve its own request would make the gate decorative.

List approvals in the bound project

Newest first, capped at 100. Only a team owner or admin on a device session may list. An API-key principal is refused with 403 principal_not_permitted, because a key that launched the run must not be able to approve it. Cross-tenant reads are 404, not 403.

Authorizations:
DeviceSession
query Parameters
status
string
Enum: "pending" "approved" "rejected" "expired"
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Approve a pending approval

Resumes the parked run at the same step. Only a team owner or admin on a device session may decide. An API-key principal is 403 principal_not_permitted. A cross-tenant ULID is 404, not 403. An expired pending row is 410 approval_expired.

Authorizations:
DeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reject a pending approval

Fails the parked run. Only a team owner or admin on a device session may decide. An API-key principal is 403 principal_not_permitted. A cross-tenant ULID is 404, not 403. An expired pending row is 410 approval_expired.

Authorizations:
DeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
reason
string or null <= 500 characters

Responses

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Agents

Create an agent

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
name
required
string
description
string
permissions
required
Array of strings

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "permissions": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List agents

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show an agent

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update an agent

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
name
string
description
string
permissions
Array of strings

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "permissions": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke an agent

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Agent Sessions

Open an MCP agent session

Opens an agent_run with trigger_type = mcp for out-of-tree MCP clients. Requires runs:write and api_keys.agent_access_enabled. Omitting agent_id resolves the project's Standard-preset agent.

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
agent_id
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$

Responses

Request samples

Content type
application/json
{
  • "agent_id": "01J00000000000000000000000"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Release an MCP agent session

Closes a running MCP session and frees its cleanup ledger. Idempotent — releasing an already-released session returns 200, not an error.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

HTTP Origins

Create an HTTP origin

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
origin
required
string
description
string

Responses

Request samples

Content type
application/json
{
  • "origin": "string",
  • "description": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List HTTP origins

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show an HTTP origin

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update HTTP origin description

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
required
description
required
string or null

Responses

Request samples

Content type
application/json
{
  • "description": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete an HTTP origin

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Scenario Secrets

Create a scenario secret

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
name
required
string
value
required
string

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "value": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List scenario secrets

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Rotate a scenario secret

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
required
value
required
string

Responses

Request samples

Content type
application/json
{
  • "value": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Show scenario secret metadata

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a scenario secret

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Scenarios

Create a scenario

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
name
required
string
description
string
object

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "draft_definition": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List scenarios

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show a scenario

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update a scenario draft

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

If-Match
string

Integer draft_revision for optimistic concurrency on scenario updates.

Request Body schema: application/json
name
string
description
string
object

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "description": "string",
  • "draft_definition": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a scenario

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Validate a scenario definition

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
object

Responses

Request samples

Content type
application/json
{
  • "definition": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Scenario Versions

Publish the current scenario draft

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List scenario versions

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show a scenario version

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
version
required
integer >= 1

The sequential version number from ScenarioVersion.version — not a ULID. Versions are numbered per scenario starting at 1.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Runs

Launch a scenario run

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
wait
integer [ 0 .. 60 ]

Block for up to this many seconds for the run to reach a terminal state or await approval, then return whatever it has. Capped at 60 by default (deployment-configurable); above the cap is 422.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
agent_id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
scenario_id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
object

Responses

Request samples

Content type
application/json
{
  • "agent_id": "01J00000000000000000000000",
  • "scenario_id": "01J00000000000000000000000",
  • "inputs": { }
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List runs

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
status
string
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show a run

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List run events

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Request run cancellation

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Usage

Show project usage and quotas

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Inboxes

Create an inbox

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Request Body schema: application/json
name
string or null <= 100 characters
ttl_seconds
integer or null [ 60 .. 2592000 ]

Lifetime of a disposable inbox, from now. Rejected with 422 alongside domain_id: a persistent mailbox does not expire.

domain_id
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
local_part
string <= 64 characters

Mailbox local part. Required with domain_id (persistent mailbox). Optional on its own, where it claims that address on the project's shared subdomain and returns 422 address_taken if it is in use. Omit it entirely to get a generated name-based address such as vince.sporer.

watch
boolean

Watch the new inbox from the calling device. Device principals only; an API key passing this receives 422.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "ttl_seconds": 60,
  • "domain_id": "01J00000000000000000000000",
  • "local_part": "string",
  • "watch": true
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List inboxes

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show an inbox

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Update an inbox

Send at least one of name or ttl_seconds — an empty body is 422. ttl_seconds runs from now rather than extending what remains, is clamped to the plan's ceiling instead of being rejected for exceeding it, and is 422 on a persistent mailbox, which does not expire.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
required
non-empty
name
string or null <= 100 characters
ttl_seconds
integer >= 1

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "ttl_seconds": 1
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete an inbox

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Get inbox mailbox credentials

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Advance the read watermark for an inbox

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
required
last_read_ulid
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$

Responses

Request samples

Content type
application/json
{
  • "last_read_ulid": "01J00000000000000000000000"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Watch this inbox from the calling device

Subscribes the calling device to this inbox. A watch means "tell me about this inbox" — arrivals plus that inbox's expiry and quota alerts. Idempotent. Device principals only; an API key has no device and receives 403.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Stop watching this inbox from the calling device

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Messages

List inbox messages

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
view
string
Default: "summary"
Enum: "summary" "full" "agent"

summary (the default) returns MessageSummary and carries no bodies. full returns the complete Message. agent returns the enveloped AgentMessage projection.

headers
string
Default: "projected"
Enum: "projected" "full"

Header projection mode for the agent representation. projected (the default) returns a bounded subset; full returns every received header, bounded with truncation when needed.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Get the latest or next inbox message

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
wait
integer [ 0 .. 60 ]

Long-poll for up to this many seconds for a message newer than the baseline. The baseline is after when given, otherwise the newest message at call time — so ?wait=30 with no after waits for the next arrival, not the latest one. Capped at 60 by default (deployment-configurable); above the cap is 422. At the deadline a normal caller receives 204; a caller in the agent representation receives {status: "pending", poll_token, expires_in} instead.

after
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: after=01J00000000000000000000000
view
string
Default: "full"
Enum: "summary" "full" "agent"

full (the default) returns the complete Message. summary returns MessageSummary and carries no bodies. agent returns the enveloped AgentMessage projection.

headers
string
Default: "projected"
Enum: "projected" "full"

Header projection mode for the agent representation. projected (the default) returns a bounded subset; full returns every received header, bounded with truncation when needed.

poll_token
string

Resume a bounded wait. Returned as poll_token on a pending response. Agent representation only — a 422 otherwise, and a 422 if combined with after, whose role it already fills. The wait is bounded cumulatively: the token carries the original session start, so re-issuing does not refresh the clock. Once the session budget (600 s by default, deployment-configurable) is spent the response is expired and carries no token.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Show a message

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
view
string
Default: "full"
Enum: "summary" "full" "agent"

full (the default) returns the complete Message. summary returns MessageSummary. agent returns the enveloped AgentMessage projection.

headers
string
Default: "projected"
Enum: "projected" "full"

Header projection mode for the agent representation. projected (the default) returns a bounded subset; full returns every received header, bounded with truncation when needed.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a message

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Get a message's sanitized HTML body

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
remote
string
Default: "strip"
Enum: "strip" "allow"

strip (the default) removes remote references so no tracking pixel fires when the body is rendered. allow restores the original URLs for a deliberate "show images" action. Script policy is identical in both modes.

headers
string
Default: "projected"
Enum: "projected" "full"

Header projection mode for the agent representation. projected (the default) returns a bounded subset; full returns every received header, bounded with truncation when needed.

header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

X-MailHulk-Agent-Session
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Optional MCP session ulid from POST /v1/agent-sessions. When absent, the route behaves exactly as without a session. When present, the call is recorded as an agent-run step and subject to session validation and the three-axis permission intersection.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Download the raw RFC 822 message

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Download a message attachment

The attachment ULID comes from Message.attachments[].id; the summary view carries only attachment_count, so fetch the message first.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
attachmentId
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Read one attachment as JSON

The agent representation of an attachment: metadata, a sha256 of the exact bytes, and decoded text for text/plain, text/csv, text/calendar, and application/json only. Raw bytes are never returned here — use the sibling download route for those. The filename sits inside the untrusted envelope because the sender wrote it.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
attachmentId
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Domains

Add a normal domain to the project

Only normal-mode domains are exposed on /v1/domains.

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
domain
required
string

Responses

Request samples

Content type
application/json
{
  • "domain": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List normal domains

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Show a domain

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a domain

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Show DNS verification status

The verification view of a domain: its status, when DNS was last checked, and the expected records each carrying its own last-seen check result. Narrower than the domain resource — it omits dkim, project, and the timestamps.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Trigger DNS verification

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

List inboxes on a domain

An identifying projection, not the inbox resource — it carries no connection details, counts, status, or storage. Call GET /v1/inboxes/{id} for those.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Webhooks

Create a webhook

Authorizations:
ApiKeyAuthDeviceSession
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
url
required
string <uri>
description
string

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List webhooks

Authorizations:
ApiKeyAuthDeviceSession
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Delete a webhook

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Routing Rules

Create a routing rule

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Idempotency-Key
string <= 255 characters

Optional idempotency key for safe retries on create operations.

Request Body schema: application/json
required
type
required
string
source
required
string
action
required
string
target_inbox_id
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
forward_targets
Array of strings
priority
integer

Responses

Request samples

Content type
application/json
{
  • "type": "string",
  • "source": "string",
  • "action": "string",
  • "target_inbox_id": "01J00000000000000000000000",
  • "forward_targets": [
    ],
  • "priority": 0
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

List routing rules for a domain

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
query Parameters
cursor
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: cursor=01J00000000000000000000000

Opaque pagination cursor from links.next.

limit
integer [ 1 .. 100 ]
Default: 50
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "meta": {
    },
  • "links": {}
}

Update a routing rule

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Request Body schema: application/json
active
boolean
priority
integer
target_inbox_ulid
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
forward_targets
Array of strings

Responses

Request samples

Content type
application/json
{
  • "active": true,
  • "priority": 0,
  • "target_inbox_ulid": "01J00000000000000000000000",
  • "forward_targets": [
    ]
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Delete a routing rule

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
header Parameters
X-MailHulk-Project
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Selects the project, as a project ULID from GET /v1/projects.

Marked optional because whether it is required depends on the credential, which OpenAPI cannot express: a device session must send it on every route that carries this parameter — omitting it is 400 project_required, and a project the user is not a member of is 404 not_found rather than a 403, so tenancy is never confirmed across teams. An API key is already bound to one project and may omit it; sending a value that contradicts the key's own project is 400 project_mismatch rather than being ignored, because silently ignoring it would hide a client bug.

A mobile client should simply always send it.

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Devices

The user's registered devices

Authorizations:
DeviceSession

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Update the calling device's push registration

Registers or rotates this device's FCM token. Self only — patching another of your own devices is 403 not_calling_device, because you can already list and delete it and a 404 would lie.

Authorizations:
ApiKeyAuthDeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000
Request Body schema: application/json
push_token
string or null <= 4096 characters
app_version
string or null <= 20 characters

Responses

Request samples

Content type
application/json
{
  • "push_token": "string",
  • "app_version": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    }
}

Revoke a device session

Deletes the device's token and drops its inbox watches, so it stops receiving push immediately. The device row is kept as the audit trail. Revoking the calling device is allowed — it is how "sign out" works.

Authorizations:
DeviceSession
path Parameters
id
required
string (Ulid) ^[0-9A-HJKMNP-TV-Z]{26}$
Example: 01J00000000000000000000000

Responses

Response samples

Content type
application/json
{
  • "code": "string",
  • "message": "string",
  • "details": { },
  • "request_id": "string"
}

Device Auth

The guest plane that mints a device session. These routes produce a credential, so they require none, and they are throttled per IP — a 429 carries a Retry-After. They are the only /v1 routes that take no X-MailHulk-Project header: the project is chosen afterwards, with GET /v1/projects.

Exchange a 2FA challenge for a device session

Request Body schema: application/json
required
challenge_token
required
string
code
required
string
required
object (DeviceRegistration)

Responses

Request samples

Content type
application/json
{
  • "challenge_token": "string",
  • "code": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "token": "string",
  • "expires_at": "2019-08-24T14:15:22Z",
  • "device": {
    }
}

Exchange a 2FA recovery code for a device session

Request Body schema: application/json
required
challenge_token
required
string
recovery_code
required
string
required
object (DeviceRegistration)

Responses

Request samples

Content type
application/json
{
  • "challenge_token": "string",
  • "recovery_code": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "token": "string",
  • "expires_at": "2019-08-24T14:15:22Z",
  • "device": {
    }
}

Exchange a password for a device session

Request Body schema: application/json
required
email
required
string <email>
password
required
string
required
object (DeviceRegistration)

Responses

Request samples

Content type
application/json
{
  • "email": "user@example.com",
  • "password": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
Example
{
  • "token": "string",
  • "expires_at": "2019-08-24T14:15:22Z",
  • "device": {
    }
}

Poll a pairing token; returns the session once the desktop approves

path Parameters
id
required
string

The 40-character pairing token, or the 8-character short code.

Responses

Response samples

Content type
application/json
Example
{
  • "status": "scanned"
}

Register a scan of a desktop pairing QR

Request Body schema: application/json
required
pairing_token
required
string

The scanned 40-character token, or the 8-character short code shown beside the QR. Case-insensitive; grouping hyphens and spaces are ignored.

required
object (DeviceRegistration)

Responses

Request samples

Content type
application/json
{
  • "pairing_token": "string",
  • "device": {
    }
}

Response samples

Content type
application/json
{
  • "status": "scanned"
}

Identity

Who the caller is and what it may select. Bind no project and take no X-MailHulk-Project header.

The authenticated principal

Authorizations:
ApiKeyAuthDeviceSession

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Projects the caller may select

Authorizations:
ApiKeyAuthDeviceSession

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}