Using the API

All Conduit features are accessible through a versioned JSON REST API at /api/v1. This reference covers authentication, common conventions, and a summary of every available endpoint. For features that are also available in the web UI, the relevant UI path is noted alongside the API endpoint.


Base URL

https://conduit.email/api/v1

Authentication

Conduit supports two distinct authentication mechanisms for API access. Both are presented identically in requests — include a token in the Authorization header:

Authorization: Bearer <token>

The server tries JWT validation first, then falls back to API token validation, so the calling code never needs to know which type of token it holds.


Method 1 — Session tokens (JWT)

Session tokens are short-lived JWTs issued when you sign in with your email and password (or via OAuth). They consist of two parts:

  • Access token — valid for 15 minutes. Include this in every API request.
  • Refresh token — long-lived. Exchange it for a new access token when the current one expires, without re-entering credentials.

Obtaining tokens

POST /api/v1/sessions
Content-Type: application/json

{
  "email": "you@example.com",
  "password": "correct-horse-battery-staple"
}
{
  "access_token": "eyJ...",
  "refresh_token": "eyJ...",
  "expires_in": 900
}

Refreshing an access token

POST /api/v1/sessions/refresh
Content-Type: application/json

{
  "refresh_token": "eyJ..."
}

Signing out

Revoking the refresh token immediately invalidates the session:

DELETE /api/v1/sessions
Authorization: Bearer <access_token>
Content-Type: application/json

{
  "refresh_token": "eyJ..."
}

The web UI equivalent is the Sign out action available from the navigation.

When to use session tokens

Session tokens are best suited for interactive, user-facing applications:

  • Web and mobile apps where a human types their credentials at login time.
  • Short-lived scripts or one-off API calls where you can sign in at the start and discard the tokens when done.
  • Any context where you want the authentication to be tied to the account owner's active session so that signing out everywhere immediately revokes access.

Pros and cons

✅ Short-lived — a leaked access token expires quickly (15 minutes)
✅ Revocable — signing out invalidates the refresh token immediately
✅ Compatible with 2FA — the sign-in flow enforces TOTP if enabled
❌ Requires refresh logic — callers must implement token rotation
❌ Awkward for automation — storing a password or refresh token in a CI secret is nearly equivalent to storing an API token

Method 2 — Long-lived API tokens

API tokens are opaque, long-lived credentials you create in the API or web UI. Unlike session tokens they do not expire by default and do not need to be refreshed.

See the API Tokens section below for the full CRUD reference.

When to use API tokens

API tokens are best suited for automated, non-interactive clients:

  • CI/CD pipelines that need to create or update webhooks as part of a deployment.
  • Server-side scripts or cron jobs that run unattended.
  • Third-party integrations where you want to issue a dedicated credential per integration that can be revoked independently.
  • Any context where implementing a token-refresh loop is impractical.

Scoping and restricting tokens

API tokens support two optional restrictions that reduce blast radius if a token is leaked:

  • expires_in — set a finite lifetime in seconds. Useful when you only need access for a bounded period (e.g. a one-time migration).
  • allowed_ips — restrict which client IPs or CIDR ranges may use the token. Useful when your automation runs from a known IP range (e.g. a GitHub Actions runner pool or a fixed office NAT).

Pros and cons

✅ No refresh loop — tokens work until they expire or are revoked
✅ Per-integration revocation — each token is independent; revoking one does not affect others
✅ IP restrictions — optionally restrict which IPs may use a token
✅ Optional expiry — can be time-bounded for temporary access
❌ Long-lived by default — a leaked token remains valid until manually revoked
❌ Not 2FA-protected — token creation requires an active session, but using a token bypasses the TOTP challenge
❌ No automatic rotation — you must explicitly revoke and replace tokens you want to cycle

Choosing the right method

Situation Recommended method
Interactive web / mobile app Session token (JWT)
User-initiated CLI tool Session token (JWT)
CI/CD pipeline or cron job API token
Server-to-server integration API token
Short one-off automation Either — API token is simpler; session token avoids storing a long-lived secret
Temporary / bounded access API token with expires_in
Access from a known IP range API token with allowed_ips

Two-factor authentication (TOTP)

If 2FA is enabled on your account, the POST /api/v1/sessions response returns a TOTP challenge instead of tokens:

{
  "totp_required": true,
  "totp_token": "..."
}

Complete the challenge with a code from your authenticator app:

POST /api/v1/sessions/2fa
Content-Type: application/json

{
  "totp_token": "...",
  "code": "123456"
}

Errors

All errors follow the same shape:

{
  "error": "Human-readable message",
  "code": "machine_readable_code"
}

Common HTTP status codes:

Status Meaning
400 Bad request — malformed JSON or missing required fields
401 Unauthorized — missing or invalid access token
404 Not found
409 Conflict — e.g. email address already registered
422 Validation error — request was understood but values are invalid

Authentication-specific error codes:

