Skip to content

Errors & status codes

How Topiic signals errors in REST responses and what each status code means in context.

Topiic uses standard HTTP status codes and RFC 7807 problem-details JSON bodies on error responses.

{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Forbidden",
"status": 403,
"detail": "Caller does not belong to the requested merchant."
}

title is a short human-readable summary, detail is a longer explanation. Some endpoints add extra fields (e.g. validation error lists under errors).

Always log the body - status alone rarely tells you why a call failed.

CodeMeaning in Topiic context
200 OKRequest succeeded; body holds the resource or list.
201 CreatedResource created; body holds the new resource.
202 AcceptedRequest accepted for processing (e.g. webhook delivery replay queued).
204 No ContentRequest succeeded; no body (delete, simple update).
400 Bad RequestValidation failed. Body usually includes an errors map keyed by field name. Fix the request.
401 UnauthorizedMissing, malformed, expired, or invalid bearer credential. Mint or re-mint your API key.
403 ForbiddenAuthenticated but not allowed - typically the merchant id on the request doesn’t match your API key’s merchant.
404 Not FoundResource id doesn’t exist, or belongs to a merchant you can’t see.
409 ConflictDuplicate (e.g. a nonce already used, a unique-per-merchant email collision). Don’t retry blindly - change inputs.
410 GoneResource expired (e.g. signed deep link past exp). Generate a fresh one.
422 Unprocessable EntityRequest was syntactically valid but semantically rejected (e.g. enrolling a member in an inactive plan).
429 Too Many RequestsRate-limit tripped. Respect Retry-After header.
500 Internal Server ErrorTopiic bug. Capture the traceId from the response if present and contact support. Safe to retry idempotent requests.
502 Bad GatewayTopiic couldn’t reach the upstream payment gateway. Safe to retry with backoff.
503 Service UnavailableTopiic is degraded. Respect Retry-After; check the status page.

Endpoints that move money or change billing require an Idempotency-Key header. Without it they return 400 before the request is processed:

Endpoint
POST /api/SubscriptionsCreate subscription
POST /api/Subscriptions/{id}/pause · /resume · /cancelSubscription lifecycle
POST /api/members/{memberId}/payment-methods · …/from-tokenAdd payment method
POST /api/Transactions/{id}/retry · /refundRetry or refund a charge

Send a unique value (a UUID is ideal, max 255 characters) per logical operation, and reuse it when retrying that same operation. Topiic reserves the key before running the request:

  • A retry with the same key and body replays the original response - status, body, and Location on a 201. Nothing runs twice.
  • A retry that arrives while the original is still running is rejected rather than reaching the gateway twice.
  • Reusing a key with a different body returns 409.

Keys are scoped to your merchant.

What a retry does depends on how the first attempt ended:

First attemptRetry with the same key
2xxReplays that response. Safe and free.
4xxRuns again. The request was rejected before anything happened, so fix the payload and retry on the same key.
5xxDoes not run again. Replays application/problem+json carrying the original status and a detail saying the outcome is unknown.

A 5xx means the operation was already under way and may have moved money before it failed. Topiic cannot tell whether it did, so retrying that key can never charge the customer a second time. Reconcile first - look for the transaction or subscription the call would have created - and only then make a genuinely fresh attempt, with a new key.

If your HTTP client retries 5xx automatically, expect that problem document rather than a second attempt.

POST /api/Subscriptions also refuses a second live (non-cancelled) subscription for the same member and plan with 409 and an existingSubscriptionId. Hosted checkout creates the subscription itself, so on checkout.completed store data.subscriptionId - don’t create another.

Other writes don’t take the header:

  • Deep-link resolve is naturally idempotent via the nonce - replaying the same d/s always returns the same outcome.
  • Webhook delivery replay creates a new attempt every time - that’s intentional.
  • Member creation is unique-per-merchant by email - a duplicate returns the existing record on a second call. Direct attempts to insert a duplicate via direct API give a 409 in some edge cases.
  • Other writes - apply your own deduplication on the client side.

400 responses for typed commands include a per-field error map:

{
"status": 400,
"title": "One or more validation errors occurred.",
"errors": {
"email": ["The Email field is not a valid e-mail address."],
"firstName": ["The FirstName field is required."]
}
}

Map these into your form-validation UI rather than displaying the raw text.

A 401 from a REST endpoint usually means one of:

CauseFix
No Authorization header.Add Authorization: Bearer tpk_….
Wrong scheme (Basic, lowercase bearer).Use exact Bearer (capital B).
API key revoked.Mint a new one.
API key past expiresAt.Mint a new one or extend in the portal.

If Topiic’s webhook delivery to your endpoint returns a non-2xx, it’s not an error from your perspective - it’s part of the retry loop. See Retries & replay.