Error reference

Reference every error the Samva API can return, with its HTTP status, response fields, cause, and how to resolve it.

Every Samva API error is returned as a flat JSON body. The _tag field is the machine-readable discriminator, and the remaining fields sit at the top level of the response beside it. The _tag determines the HTTP status code. The SDK throws a class named for each _tag; see Error handling.

{
  "_tag": "ValidationError",
  "message": "Invalid request",
  "fields": {
    "email.tracking.typo": ["Unexpected property"]
  }
}

Branch on _tag, never on message text.

After a failed or interrupted mailbox send response, preserve the original request and Idempotency-Key. If the error includes a receipt, observe its actionId. Follow mailbox recovery before submitting another email.

Error types

This table lists every error the public API declares. Each operation's API reference page lists the errors that operation can return.

_tagStatusFieldsCauseHow to resolve
AnalyticsQueryError400messageAn analytics query's date range or limit is out of bounds.Correct the query as message describes.
UnauthorizedError401messageThe credential is missing, invalid, revoked, or expired.Send a valid credential. See Authentication.
PaymentRequiredError402message, resource, currentUsage, limitA plan limit or daily send quota blocks the request.Add a payment method, upgrade, or wait for the limit to reset.
ForbiddenError403messageThe credential lacks permission, or the organization's sending is paused.Use a credential with the permission message names, or contact support.
OnboardingReviewRequiredError403message, code, decision, stage, nextAction, identityIdEmail review blocks the organization or the sending identity.Follow nextAction.
MailboxSendRefusedError403message, receiptA mailbox policy refuses the recorded send.Inspect the receipt and resolve the refusal before deliberately submitting a new intent.
FlagDisabledError403message, flagThe request uses a feature that is not enabled for the organization.No client-side fix; contact support.
ResourceNotFoundError404message, resource, idThe referenced resource does not exist in your organization.Check the id.
ConflictError409message, resource, idThe request conflicts with existing state.Reconcile with the resource resource and id name.
MailboxSendConflictError409message, resource, id, receiptA competing decision or request already owns the send action.Observe the winning receipt's action instead of submitting a replacement email.
MalformedRequestError400message, fieldsThe request body carries a field its endpoint does not declare.Remove or replace the fields named in fields; the messages list the declared alternatives.
ValidationError422message, fieldsThe request body or parameters failed validation.Correct the request; fields gives per-field detail.
WhatsAppServiceWindowClosedError422message, closedAtA free-form WhatsApp message is outside the customer service window.Send an approved template, or wait for the customer to reply.
TemplateSourceError422message, diagnostics, optional revisionThe template source cannot build, so a save, open, publish, or restore is refused.Reload the workspace when revision is present, fix the source, then retry.
BrandAddressRequiredError422message, brandA campaign's footer brand has no postal address.Add the address in the brand's footer, then schedule or send again.
CampaignFooterRequiredError422messageA campaign turned the footer off and its template does not render BrandFooter.Turn the footer on, or render BrandFooter in the template.
FromAddressRequiredError422message, verifiedDomainsA send omitted from and no default sender applies.Pass from on a verifiedDomains domain, or set a default sender.
RateLimitedError429message, retryAfterSecondsThe organization or API key exceeded its send rate or a probation limit.Wait retryAfterSeconds, then retry.
InternalError500messageAn unexpected server error.Retry with backoff; contact support if it persists.
BillingProviderError502messageSamva could not read the organization's plan.Retry with backoff.
EmailChannelError502reason, message, statusCodeThe email service refused or could not complete a domain or sender step.Retry when reason is transport or rate_limited.
ExternalServiceError502messageA service Samva depends on returned an error.Retry with backoff.
DeliveryUnavailableError503message, retryAfterSecondsSamva could not hand an accepted send to delivery.Wait retryAfterSeconds, then retry with the same Idempotency-Key.
ServiceUnavailableError503message, retryAfterSecondsA required service is temporarily unavailable.Wait retryAfterSeconds, then retry with the same idempotency key.
SenderNotReadyError503message, sender, retryAfterSecondsThe sending identity is still being prepared or verified.Wait for it to become ready; honor retryAfterSeconds when present.
GatewayTimeoutError504message, retryAfterSecondsThe request did not finish inside the API's time limit.Wait retryAfterSeconds, then retry with the same idempotency key.

The published OpenAPI document marks each error schema with x-retryable and x-category, so a client can decide whether to retry without matching on _tag.

Errors in detail

AnalyticsQueryError (400)

Returned when an analytics query cannot be answered as asked, for example a before earlier than its after, or a range longer than the operation allows. message says which.

{
  "_tag": "AnalyticsQueryError",
  "message": "before must not precede after"
}

UnauthorizedError (401)

Returned when the credential cannot be validated. See Authentication.

{
  "_tag": "UnauthorizedError",
  "message": "Invalid API key"
}

PaymentRequiredError (402)

Returned when a plan limit or entitlement blocks the request. resource names the ceiling reached, and currentUsage and limit describe it. A spent daily send quota is this error with resource set to daily send quota; see Daily send quota.

