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.