Using the API

Every Conduit feature is available through a versioned JSON REST API at /api/v1. This reference covers the authentication, the common conventions, and every endpoint. When a feature is also in the web UI, this page gives the UI path next to the API endpoint.


Base URL

https://conduit.email/api/v1

Authentication

Conduit has two different authentication methods for API access. A request sends both of them in the same way, as a token in the Authorization header:

Authorization: Bearer <token>

The server tries JWT validation first and then falls back to API token validation. Your code therefore never needs to know which type of token it has.


Method 1: Session tokens (JWT)

A session token is a short-lived JWT. Conduit issues one when you sign in with your email address and password, or through OAuth. There are two parts:

  • Access token. Valid for 15 minutes. Include this in every API request.
  • Refresh token. Long-lived. When the access token expires, exchange the refresh token for a new one. You do not enter your credentials again.

Obtaining tokens

POST https://conduit.email/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 https://conduit.email/api/v1/sessions/refresh
Content-Type: application/json

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

Signing out

A revoked refresh token invalidates the session immediately:

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

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

The Sign out action in the navigation does the same thing in the web UI.

When to use session tokens

A session token is the best choice for an interactive application with a user:

  • A web app or a mobile app, where a person types their credentials at sign-in.
  • A short script or a single API call. You sign in at the start and discard the tokens at the end.
  • Any place where the authentication must follow the active session of the account owner. A sign-out everywhere then revokes the access immediately.

Pros and cons

✅ Short-lived. A leaked access token expires quickly (15 minutes)
✅ Revocable. Signing out invalidates the refresh token immediately
✅ Works with 2FA. The sign-in flow enforces TOTP when 2FA is on
❌ Needs refresh logic. The caller must rotate the token
❌ Awkward for automation. A password or refresh token in a CI secret is almost the same as an API token

Method 2: Long-lived API tokens

An API token is an opaque, long-lived credential that you create in the API or in the web UI. A session token is different: an API token does not expire by default, and it needs no refresh.

For the full CRUD reference, see API Tokens.

When to use API tokens

An API token is the best choice for an automated client with no user:

  • A CI/CD pipeline that creates or updates webhooks during a deployment.
  • A server-side script or a cron job that runs without a person.
  • A third-party integration. Each integration gets its own credential, and you can revoke one without an effect on the others.
  • Any place where a token-refresh loop is not practical.

Scoping and restricting tokens

An API token has two optional restrictions. They limit the damage when a token leaks:

  • expires_in. A lifetime in seconds. Use it when you need access for a limited period only, for example a one-time migration.
  • allowed_ips. The client IP addresses or CIDR ranges that can use the token. Use it when your automation runs from a known IP range, for example a GitHub Actions runner pool or a fixed office NAT.

Pros and cons

✅ No refresh loop. A token works until it expires, or until you revoke it
✅ Revocation for each integration. Each token is independent, so a revocation does not affect the others
✅ IP restrictions. You can limit the IP addresses that can use a token
✅ Optional expiry. A token can have a limited lifetime for temporary access
❌ Long-lived by default. A leaked token stays valid until you revoke it
❌ No 2FA protection. Token creation needs an active session, but a token in use skips the TOTP challenge
❌ No automatic rotation. To cycle a token, you must revoke it and create a new one

Choosing the right method

Situation Best 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-time automation Either one. An API token is simpler. A session token avoids 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 https://conduit.email/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, for example an email address that is 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

Admin API audit logs

For operators using the admin API (/api/v1/admin/*), Conduit provides an endpoint to retrieve audit events across all accounts:

Operation API CLI
List all-account audit logs GET /api/v1/admin/audit-log conduitctl audit-log [--limit N]

limit is optional and controls the maximum number of entries returned (default 50, maximum 1000).


System

Ping

GET https://conduit.email/api/v1/ping

No authentication required. Returns a fixed 200 OK response confirming that the API is reachable.

{ "status": "ok" }

Accounts

Operation API Web UI
Create account POST /api/v1/accounts /app/signup
Get your account GET /api/v1/accounts/me n/a
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 /app/settings/account/delete

Create an account

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

Account creation sends a confirmation email with a verification link and a plain-text fallback. The account cannot create a session before somebody confirms that email address.

Change your password

PUT https://conduit.email/api/v1/accounts/me/password
Field Type Required Description
current_password string Yes Your current password
new_password string Yes Minimum 12 characters
revoke_api_tokens boolean No When true, revoke all refresh tokens and API access tokens for this account after the password change

Refresh tokens and API access tokens are revoked only when revoke_api_tokens=true.

Update your timezone

PUT https://conduit.email/api/v1/accounts/me/timezone
Field Type Required Description
timezone string Yes An IANA timezone name, for example America/New_York, Europe/Berlin or 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 https://conduit.email/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 branded password-reset email containing the token used by the confirmation endpoint below.

Confirm a password reset

PUT https://conduit.email/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

All refresh tokens and API access tokens are revoked after a successful password reset confirmation.

Delete your account

DELETE https://conduit.email/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 https://conduit.email/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 https://conduit.email/api/v1/accounts/me/2fa/enable
Field Type Required Description
code string Yes 6-digit code from your authenticator app

Disable TOTP

DELETE https://conduit.email/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 https://conduit.email/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 https://conduit.email/api/v1/webhooks
Field Type Required Description
address string No The full email address that receives mail, for example alerts@mail.example.com. Omit it to use the public domain. On the public domain the local part comes from the webhook ID and you cannot change it. 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 Your own HMAC secret. Conduit generates one when you omit this field
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

The secret field is only returned at creation time.

Update a webhook

PUT https://conduit.email/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 https://conduit.email/api/v1/webhooks/{id}/simulate

The request body is optional. These fields set the values in 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} n/a

Pagination

The list endpoint accepts two optional query parameters:

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

Example: fetch the second page of 100 results:

GET https://conduit.email/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
transaction_id Unique ID of the received email and this delivery attempt, also sent to the target in the X-Conduit-Transaction-Id header. null for entries recorded before transaction IDs were introduced
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 https://conduit.email/api/v1/domains
Field Type Required Description
name string Yes The domain name to claim, for example mail.example.com

Verify a domain

POST https://conduit.email/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

A long-lived API token authenticates you without a short-lived JWT. Use one for a CI/CD pipeline, a script, or an integration, where a token refresh is not practical.

A token goes in the same header as a JWT: Authorization: Bearer <token>. The authentication middleware tries JWT validation first and then falls back to API token validation.

CAUTION: The response returns the raw token value one time only, when you create the token. Store it in a safe place. You cannot read it again.

List tokens

GET https://conduit.email/api/v1/accounts/me/api-tokens

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

Create a token

POST https://conduit.email/api/v1/accounts/me/api-tokens
Field Type Required Description
name string Yes A label for a person to read, for example 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. Pass a JSON array with one entry per address or range. Both individual IPs ("203.0.113.5") and CIDR notation ("192.168.1.0/24") are accepted and can be mixed freely.

Example request body with multiple IP restrictions:

{
  "name": "CI/CD Pipeline",
  "allowed_ips": ["203.0.113.5", "10.0.0.0/8", "2001:db8::/32"]
}

Response (201):

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

Revoke a token

DELETE https://conduit.email/api/v1/accounts/me/api-tokens/{id}

Returns 204 No Content on success.