Skip to content

Errors

Every non-2xx response from the Platform API uses one frozen envelope:

{
"error": {
"code": "distribution_not_ready",
"message": "Distribution must be validated before launch.",
"param": null,
"request_id": "req_9f2c..."
}
}

code is the contract — branch on it, not on message and not on the status alone. request_id is also returned as the Request-Id response header; quote it in support requests.

The SDK picks a class first from the typed code, then from the HTTP status:

import {
BoominError,
AuthenticationError,
PermissionError,
InvalidRequestError,
RateLimitError,
ConflictError,
APIError,
OperationConflictError,
BandLimitReachedError,
FundingRequiredError,
WebhookSignatureVerificationError,
ImmutableParameterError,
ConflictingParametersError,
PayoutRulesRequiredError,
PayoutRailRequiredError,
PayoutRailAlreadyExistsError,
PayoutBatchEmptyError,
PayoutBatchStateError,
} from "@boomin/sdk";

Every one extends BoominError, which carries:

PropertyMeaning
codeTyped BoominErrorCode, or null for pre-flight/connection errors
statusHTTP status, or null when the request never reached the API
requestIdEchoed request id
paramThe offending parameter, when the API names one
rawThe raw error object from the body
try {
await boomin.distributions.launch(id);
} catch (err) {
if (err instanceof FundingRequiredError) {
// top up the wallet; the launch operation is waiting, not dead
} else if (err instanceof OperationConflictError) {
// a live operation already holds this subject — poll it instead
} else if (err instanceof RateLimitError) {
// back off; the SDK already retried maxRetries times
} else if (err instanceof BoominError) {
console.error(err.code, err.status, err.requestId);
}
}

Status → class mapping: 400/402/404/422InvalidRequestError, 401AuthenticationError, 403PermissionError, 409ConflictError, 429RateLimitError, anything else → APIError. Four codes are promoted to their own class: operation_conflict and cancellation_in_progressOperationConflictError, band_limit_reachedBandLimitReachedError, funding_requiredFundingRequiredError.

The SDK retries 429 and 5xx up to maxRetries (default 2) with jittered exponential backoff, honouring Retry-After when present. Mutations are safe to retry because they always carry an Idempotency-Key. Connection errors and timeouts are retried too; 4xx (other than 429) never is.

CodeHTTPWhat it meansRecover by
platform_token_required401No Authorization: Bearer header. v1 is Bearer-only — there is no body-token affordance.Send the header.
platform_token_unknown401The key does not exist.Check for a truncated paste; mint a new key.
platform_token_inactive401The key was revoked.Mint a replacement under Developers.
platform_token_expired401The key passed its expiry.Rotate the key.
platform_token_org_missing401The key’s organization no longer resolves.Contact support.
missing_scope403The key lacks the scope the route requires. The message is missing_scope:<scope>.Mint a key with that scope; see Scopes.
test_mode_not_yet_available400You sent an sk_test_/pk_test_ key. Test mode is reserved but not built.Use a live key against a disposable brand, and validate() / dry runs for launch-era testing.
developer_access_paused503The platform surface is paused by an operator kill switch.Retry later; check status.
CodeHTTPWhat it meansRecover by
invalid_request400Body or parameter failed validation. param names the field.Fix the named field.
invalid_cursor400starting_after does not reference a known object of that type.Restart pagination from the first page.
invalid_status400A status filter value is not in that resource’s set.Use a documented status value.
invalid_event_type400An event type is not in the public vocabulary.Use a type from the events vocabulary.
invalid_period400period_start is not before period_end.Order the dates.
idempotency_key_conflict409The same Idempotency-Key was reused with a different body.Use a fresh key for a genuinely different request.
create_rate_limit_exceeded429Creation rate limit hit.Back off and retry.

brand_not_found, distribution_not_found, deployment_not_found, enrollment_not_found, relationship_not_found, connection_not_found, operation_not_found, program_not_found, entity_not_found, payout_batch_not_found, webhook_endpoint_not_found — all 404.

These are returned identically for “does not exist”, “belongs to another organization”, and “is a malformed id”. That is deliberate: existence is never leaked across tenants.

The state machine refused the verb. Read the object’s current status and either wait or take the legal transition.

