REST API

Reference REST conventions for Samva's email API, covering auth, request and response shapes, status codes, rate limits, pagination, 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.

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/v1

All 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: samva_sk_live_your_api_key

See API keys for key format and management.

Headers

HeaderRequiredDescription
X-API-KeyYesYour API key.
Content-TypeFor requests with a bodyapplication/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:

MethodPathPurpose
POST/v1/messagesSend a message.
GET/v1/messagesList 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" }],
  "cc": [{ "email": "grace@example.com" }],
  "channel": "email",
  "email": {
    "subject": "Welcome to Samva",
    "html": "<h1>Welcome!</h1>",
    "text": "Welcome!",
    "replyTo": ["support@example.com"]
  }
}

Recipient fields

to is required. cc and bcc are optional. Each field is an array, and each entry is one of:

FormExample
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

FieldTypeRequiredDescription
subjectstringCond.Email subject line. Required unless templateId is provided.
htmlstringCond.HTML body. Required unless templateId is provided.
textstringNoPlain-text body.
replyTostring[]NoReply-To address(es).
templateIdstringNoSend 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:

_tagStatusFields
ValidationError422message, fields (optional)
ResourceNotFoundError404resource, id
UnauthorizedError401message
PaymentRequiredError402resource, currentUsage, limit
ForbiddenError403message
ConflictError409message, resource
RateLimitedError429retryAfterSeconds
ExternalServiceError502provider, message
InternalError500message

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 full list of error types, their fields, and how to resolve each one, see the Error reference. The machine-readable source of truth is the OpenAPI specification.

HTTP status codes

CodeMeaningNotes
200OKRequest completed.
201CreatedResource created (a successful send returns 201).
400Bad RequestMalformed request.
401UnauthorizedMissing or invalid API key.
402Payment RequiredUsage limit or plan restriction.
403ForbiddenKey lacks permission for the resource.
404Not FoundResource does not exist.
409ConflictDuplicate request.
422Unprocessable EntityValidation failed.
429Too Many RequestsRate limit exceeded.
500Internal Server ErrorUnexpected server error.
502Bad GatewayUpstream provider error.
503Service UnavailableTemporary 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.

ParameterDefaultDescription
page1Page number (1-based).
limit20Items per page (maximum 100).
GET /v1/messages?page=2&limit=50

Paginated responses include a pagination object.

{
  "data": [],
  "pagination": {
    "page": 2,
    "limit": 50,
    "total": 245,
    "totalPages": 5
  }
}
FieldTypeDescription
pagination.pagenumberCurrent page.
pagination.limitnumberItems per page.
pagination.totalnumberTotal matching items.
pagination.totalPagesnumberTotal number of pages.

Idempotency

POST /v1/messages accepts an optional idempotencyKey in the JSON body. Use a stable key for one logical send whenever your application may retry. The key is scoped to your organization and must contain 1 to 255 characters.

Repeating an identical request with the same key returns the original message without creating another message, provider send, or billing effect. Reusing the key with different request data returns 409 Conflict. If an attempt fails before the send is committed, Samva releases the claim so a corrected retry can proceed with the same key.

OpenAPI specification

The live machine-readable specification is published at api.samva.app/v1/openapi.json. See the OpenAPI reference for details.

On this page