Documentation Index: Fetch llms.txt first to discover every published page. This page is also available as Markdown at /python-errors-reference.md.
Verified · 8/11/2026

Python SDK Error Reference

Look up every error the Python SDK raises, PrimitiveAPIError fields, webhook verification/payload/validation codes, and X402Error status conditions, with the fix for each.

The Python SDK raises three error families: PrimitiveAPIError from the email client, the webhook error classes (WebhookVerificationError, WebhookPayloadError, WebhookValidationError, RawEmailDecodeError), and X402Error from every x402 method. All are plain Python exceptions you catch with try/except.

PrimitiveAPIError#

Raised by client.send, client.reply, client.forward, and client.semantic_search (and their a* async variants) whenever the API responds with a non-2xx status, or the response envelope is missing its data field.

from primitive.client import PrimitiveAPIError, PrimitiveClient

client = PrimitiveClient(api_key="prim_test")

try:
    client.send(
        from_email="support@example.com",
        to="alice@example.com",
        subject="Hello",
        body_text="Hi there",
    )
except PrimitiveAPIError as err:
    print(err.status_code, err.code, str(err))
FieldTypeDescription
status_codeint | NoneHTTP status code. None when no response was ever parsed.
codestr | NoneMachine-readable error code from the API's error.code field (e.g. validation_error, recipient_not_allowed, rate_limit_exceeded).
gateslist[dict] | NoneGate-denial details when a send is blocked by an authorization gate. Each entry carries name, reason, subject, message, and an optional fix.
request_idstr | NoneThe API's request id, useful when filing a support ticket.
retry_afterint | NoneSeconds to wait before retrying, parsed from the Retry-After header. Present on 429 responses.
detailsdict | NoneAdditional structured context (e.g. sent_email_id, required_entitlements).
payloadAnyThe raw parsed error response, or the fallback payload when parsing failed.

str(err) returns the human-readable message.

Common code values and fixes#

