REST API
REST conventions for the Samva email API — base URL, authentication, request and response shapes, status codes, rate limits, pagination, idempotency, and errors.
Conventions reference for Samva's REST API. Every email send and management call resolves to a request against this API. This page documents the shared conventions — base URL, headers, body and response shapes, status codes, rate limiting, pagination, idempotency, and error format.
Email first. Samva is launching with email. SMS, WhatsApp, and voice are staged and will be documented as they ship.
For a runnable send walkthrough (curl, Python, Go, PHP), see Send an email. For per-endpoint request and response detail, see the API reference.
Base URL
https://api.samva.app/v1All endpoints are relative to this base URL.
Authentication
Every request authenticates with an API key passed in the X-API-Key header.
X-API-Key: sk_sm_your_api_keySee Authentication for key format and management.
Headers
| Header | Required | Description |
|---|---|---|
X-API-Key | Yes | Your API key. |
Content-Type | For requests with a body | application/json for POST, PUT, and PATCH. |
Content type
Requests carrying a body must send Content-Type: application/json. All responses are returned as JSON.
Endpoints
All operations are REST endpoints under the base URL. The send path is the most common:
| Method | Path | Purpose |
|---|---|---|
POST | /v1/messages | Send a message. |
GET | /v1/messages | List messages. |
GET | /v1/messages/{id} | Retrieve a single message. |
There is no email.send HTTP endpoint. The SDK's samva.email.send is an ergonomic wrapper
that posts to POST /v1/messages. All REST calls use the POST /v1/messages body shape below.
Full per-resource endpoint listings (conversations, contacts, webhooks, API keys, organizations) live in the API reference.
Request body
POST /v1/messages accepts a JSON body with a channel discriminator and channel content nested under a matching key. For email, content is nested under email.
{
"to": [{ "email": "ada@example.com" }],
"channel": "email",
"email": {
"subject": "Welcome to Samva",
"html": "<h1>Welcome!</h1>",
"text": "Welcome!"
}
}to field
to is an array. Each entry is one of:
| Form | Example |
|---|---|
| Email address object | { "email": "ada@example.com" } |
| Existing contact reference | { "contactId": "c0a8011e-0000-4000-8000-000000000001" } |
Both forms resolve to the same POST /v1/messages endpoint.
email object
| Field | Type | Required | Description |
|---|---|---|---|
subject | string | Cond. | Email subject line. Required unless templateId is provided. |
html | string | Cond. | HTML body. Required unless templateId is provided. |
text | string | No | Plain-text body. |
templateId | string | No | Send from a stored template instead of inline subject/html. |
Response format
Success
A successful POST /v1/messages returns 201 with the created message object. A newly
accepted send starts in pending status. The full object carries many more fields (recipients,
deliveries, metadata); the most relevant are shown here:
{
"id": "3f2a9c1e-7b4d-4e8a-9c2f-1a2b3c4d5e6f",
"status": "pending",
"channel": "email",
"createdAt": "2024-01-15T10:30:00.000Z"
}See the API reference for the complete response schema.
Error
Errors are returned as a flat JSON object. A _tag field carries the machine-readable
discriminator, and the remaining fields are specific to that error type. There is no wrapper
object — the error fields sit at the top level of the response body.
A validation failure (422):
{
"_tag": "ValidationError",
"message": "Invalid request body",
"fields": {
"to": ["Must be a valid email address"]
}
}The _tag value maps to the HTTP status code:
_tag | Status | Fields |
|---|---|---|
ValidationError | 422 | message, fields (optional) |
ResourceNotFoundError | 404 | resource, id |
UnauthorizedError | 401 | message |
PaymentRequiredError | 402 | resource, currentUsage, limit |
ForbiddenError | 403 | message |
ConflictError | 409 | message, resource |
RateLimitedError | 429 | retryAfterSeconds |
ExternalServiceError | 502 | provider, message |
InternalError | 500 | message |
For example, a missing resource (404):
{
"_tag": "ResourceNotFoundError",
"resource": "Message",
"id": "3f2a9c1e-7b4d-4e8a-9c2f-1a2b3c4d5e6f"
}A rate-limit response (429):
{
"_tag": "RateLimitedError",
"retryAfterSeconds": 30
}For the authoritative list of error types and their fields, see the OpenAPI specification.
HTTP status codes
| Code | Meaning | Notes |
|---|---|---|
200 | OK | Request completed. |
201 | Created | Resource created (a successful send returns 201). |
400 | Bad Request | Malformed request. |
401 | Unauthorized | Missing or invalid API key. |
402 | Payment Required | Usage limit or plan restriction. |
403 | Forbidden | Key lacks permission for the resource. |
404 | Not Found | Resource does not exist. |
409 | Conflict | Duplicate request. |
422 | Unprocessable Entity | Validation failed. |
429 | Too Many Requests | Rate limit exceeded. |
500 | Internal Server Error | Unexpected server error. |
502 | Bad Gateway | Upstream provider error. |
503 | Service Unavailable | Temporary outage; retry later. |
Rate limiting
Requests are rate-limited per organization. When a limit is exceeded, the API returns 429 Too Many Requests with a RateLimitedError body. Its retryAfterSeconds field tells you how long to wait before retrying.
{
"_tag": "RateLimitedError",
"retryAfterSeconds": 30
}Back off for retryAfterSeconds and retry.
Pagination
List endpoints accept page and limit query parameters.
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number (1-based). |
limit | 20 | Items per page (maximum 100). |
GET /v1/messages?page=2&limit=50Paginated responses include a pagination object.
{
"data": [],
"pagination": {
"page": 2,
"limit": 50,
"total": 245,
"totalPages": 5
}
}| Field | Type | Description |
|---|---|---|
pagination.page | number | Current page. |
pagination.limit | number | Items per page. |
pagination.total | number | Total matching items. |
pagination.totalPages | number | Total number of pages. |
Idempotency
Samva automatically deduplicates identical sends: a message with the same recipient and content submitted again within a short window resolves to the original message instead of sending twice. You don't need to supply an idempotency key.
OpenAPI specification
The live machine-readable specification is published at api.samva.app/v1/openapi.json. See the OpenAPI reference for details.