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

_tagStatusCauseHow to resolve
ValidationError422The request body or parameters failed validation.Read fields for per-field detail and correct the request.
UnauthorizedError401The API key is missing or invalid.Check the X-API-Key header carries a valid, unrevoked key.
ForbiddenError403The 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.
PaymentRequiredError402A 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.
ResourceNotFoundError404The referenced resource does not exist.Check the id; resource and id name what was not found.
ConflictError409The request conflicts with existing state (e.g. duplicate).Reconcile with the existing resource named by resource.
RateLimitedError429The organization exceeded its request rate limit.Back off for retryAfterSeconds, then retry. A daily quota is a 402, not this.
FlagDisabledError403A feature required by the request is not enabled.The feature is unavailable for the organization; no client-side fix.
WebhookEnqueueError500The message was accepted but its webhook could not be queued.Transient; the message send itself succeeded. Retry-safe on the client.
ExternalServiceError502An upstream service dependency returned an error.Transient upstream fault; retry with backoff.
BillingProviderError502The billing service is temporarily unavailable.Transient; retry with backoff.
InternalError500An unexpected server error.Retry with backoff; if it persists, contact support.
GatewayTimeoutError504The 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:

  • message is present on ValidationError, UnauthorizedError, ForbiddenError, ConflictError, ExternalServiceError, BillingProviderError, and InternalError.
  • operation names what the API was doing. It is present on ExternalServiceError, BillingProviderError, InternalError, and GatewayTimeoutError.
  • resource and id are present on ResourceNotFoundError.
  • fields is an optional map of field name to error messages on ValidationError.
  • retryAfterSeconds is present on RateLimitedError and GatewayTimeoutError.
  • currentUsage and limit are present on PaymentRequiredError.
  • flag is present on FlagDisabledError.
  • reason is present on WebhookEnqueueError.

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.

HeaderValuesMeaning
x-samva-throttle-sourcesamva_admissionSamva's own plan and API-key admission shed the request.
x-samva-error-layersql, application, provider, deadlineThe 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

StatusRetryHow
429yesWait Retry-After seconds, or retryAfterSeconds from the body, then retry.
500yesExponential backoff. A repeated x-samva-error-layer: sql is worth reporting.
502yesExponential backoff; a service Samva calls is unavailable.
503yesExponential backoff with jitter; the API was at capacity and ran no code.
504yesWait Retry-After seconds, then retry with the same idempotency key.
402noA quota or entitlement blocks it. Waiting does not clear it.
4xxnoCorrect 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.

On this page