API Conventions
Behaviour that applies across every endpoint — stated once here so it is not repeated in each endpoint's docs.
Base URL
All API endpoints share a single base URL:
https://api.quolle.com
Every path in this documentation is relative to this base. There is no versioning prefix in the URL — breaking changes ship as new resources, not new versions.
Authentication
All public API requests require an API key in the Authorization header:
Authorization: Bearer qle_your_api_key
Keys start with qle_ and are created in the
dashboard
under API Keys. See the Authentication
page for setup and security guidance.
Request format
All request bodies must be JSON. Set the content type on every POST request:
Content-Type: application/json
Requests without a body (GET, DELETE) do not need a Content-Type header.
Response format
All responses are JSON. The shape depends on whether the request succeeded.
Success
The response body is the resource or result directly — no wrapper envelope:
// POST /v1/emails/send — 200
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"message": "Email queued successfully"
}
Error
Errors always include an error string. Many errors include additional
fields alongside it:
// 402 — monthly limit reached
{
"error": "Monthly limit reached",
"limit": 3000,
"used": 3000,
"plan": "Starter"
}
Validation errors (missing or wrong-typed fields) return a different shape — see Errors → 400 for the full Zod error structure.
Dates and times
All timestamps are ISO 8601 strings in UTC, ending in Z:
2026-07-05T12:00:00.000Z
When submitting a datetime (e.g. scheduledAt), use the same format.
The API rejects non-ISO strings and datetimes that are not in the future.
Idempotency
POST /v1/emails/send and POST /v1/emails/batch support an
optional Idempotency-Key header. If you include one and the request
succeeds, the response is cached for 24 hours under that key. A
duplicate request with the same key (scoped to your account) returns the cached
response immediately with an Idempotency-Replay: true response header —
the email is not sent again.
Idempotency-Key: order_invoice_12345
Pagination
List endpoints that return multiple records use a consistent pagination envelope:
{
"data": [ /* array of records */ ],
"pagination": {
"total": 42,
"page": 1,
"limit": 20,
"pages": 3
}
}
Query parameters: page (default 1) and
limit (default 20, max 100).
pages is Math.ceil(total / limit).
Retention and the clamped flag
List endpoints that support a date range (e.g. email logs) respect your plan's
data-retention window. When your requested from date falls outside the
retention window, it is silently moved forward to the earliest allowed date.
The response includes a retention object that tells you when this happened:
"retention": {
"days": 7,
"clamped": true
}
| Field | Meaning |
|---|---|
days |
How many calendar days back your plan allows you to query |
clamped |
true — your from date was earlier than the retention limit; results start from the retention boundary. false — your date range was within the allowed window |
Upgrade your plan to increase the retention window.
Rate limits
All authenticated endpoints are limited to 100 requests per 60-second window per API key. Every response includes three headers:
| Header | Value |
|---|---|
X-RateLimit-Limit | 100 |
X-RateLimit-Remaining | Requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the window resets |
When the limit is exceeded, the API returns 429 with
{ "error": "Rate limit exceeded. Please slow down." }.
See Errors → 429 for the full shape and
handling guidance.
Sending limits
In addition to the per-minute rate limit, your account has two independent sending
caps that apply to POST /v1/emails/send and
POST /v1/emails/batch:
| Limit | Status | Resets |
|---|---|---|
| Monthly — total emails per calendar month | 402 |
1st of each month (UTC) |
Daily — floor(monthlyLimit / 30) per day |
429 |
Midnight UTC |
Unlimited plans (Enterprise) skip both checks. See Errors → 402 and Errors → 429 for the exact error shapes, including the batch variants.