Error reference
Reference every error the Samva API can return, its HTTP status, cause, response fields, and how to resolve it.
Every Samva API error is returned as a flat JSON body. A _tag field carries the machine-readable
discriminator, and the remaining fields are specific to that error type. There is no wrapper
object; the fields sit at the top level of the response. The _tag value determines the HTTP
status code. For the request and response conventions around errors, see the
REST API reference.
{
"_tag": "ValidationError",
"message": "Invalid request body",
"fields": {
"to": ["Must be a valid email address"]
}
}Error types
_tag | Status | Cause | How to resolve |
|---|---|---|---|
ValidationError | 422 | The request body or parameters failed validation. | Read fields for per-field detail and correct the request. |
UnauthorizedError | 401 | The API key is missing or invalid. | Check the X-API-Key header carries a valid, unrevoked key. |
ForbiddenError | 403 | The key lacks permission, or the workspace's sending is paused. | Use a key with the required scope. For a pause, message names the observed rate; email support@samva.dev. |
PaymentRequiredError | 402 | A plan usage limit or entitlement blocks the request. | Reduce usage or upgrade the plan; currentUsage and limit show the ceiling. resource names which ceiling, including daily send quota. |
ResourceNotFoundError | 404 | The referenced resource does not exist. | Check the id; resource and id name what was not found. |
ConflictError | 409 | The request conflicts with existing state (e.g. duplicate). | Reconcile with the existing resource named by resource. |
RateLimitedError | 429 | The organization exceeded its request rate limit. | Back off for retryAfterSeconds, then retry. A daily quota is a 402, not this. |
FlagDisabledError | 403 | A feature required by the request is not enabled. | The feature is unavailable for the organization; no client-side fix. |
WebhookEnqueueError | 500 | The message was accepted but its webhook could not be queued. | Transient; the message send itself succeeded. Retry-safe on the client. |
ExternalServiceError | 502 | An upstream service dependency returned an error. | Transient upstream fault; retry with backoff. |
BillingProviderError | 502 | The billing service is temporarily unavailable. | Transient; retry with backoff. |
InternalError | 500 | An unexpected server error. | Retry with backoff; if it persists, contact support. |
GatewayTimeoutError | 504 | The request did not finish inside the API's time limit. | Retry after retryAfterSeconds. Nothing was refused; the work did not finish. |
Response fields
Each error carries only the fields listed for its type. Common fields:
messageis present onValidationError,UnauthorizedError,ForbiddenError,ConflictError,ExternalServiceError,BillingProviderError, andInternalError.operationnames what the API was doing. It is present onExternalServiceError,BillingProviderError,InternalError, andGatewayTimeoutError.resourceandidare present onResourceNotFoundError.fieldsis an optional map of field name to error messages onValidationError.retryAfterSecondsis present onRateLimitedErrorandGatewayTimeoutError.currentUsageandlimitare present onPaymentRequiredError.flagis present onFlagDisabledError.reasonis present onWebhookEnqueueError.
ValidationError (422)
Returned when the request body or parameters fail schema validation. The optional fields map
lists per-field error messages.
{
"_tag": "ValidationError",
"message": "Invalid request body",
"fields": {
"to": ["Must be a valid email address"]
}
}UnauthorizedError (401)
Returned when the API key cannot be validated. See API keys.
{
"_tag": "UnauthorizedError",
"message": "Invalid API key"
}ForbiddenError (403)
Returned when the key is valid but not authorized for the request.
{
"_tag": "ForbiddenError",
"message": "API key lacks the required permission"
}PaymentRequiredError (402)
Returned when a plan usage limit or entitlement blocks the request. currentUsage and limit
describe the ceiling reached for resource.
{
"_tag": "PaymentRequiredError",
"resource": "messages",
"currentUsage": 10000,
"limit": 10000
}ResourceNotFoundError (404)
Returned when the referenced resource does not exist. resource and id identify what was not
found.
{
"_tag": "ResourceNotFoundError",
"resource": "Message",
"id": "msg_7q2xk9mvt4znw8rh"
}ConflictError (409)
Returned when the request conflicts with existing state, such as a duplicate. resource names the
conflicting resource, and id identifies it when applicable.
{
"_tag": "ConflictError",
"message": "A webhook with this URL already exists",
"resource": "webhook"
}RateLimitedError (429)
Returned when the organization exceeds its request rate limit. retryAfterSeconds tells you how
long to wait before retrying.
{
"_tag": "RateLimitedError",
"retryAfterSeconds": 30
}A daily send quota is not this error. It returns PaymentRequiredError with no Retry-After,
because backing off for a few seconds does not clear it. See
Sending limits.
FlagDisabledError (403)
Returned when the request requires a feature that is not enabled for the organization. flag
names the feature.
{
"_tag": "FlagDisabledError",
"flag": "campaigns"
}WebhookEnqueueError (500)
Returned when a message was accepted but Samva could not enqueue its webhook delivery. The send itself succeeded; this reflects a transient failure in the webhook pipeline.
{
"_tag": "WebhookEnqueueError",
"reason": "Failed to enqueue webhook delivery"
}ExternalServiceError (502)
Returned when an upstream service dependency returns an error. Provider diagnostics are kept server-side for internal logging and are not included in the public response.
{
"_tag": "ExternalServiceError",
"operation": "email.send",
"message": "Upstream service is temporarily unavailable"
}BillingProviderError (502)
Returned when the billing service is temporarily unavailable while resolving entitlements. Provider diagnostics and causes are kept server-side for internal logging.
{
"_tag": "BillingProviderError",
"operation": "billing",
"message": "Billing service is temporarily unavailable"
}InternalError (500)
Returned for an unexpected server error. Retry with backoff; if it persists, contact support.
{
"_tag": "InternalError",
"operation": "email.send",
"message": "Internal server error"
}When the API's own database refused the query, operation is http.request and the response
carries x-samva-error-layer: sql.
GatewayTimeoutError (504)
Returned when a request runs past the time limit the API allows and is abandoned rather than held
open. The response carries Retry-After with the same value as retryAfterSeconds and
x-samva-error-layer: deadline.
A 504 says the request did not finish, not that it did nothing. Work the handler had already
committed stays committed, so a retry is safe only for a read or for a request carrying the same
Idempotency-Key as the attempt that timed out.
{
"_tag": "GatewayTimeoutError",
"operation": "http.request",
"message": "The request did not complete within the API's time limit.",
"retryAfterSeconds": 1
}Which layer refused
Two response headers name the layer behind a refusal, so a status code alone is never the whole answer.
| Header | Values | Meaning |
|---|---|---|
x-samva-throttle-source | samva_admission | Samva's own plan and API-key admission shed the request. |
x-samva-error-layer | sql, application, provider, deadline | The database, Samva's own side of the wire, a service Samva calls, or the API's time limit. |
Every 5xx Samva's code produced carries x-samva-error-layer. A 5xx without it, and a 429
without x-samva-throttle-source, came from infrastructure ahead of the API and never reached
Samva's code. Both are safe to retry with backoff.
What to retry
| Status | Retry | How |
|---|---|---|
429 | yes | Wait Retry-After seconds, or retryAfterSeconds from the body, then retry. |
500 | yes | Exponential backoff. A repeated x-samva-error-layer: sql is worth reporting. |
502 | yes | Exponential backoff; a service Samva calls is unavailable. |
503 | yes | Exponential backoff with jitter; the API was at capacity and ran no code. |
504 | yes | Wait Retry-After seconds, then retry with the same idempotency key. |
402 | no | A quota or entitlement blocks it. Waiting does not clear it. |
4xx | no | Correct the request. |
A read is safe to retry as-is. A write is not, because a 5xx does not tell you whether the write
landed: a 504 in particular can follow work the handler already committed. Send the same
Idempotency-Key you used the first time so the retry
replays the original result instead of performing the write twice. Without a key, read the
resource back before retrying.