{
  "_tag": "PaymentRequiredError",
  "message": "daily send quota limit reached: usage 1000, limit 1000. Resets at 00:00 UTC. Raise your plan's limit in your dashboard's Settings > Billing (/dashboard/<orgSlug>/settings/billing).",
  "resource": "daily send quota",
  "currentUsage": 1000,
  "limit": 1000
}

ForbiddenError (403)

Returned when the credential is valid but not authorized for the request. A missing permission names the permission in message. While an organization's sending is paused or disabled, every send returns this error with message saying why; see Paused sending.

{
  "_tag": "ForbiddenError",
  "message": "Caller requires contacts:create permission"
}

OnboardingReviewRequiredError (403)

Returned by a send when your organization is waiting for review before its first send, when review rejected it, or when review rejected the domain or sender the message would go out from. code says what is blocking, decision and stage report the review, and nextAction says what to do: wait_for_review, provide_information, verify_sending_identity, reapply, or contact_support. identityId names the domain or sender when the block is about one identity. See Check email review status.

{
  "_tag": "OnboardingReviewRequiredError",
  "message": "Email sending is blocked by review (review_in_progress). Wait for review to complete before sending again.",
  "code": "review_in_progress",
  "decision": "undecided",
  "stage": "manual_review",
  "nextAction": "wait_for_review"
}

FlagDisabledError (403)

Returned when the request uses a feature that is not enabled for your organization. flag names the feature.

{
  "_tag": "FlagDisabledError",
  "message": "Feature 'email' is not enabled for your organization. Email support@samva.dev to request access.",
  "flag": "email"
}

MailboxSendRefusedError (403)

Returned when a mailbox policy refuses a recorded send. receipt identifies the action and its outcome. Inspect its decision and problem before correcting the policy or email. Replaying the original request observes that action; it does not bypass the refusal.

See mailbox approvals.

ResourceNotFoundError (404)

Returned when the referenced resource does not exist in your organization. resource and id identify what was not found.

{
  "_tag": "ResourceNotFoundError",
  "message": "Message 'msg_7q2xk9mvt4znw8rh' not found. Call messages.list to find valid ids.",
  "resource": "Message",
  "id": "msg_7q2xk9mvt4znw8rh"
}

ConflictError (409)

Returned when the request conflicts with existing state, such as a duplicate, or an Idempotency-Key reused with a different request. resource names the conflicting resource, and id identifies it when applicable.

{
  "_tag": "ConflictError",
  "message": "A webhook with this URL already exists",
  "resource": "webhook"
}

MailboxSendConflictError (409)

Returned when another resolution wins, an email has already been accepted before cancellation, or the request conflicts with the original send. receipt is the authoritative current phase of the action named by id. Observe that action and preserve its original request key; do not replace it to recover an uncertain outcome.

ValidationError (422)

Returned when the request body or parameters fail validation. The optional fields map lists per-field messages. Request-shape failures use Invalid request and dotted paths, including array indices. Query and path failures name the parameter; an undecodable cursor names cursor. Fix the listed fields before retrying.

{
  "_tag": "ValidationError",
  "message": "Invalid request",
  "fields": {
    "email.tracking.typo": ["Unexpected property"]
  }
}

TemplateSourceError (422)

Returned when a template workspace save, open, publish, or restore is refused because the source cannot build. diagnostics lists the findings, each with code, severity, message, and origins (fileName, lineNumber, columnNumber). A compiler finding names its locations. When the project or a historical entry is rejected without one, diagnostics holds a single sml/build-failed finding whose origins is empty. Fix the source as the findings describe, then retry. A real conflict, such as a moved branch head or a save already in progress, is a ConflictError. Checking a workspace reports findings in a 200 response instead of failing. If Save pulled an outside commit before compilation failed, the error also includes revision, the workspace revision those diagnostics describe. Reload the workspace and use its source, revision, and head for the repair. Both editors adopt that workspace automatically.

{
  "_tag": "TemplateSourceError",
  "message": "The template source has compiler errors.",
  "diagnostics": [
    {
      "code": "unknown-field",
      "severity": "error",
      "message": "Unknown field `nmae`; did you mean `name`?",
      "origins": [{ "fileName": "templates/welcome.tsx", "lineNumber": 12, "columnNumber": 4 }]
    }
  ]
}

RateLimitedError (429)

Returned when the organization or the API key exceeds its send rate, or an organization on probation exceeds a probation limit. retryAfterSeconds, and the Retry-After header with the same value, say how long to wait. A spent daily quota is a 402, not this error, because waiting a few seconds does not clear it. The rates are in Rate limiting and the probation limits in Probation limits.

{
  "_tag": "RateLimitedError",
  "message": "Organization or API key send rate limit reached. Retry after 1 seconds.",
  "retryAfterSeconds": 1
}

InternalError (500)

Returned for an unexpected server error. Retry with backoff; if it persists, contact support.

