API Overview
The REST API backs both the SDK and the CLI. Most integrations should use the SDK Reference instead of calling routes directly — this page covers the pieces every request shares.
Base URL
The hosted API is at https://develemail.com/api. Self-hosted instances
listen on http://localhost:3001 in development; use the URL of your deployed
API in production.
Authentication
Send your API key as a bearer token on every request:
Authorization: Bearer <your-api-key>Create a key with devel key create --project <id> --name <name> (see
the CLI Reference) or from a project's page in the dashboard.
Requests without a valid Authorization header return
401 Unauthorized.
Errors
Failed requests return a JSON body shaped like:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing or invalid Authorization header"
}Send-related failures carry a machine-readable error code:
| Status | error | Meaning |
|---|---|---|
| 400 | reserved_recipient_domain | A recipient is on a reserved domain |
| 400 | mixed_sandbox_recipients | One send mixes sandbox and real recipients |
| 403 | domain_not_verified | The sender domain isn't verified yet |
| 403 | sending_restricted | Sending is restricted for this account |
| 403 | organization_suspended | The organization is suspended |
| 413 | attachment_too_large | An attachment exceeds the size limit |
| 429 | quota_exceeded | Monthly quota used up; Retry-After is set |
| 429 | daily_limit_exceeded | Daily tier limit reached; Retry-After is set |
When billing is enabled on the instance, a quota_exceeded response also
includes upgrade_url, a link to the dashboard billing page where an org
admin can pick a larger plan.
The SDK parses this automatically and throws DevelEmailError with
status, message, and code (from the error field) — see the
"Error handling" section of the SDK Reference.
Retries
The API returns 429 when you're rate-limited and 5xx for transient
failures. Both are safe to retry; the SDK does this automatically with
exponential backoff, respecting Retry-After when present.
Idempotency
POST /v1/emails, POST /v1/emails/batch, POST /v1/sms and
POST /v1/sms/batch accept an Idempotency-Key header. Retrying the same
key returns the original result instead of sending a duplicate. The SDK
generates one for you unless you pass options.idempotencyKey explicitly.
Email and SMS keys live in separate namespaces, so the same key on
/v1/emails and /v1/sms doesn't collide.
List queries
| Resource | limit max | Notes |
|---|---|---|
| Emails | 200 | recipient up to 320 chars, subject up to 200 |
| Contacts | 200 | search up to 200 chars |
| Lists | 200 | |
| SMS | 200 | cursor-paginated |
| Campaigns | 100 | default 50 |
A limit above the cap, or an email status filter that isn't one of the
email statuses, returns 400.
In search strings, % and _ match literally rather than as wildcards.