Code Meaning
email_not_confirmed The account exists, but the signup confirmation link has not been opened yet
token_expired The password-reset or confirmation token has expired
token_invalid The password-reset or confirmation token is malformed or no longer valid

Accounts

Operation API Web UI
Create account POST /api/v1/accounts /app/signup
Get your account GET /api/v1/accounts/me
Change password PUT /api/v1/accounts/me/password /app/settings/account
Update timezone PUT /api/v1/accounts/me/timezone /app/settings/account
Request password reset POST /api/v1/accounts/me/password-reset /app/reset-password
Confirm password reset PUT /api/v1/accounts/me/password-reset /app/reset-password/confirm
Delete account DELETE /api/v1/accounts/me

Create an account

POST /api/v1/accounts
Field Type Required Description
email string Yes Account email address
password string Yes Minimum 12 characters

Account creation sends a signup confirmation email. The account cannot create a session until that email has been confirmed.

Change your password

PUT /api/v1/accounts/me/password
Field Type Required Description
current_password string Yes Your current password
new_password string Yes Minimum 12 characters

Update your timezone

PUT /api/v1/accounts/me/timezone
Field Type Required Description
timezone string Yes IANA timezone name (e.g. America/New_York, Europe/Berlin, UTC)

The timezone setting controls how timestamps are displayed in the web UI. All timestamps in API responses remain in UTC regardless of this setting.

Request a password reset

POST /api/v1/accounts/me/password-reset
Field Type Required Description
email string Yes Account email address

This endpoint always returns success for both known and unknown addresses. When the account exists, Conduit sends a password-reset email containing the token used by the confirmation endpoint below.

Confirm a password reset

PUT /api/v1/accounts/me/password-reset
Field Type Required Description
token string Yes Reset token (from the reset email)
new_password string Yes Minimum 12 characters

Delete your account

DELETE /api/v1/accounts/me
Field Type Required Description
password string Yes Current password to confirm deletion
confirm boolean Yes Must be true to acknowledge that deletion is irreversible

This action is irreversible. All webhooks, delivery logs, security policies, and custom domains are permanently removed.


Two-factor authentication

Operation API Web UI
Set up TOTP POST /api/v1/accounts/me/2fa/setup /app/settings/2fa/setup
Enable TOTP POST /api/v1/accounts/me/2fa/enable /app/settings/2fa/setup (same form)
Disable TOTP DELETE /api/v1/accounts/me/2fa /app/settings/account
Regenerate backup codes POST /api/v1/accounts/me/2fa/backup-codes /app/settings/account

Set up TOTP

POST /api/v1/accounts/me/2fa/setup

Returns a secret, an otpauth_url, and a qr_code_png data URI. Scan the QR code with your authenticator app.

Enable TOTP

POST /api/v1/accounts/me/2fa/enable
Field Type Required Description
code string Yes 6-digit code from your authenticator app

Disable TOTP

DELETE /api/v1/accounts/me/2fa
Field Type Required Description
code string Yes 6-digit TOTP code to confirm

Backup codes

When you enable TOTP (POST /api/v1/accounts/me/2fa/enable), the response includes 8 single-use backup codes:

{
  "backup_codes": [
    "A1B2C-D3E4F",
    "G5H6I-J7K8L",
    ...
  ]
}

Save these somewhere safe. Each code can be used once in place of a TOTP code when completing the sign-in challenge (POST /api/v1/sessions/2fa). After a backup code is used, it is consumed and cannot be reused.

Regenerate backup codes

POST /api/v1/accounts/me/2fa/backup-codes

Returns a new set of 8 single-use backup codes and invalidates all previous backup codes. Requires an active 2FA session (you must already be signed in).


Webhooks

See Webhook Payload Reference for the JSON structure delivered to your target URL, template variables, and custom headers.

Operation API Web UI
List webhooks GET /api/v1/webhooks /app/webhooks
Create a webhook POST /api/v1/webhooks /app/webhooks/new
Get a webhook GET /api/v1/webhooks/{id} /app/webhooks/{id}
Update a webhook PUT /api/v1/webhooks/{id} /app/webhooks/{id}/edit
Delete a webhook DELETE /api/v1/webhooks/{id} /app/webhooks/{id} (Delete button)
Toggle active state PUT /api/v1/webhooks/{id} (set active) /app/webhooks/{id} (Activate/Deactivate button)
Rotate secret PUT /api/v1/webhooks/{id} (set secret) /app/webhooks/{id} (Rotate secret button)
Simulate email delivery POST /api/v1/webhooks/{id}/simulate /app/webhooks/{id} (Simulate button)
View delivery logs GET /api/v1/webhooks/{id}/logs /app/webhooks/{id}/logs

Create a webhook