{
  "_tag": "InternalError",
  "message": "Internal server error"
}

BrandAddressRequiredError (422)

Returned when a campaign is scheduled or sent and the brand that renders its footer has no postal address. Campaigns sign off with a mailing address; transactional sends never fail on it. brand names the brand to fix, or is null when no brand could be resolved.

{
  "_tag": "BrandAddressRequiredError",
  "message": "Add a postal address to the brand \"default\" before scheduling this campaign: a campaign's footer must include one.",
  "brand": "default"
}

CampaignFooterRequiredError (422)

Returned when a campaign is scheduled or sent with the appended footer turned off and a template that does not render BrandFooter. Every campaign carries the brand footer: its postal address and unsubscribe link. Transactional sends never fail on it.

{
  "_tag": "CampaignFooterRequiredError",
  "message": "A campaign must carry the brand footer (postal address and unsubscribe link). Turn the footer on, or render BrandFooter from samva:brand in the template."
}

FromAddressRequiredError (422)

Returned when a send omits from, the template names no fromDefault, and no default sender applies: the default of the send's namespace, then of the organization, then its only verified sender. verifiedDomains lists the verified domains the caller can send from, sorted; it is empty when the organization has no verified domain. Any address on a verified domain is a valid from without a sender row. Pass one, or make a verified sender the default with Set the default sender.

{
  "_tag": "FromAddressRequiredError",
  "message": "No default sender for this organization. Pass `from` with any address on acme.test, or add a sender on that domain (POST /v1/email/senders, `samva email senders add`) and make it the default (POST /v1/email/senders/:id/default, `samva email senders set-default`).",
  "verifiedDomains": ["acme.test"]
}

BillingProviderError (502)

Returned when Samva cannot read your organization's plan while deciding whether to admit the request. Retry with backoff.

{
  "_tag": "BillingProviderError",
  "message": "Billing service is temporarily unavailable"
}

EmailChannelError (502)

Returned by domain, sender, tracking, and suppression operations when the email service refuses or cannot complete the step. reason is one of:

reasonMeaningRetry
transportThe email service was temporarily unavailable.yes
rate_limitedThe email service is temporarily rate limited.yes
rejectedThe email service rejected the request.no
authSamva could not authenticate to the service.no

statusCode, when present, is the status the email service answered with.

{
  "_tag": "EmailChannelError",
  "reason": "transport",
  "message": "Email service is temporarily unavailable"
}

ExternalServiceError (502)

Returned when a service Samva depends on returns an error. The response carries no diagnostics from that service. Retry with backoff.

{
  "_tag": "ExternalServiceError",
  "message": "Upstream service is temporarily unavailable"
}

DeliveryUnavailableError (503)

Returned by a send when Samva accepted the request but could not hand it to delivery. Nothing was sent. retryAfterSeconds, and the Retry-After header with the same value, say how long to wait; then retry with the same Idempotency-Key, and if an earlier attempt did complete, the retry returns its message instead of sending again.

{
  "_tag": "DeliveryUnavailableError",
  "message": "Samva could not hand the message to delivery. Retry the request.",
  "retryAfterSeconds": 5
}

ServiceUnavailableError (503)

Returned when a required service is temporarily unavailable. The response carries Retry-After with the same value as retryAfterSeconds. Wait that long before retrying. For a write, reuse the same Idempotency-Key because an earlier attempt may have completed.

{
  "_tag": "ServiceUnavailableError",
  "message": "The service is temporarily unavailable. Retry the request.",
  "retryAfterSeconds": 1
}

SenderNotReadyError (503)

Returned by a send when its sending identity is still being prepared or verified. sender names the sending address. Wait for that identity to become ready before replaying the original request with the same Idempotency-Key.

retryAfterSeconds is optional. When present, it and the Retry-After header say how long to wait before another attempt. When absent, no retry interval is supplied. This request error does not arrange an automatic retry. A queued mailbox action already owns its retries; observe that action instead of starting another send.

{
  "_tag": "SenderNotReadyError",
  "message": "The sending identity is not ready to send yet.",
  "sender": "support@acme.samva.email"
}

GatewayTimeoutError (504)

Returned when a request runs past the API's time limit and is abandoned rather than held open, or when work the request waits on misses its own deadline. 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 already committed stays committed, so retry a write only with the same Idempotency-Key as the attempt that timed out. Template workspace writes carry their key in the Idempotency-Key header; retrying a save with the same key returns the commit the first attempt made instead of committing twice.

{
  "_tag": "GatewayTimeoutError",
  "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, deadlineSamva's database, Samva's own code, 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. A 413 with no _tag also came from ahead of the API: the body exceeded the request size, and a retry of the same body fails the same way.

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.
502usuallyExponential backoff, except an EmailChannelError whose reason says not to.
503yesHonor Retry-After when present; otherwise use exponential backoff with jitter.
504yesWait Retry-After seconds, then retry with the same idempotency key.
402noA quota or entitlement blocks it. Waiting a few seconds 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. 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.

Related documentation

On this page