codeWhat triggered itFix
validation_errorRequest body failed server-side validation (e.g. sending to an address that hasn't sent authenticated mail yet on the agent plan).Check the error message for the specific field; adjust the request.
recipient_not_allowedA gate denied the send (see gates for detail).Inspect err.gates[0]["reason"] and err.gates[0]["fix"] for the exact remediation, e.g. wait for an inbound email from that address first.
rate_limit_exceededExceeded the sliding-window rate limit (120 requests per 60 seconds per organization).Back off for err.retry_after seconds before retrying.
inbound_not_repliable (HTTP 422)client.reply(...) targeted an inbound row that cannot be replied to: it was rejected at ingestion, its content was discarded, or it has no recipient recorded. A missing Message-Id does not trigger this error; it only omits the threading headers.Don't retry the reply; the message is not repliable.

Webhook errors#

Raised by primitive.receive, primitive.handle_webhook, and primitive.handle_webhook_event. Full webhook verification mechanics are documented on Verifying Webhook Signatures and Handling Webhook Events; this section is the error lookup.

WebhookVerificationError#

Raised when the HMAC (or Standard Webhooks) signature check fails.

codeCauseFix
MISSING_SECRETsecret was None, empty string, or empty bytes; or (Standard Webhooks) the secret is not valid base64 (with or without the whsec_ prefix), or decodes to zero bytes.Pass the real webhook secret from GET /account/webhook-secret, as a UTF-8 string, unmodified.
INVALID_SIGNATURE_HEADERThe Primitive-Signature header is missing the t=/v1= format, or (Standard Webhooks) webhook-signature is present but empty, or webhook-id/webhook-timestamp is missing, or webhook-timestamp is not a unix-seconds integer.Confirm the header is forwarded to your handler unmodified.
TIMESTAMP_OUT_OF_RANGEThe signature timestamp is more than tolerance_seconds (default 300s) old, or more than 60 seconds in the future.Check server clock sync; pass a larger tolerance_seconds only if you understand the replay-window tradeoff.
SIGNATURE_MISMATCHThe computed HMAC does not match any signature in the header.Verify the secret matches your account, and that you're signing the raw request body, not a re-serialized json.dumps() of it.

WebhookPayloadError#

Raised when the request body isn't parseable as the expected shape.

codeCauseFix
PAYLOAD_EMPTY_BODYThe request body is empty.Check your web framework passes the raw body through.
JSON_PARSE_FAILEDThe body isn't valid JSON.Check for truncation or double-encoding before it reaches the SDK.
INVALID_ENCODINGThe body contains invalid UTF-8 bytes.If the data is binary, base64-encode it first.
PAYLOAD_UNDEFINEDNothing was passed to parse_webhook_event(...).Pass the parsed body explicitly.
PAYLOAD_NULLThe payload is None.Check that your request-body variable is defined before parsing.
PAYLOAD_IS_ARRAYThe payload is a JSON array, not an object.Webhook payloads are always objects; check upstream framing.
PAYLOAD_WRONG_TYPEThe payload (or a required nested field, e.g. email.content.download.expires_at) is missing or the wrong type.Confirm the path named in the error message is present in the raw payload.
PAYLOAD_MISSING_EVENTNeither the X-Webhook-Event header nor a top-level event field in the body identifies the event type.Pass the X-Webhook-Event header through, or call handle_webhook_event, which reads it for you.

WebhookValidationError#

Raised when a known event type (most commonly email.received) fails JSON Schema validation.

The error code is SCHEMA_VALIDATION_FAILED, and the message describes the schema violation. Fix: compare the payload against json-schema/email-received-event.schema.json, the canonical schema source referenced on Webhook Events Overview.

RawEmailDecodeError#

Raised by decode_raw_email and verify_raw_email_download.

codeCauseFix
NOT_INCLUDEDRaw content wasn't included inline; the error message includes the download URL.Fetch email.content.download.url instead of decoding inline.
INVALID_BASE64email.content.raw.data isn't valid base64.Treat as a corrupted payload; do not retry decoding the same bytes.
HASH_MISMATCHThe decoded (or downloaded) bytes' SHA-256 doesn't match email.content.raw.sha256.The content may be corrupted in transit; re-fetch or re-request delivery.

X402Error#

Raised by every method on primitive.x402.X402Client (charge, create_email_challenge, pay, pay_email_challenge, register_payout_address, get_challenge, get_spend_policy, set_spend_policy, list_payout_addresses, list_declined_payments) on a client-side, transport, or non-2xx server error.

from primitive import X402Error

try:
    x402.pay(challenge, signer=payer)
except X402Error as err:
    print(err.status, err.retry_after, err.body)
FieldTypeDescription
statusintHTTP status, or 0 for a client-side/transport error that never reached the server.
bodyAnyThe parsed error envelope when present, or a truncated raw response body.
retry_afterstr | NoneThe Retry-After response header, when the server sent one.
Warning

On pay(), a status == 0 error means the request may never have reached the server, the payment outcome is indeterminate. Do not blindly retry; check get_challenge(id) or the settlement webhook before resubmitting, since resubmitting a payment that actually landed risks a duplicate authorization attempt.

Status-0 (client-side) conditions#

These never reach the server. All are raised before any HTTP request is made.

Message containsCauseFix
no API key configuredNeither api_key nor PRIMITIVE_API_KEY was set.Pass api_key= explicitly or export PRIMITIVE_API_KEY.
unknown charge() option "..."A typo'd keyword argument to charge().Check the argument name against the documented charge() signature on Creating and Paying Challenges.
exactly one of \amount` ... or `amount_usdc``Both amount and amount_usdc were passed.Pass exactly one.
requires \amount` as a positive integer string ... or `amount_usdc``Neither amount nor amount_usdc was passed, or the value given failed validation.Pass amount_usdc="0.01" (human USDC) or amount="10000" (base units).
at most 6 decimalsamount_usdc had more than 6 decimal places, was non-positive, or was malformed.USDC has 6 decimals; use a value like "0.01".
requires a signerpay() or pay_email_challenge() was called with signer=None or a signer missing the required methods.Pass a PrivateKeySigner or an object implementing sign_typed_data/sign_message.
challenge is missing or malformed: <field>The challenge object passed to pay() is missing a required field (id, network, expires_at, nonce_binding, or a payment_requirements field).Re-fetch the challenge with get_challenge(id) rather than hand-constructing one.
email challenge is missing or malformed: <field>The challenge passed to pay_email_challenge() is missing a required field, or its interaction_id disagrees with challenge.nonce_binding.interaction_id.Use extract_email_challenge(...) to build the challenge from the raw interaction.json part instead of constructing it manually.
interaction.json part is not a valid x402 challenge: <field>extract_email_challenge(...) received a malformed or non-challenge envelope.Confirm the attachment is the unmodified interaction.json part from the challenge email.
already expiredThe challenge's expires_at (plus settlement margin) is in the past.Request a fresh challenge; an expired challenge cannot be signed into a valid authorization.
invalid expires_atThe challenge's expires_at isn't a parseable timestamp.Re-fetch the challenge rather than editing the field by hand.
network mismatchThe challenge's top-level network disagrees with payment_requirements.network.Treat as a malformed challenge; re-fetch it.
could not resolve your organization idregister_payout_address() was called without org= and the account lookup returned no id.Pass org= explicitly, or confirm your API key resolves to a valid organization.
requires \from_`/requires `to``create_email_challenge() was called without one of the required email addresses.Pass both from_ and to.
request timed out / request failedA DNS, connection, or TLS-level failure, or the request exceeded the client's timeout.Retry with backoff; check network connectivity to api.primitive.dev.
non-JSON response / missing success/data envelopeThe server returned something other than the expected {"success": ..., "data": ...} envelope.Usually transient; retry. Persisting failures indicate an API-side issue.

Server-side (non-zero status) conditions#

statusTypical cause
422Payment declined at settlement (e.g. payment_declined in the error message).
429Rate limited; retry_after names the backoff window.
Other 4xx/5xxSee err.body for the server's error.message.

Next steps#

Was this page helpful?

© Primitive SDKs

Powered by Browzer