POST /api/v1/webhooks
Field Type Required Description
address string No Full email address to receive mail (e.g. alerts@mail.example.com). Omit to use the public domain; the local part is derived automatically from the webhook ID and cannot be customised. Providing an address on the public domain is rejected (address_lhs_not_allowed).
target_url string Yes HTTPS URL to deliver the webhook payload to
secret string No Custom HMAC secret; auto-generated if omitted
active boolean No Defaults to true
custom_headers object No Key/value headers to include in every delivery
payload_template string No Go text/template for the JSON payload
rate_limit integer No Max emails per minute (0 = disabled)
smtp_security_policy_id string No ID of an SMTP security policy to attach

Note: custom_headers, payload_template, and rate_limit are currently only configurable through the API. smtp_security_policy_id can also be set via the webhook edit form in the web UI.

The secret field is only returned at creation time.

Update a webhook

PUT /api/v1/webhooks/{id}

Accepts the same fields as create. To detach the current security policy, set clear_security_policy: true.

Simulate an email delivery

Trigger a synthetic test delivery to verify that a webhook target is reachable and behaving correctly.

POST /api/v1/webhooks/{id}/simulate

The request body is optional. When provided, these fields customise the simulated email:

Field Type Default Description
from string simulate@conduit.example Sender address
subject string Test email from Conduit Email subject line
text string This is a simulated test email sent from Conduit. Plain-text body

The response contains the delivery outcome:

Field Description
http_status HTTP status code returned by the webhook target, if reached
duration_ms Time taken for the delivery attempt in milliseconds
error Error message if delivery was unsuccessful
simulated Always true

A log entry with simulated: true is written to the delivery log regardless of outcome.


Delivery logs

Operation API Web UI
List logs for a webhook GET /api/v1/webhooks/{id}/logs /app/webhooks/{id}/logs
Get a specific log entry GET /api/v1/webhooks/{id}/logs/{logId}

Pagination

The list endpoint accepts two optional query parameters:

Parameter Default Maximum Description
page 1 Page number (1-based)
page_size 50 200 Number of results per page

Example — fetch the second page of 100 results:

GET /api/v1/webhooks/wh_01HX.../logs?page=2&page_size=100
Authorization: Bearer <access_token>

Log entry fields

Each log entry includes:

Field Description
id Log entry ID
webhook_id Webhook ID
smtp_message_id SMTP Message-ID header value
sender Envelope sender address
http_status HTTP status code returned by the target (if reached)
error Error detail, if delivery failed
duration_ms Delivery round-trip time in milliseconds
simulated true when the entry was created by a simulation, not a real inbound email
attempted_at Timestamp of the delivery attempt

Simulated log entries are labelled SIM in the web UI delivery log view.


SMTP security policies

See Configuring an SMTP Security Policy for a full guide.

Operation API Web UI
List policies GET /api/v1/smtp-policies /app/smtp-policies
Create a policy POST /api/v1/smtp-policies /app/smtp-policies/new
Get a policy GET /api/v1/smtp-policies/{id} /app/smtp-policies/{id}
Update a policy PUT /api/v1/smtp-policies/{id} /app/smtp-policies/{id}/edit
Delete a policy DELETE /api/v1/smtp-policies/{id} /app/smtp-policies/{id} (Delete button)

Domains

Domain management is currently only available through the API. See Using a Custom Domain for a full guide.

Operation API
List domains GET /api/v1/domains
Register a domain POST /api/v1/domains
Get a domain GET /api/v1/domains/{id}
Verify a domain POST /api/v1/domains/{id}/verify
Delete a domain DELETE /api/v1/domains/{id}

Register a domain

POST /api/v1/domains
Field Type Required Description
name string Yes Domain name to claim (e.g. mail.example.com)

Verify a domain

POST /api/v1/domains/{id}/verify

Performs a DNS TXT lookup to confirm the verification token is published. See Using a Custom Domain for the full verification workflow.


API Tokens

Long-lived API tokens let you authenticate without short-lived JWTs — useful for CI/CD pipelines, scripts, and integrations where refreshing tokens is impractical.

Tokens are presented exactly like JWTs: Authorization: Bearer <token>. The authentication middleware tries JWT validation first, then falls back to API token validation.

Important: The raw token value is returned once at creation time. Store it securely — it cannot be retrieved again.

List tokens

GET /api/v1/accounts/me/api-tokens

Returns an array of tokens (without the raw token value).

Create a token

POST /api/v1/accounts/me/api-tokens
Field Type Required Description
name string Yes Human-readable label (e.g. CI/CD Pipeline)
expires_in integer No Token lifetime in seconds. Omit or 0 for no expiry.
allowed_ips string[] No Allowed client IPs or CIDR ranges. Omit to allow any IP.

Response (201):

{
  "id": "tok_01HX...",
  "name": "CI/CD Pipeline",
  "token": "aB3c...raw-token-shown-once...",
  "expires_at": "2026-01-01T00:00:00Z",
  "allowed_ips": ["192.168.1.0/24"],
  "last_used_at": null,
  "created_at": "2025-01-01T12:00:00Z"
}

Revoke a token

DELETE /api/v1/accounts/me/api-tokens/{id}

Returns 204 No Content on success.