CodeRefused because
distribution_not_editableupdate() outside draft | ready.
distribution_programs_draft_onlyProgram associations may only change in draft.
distribution_not_validatablevalidate() outside draft | ready.
distribution_not_readylaunch() before validation put it in ready.
distribution_not_pausablepause() outside active | partially_active.
distribution_not_resumableresume() outside paused.
distribution_not_cancelableAlready terminal.
deployment_not_pausable / _resumable / _cancelableSame, per deployment.
enrollment_not_pausable / _resumableParticipation status already there, or archived.
relationship_not_pausable / _resumable / _endableRelationship status already there, or ended.
connection_not_revocableThe connection — or every grant on it — is already revoked.
CodeHTTPWhat it meansRecover by
operation_conflict409A live operation already holds this subject’s concurrency slot.Retrieve the live operation and poll it instead of enqueueing a second.
cancellation_in_progress409A cancel is holding the subject’s control gate; mutations are refused.Wait for the cancel operation to settle.
cancellation_requires_intervention409A previous cancel failed with unresolved teardown. The gate stays closed by design.The error carries the failed operation id — an operator must retry or resolve it.
channel_type_not_yet_supported422No registered adapter supports that mode/medium/channel/format.Use a supported slot combination. validate() surfaces this before launch.

Repeating cancel() on a subject that already has a live cancel is idempotent — it returns the existing control operation rather than erroring.

CodeHTTPWhat it meansRecover by
performance_event_identity_required422Neither idempotency_key nor external_event_id was supplied.Always send one; it is what makes ingestion exactly-once.
deployment_distribution_mismatch409The deployment/distribution pair does not agree.Send only deployment and let the API resolve the distribution.
CodeHTTPWhat it meansRecover by
payout_rules_required409payouts.run on a brand with no active payout rule and no active content split. Nothing could have paid anyone. → PayoutRulesRequiredErrorCreate a rule: payouts.rules.
payout_rail_required409No active rail of the requested kind, or no usable default. Nothing is auto-provisioned. → PayoutRailRequiredErrorConfigure one: payouts.rails.
payout_rail_already_exists409rails.create for a kind already configured — create is not an upsert. → PayoutRailAlreadyExistsErrorCall rails.update explicitly.
immutable_parameter400A rule’s economics are frozen after creation. param names the frozen concept, not the key you sent. → ImmutableParameterErrorCreate a replacement rule, activate it, archive the old one.
payout_batch_empty409No settle-able payout row for this rail and period. → PayoutBatchEmptyErrorRun the period first, or widen the window.
payout_batch_conflict409A concurrent build raced this one. → PayoutBatchStateErrorRetry, or read the batch that won.
payout_batch_not_exportable / _not_confirmable / _not_cancelable409The batch’s status refuses the verb. → PayoutBatchStateErrorRetrieve the batch and read status.
payout_rule_not_found / payout_rail_not_found404Unknown, malformed, or another tenant’s id.Check the id.
payout_export_format_invalid400The rail’s config.format is not a known export format.Use paypal_payouts_csv or wise_batch_csv.
payout_export_unconfigured400The export has no usable destination configuration. The CLI raises the same code when a batch exported but no presigned URL came back, so --out cannot be written.The file exists but was not delivered — check the rail config and storage credentials.

Full context on each: Getting entities paid.

CodeHTTPWhat it meansRecover by
band_limit_reached402The organization’s active-entity band is full.Upgrade the band; see Pricing.

funding_required is not an error code in the registry — it is an operation waitingReason. A funded launch with an underfunded wallet parks the operation at status: "waiting", waitingReason: "funding_required", and resumes once the wallet is topped up. The SDK’s FundingRequiredError exists for surfaces that raise the same condition synchronously.

Raised by the client, never by the API:

CodeRaised when
operation_wait_timeoutoperations.wait() exceeded its timeout. The operation is still running — poll again.
webhook_signature_invalidconstructEvent could not verify the signature.
request_timeoutThe HTTP request exceeded timeout.
connection_errorThe request never reached the API.
internal_errorThe API returned an unparseable body.
conflicting_parametersA camelCase key and its snake_case twin both appeared in one body, query, or response. → ConflictingParametersError. Send one spelling.