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.
Problem-details body shape
Section titled “Problem-details body shape”{ "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.
Status code reference
Section titled “Status code reference”| Code | Meaning in Topiic context |
|---|---|
| 200 OK | Request succeeded; body holds the resource or list. |
| 201 Created | Resource created; body holds the new resource. |
| 202 Accepted | Request accepted for processing (e.g. webhook delivery replay queued). |
| 204 No Content | Request succeeded; no body (delete, simple update). |
| 400 Bad Request | Validation failed. Body usually includes an errors map keyed by field name. Fix the request. |
| 401 Unauthorized | Missing, malformed, expired, or invalid bearer credential. Mint or re-mint your API key. |
| 403 Forbidden | Authenticated but not allowed - typically the merchant id on the request doesn’t match your API key’s merchant. |
| 404 Not Found | Resource id doesn’t exist, or belongs to a merchant you can’t see. |
| 409 Conflict | Duplicate (e.g. a nonce already used, a unique-per-merchant email collision). Don’t retry blindly - change inputs. |
| 410 Gone | Resource expired (e.g. signed deep link past exp). Generate a fresh one. |
| 422 Unprocessable Entity | Request was syntactically valid but semantically rejected (e.g. enrolling a member in an inactive plan). |
| 429 Too Many Requests | Rate-limit tripped. Respect Retry-After header. |
| 500 Internal Server Error | Topiic bug. Capture the traceId from the response if present and contact support. Safe to retry idempotent requests. |
| 502 Bad Gateway | Topiic couldn’t reach the upstream payment gateway. Safe to retry with backoff. |
| 503 Service Unavailable | Topiic is degraded. Respect Retry-After; check the status page. |
Idempotency on writes
Section titled “Idempotency on writes”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/Subscriptions | Create subscription |
POST /api/Subscriptions/{id}/pause · /resume · /cancel | Subscription lifecycle |
POST /api/members/{memberId}/payment-methods · …/from-token | Add payment method |
POST /api/Transactions/{id}/retry · /refund | Retry 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
Locationon a201. 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.
Retrying after a failure
Section titled “Retrying after a failure”What a retry does depends on how the first attempt ended:
| First attempt | Retry with the same key |
|---|---|
| 2xx | Replays that response. Safe and free. |
| 4xx | Runs again. The request was rejected before anything happened, so fix the payload and retry on the same key. |
| 5xx | Does 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 samed/salways 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.
Validation errors
Section titled “Validation errors”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.
Authentication errors specifically
Section titled “Authentication errors specifically”A 401 from a REST endpoint usually means one of:
| Cause | Fix |
|---|---|
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. |
Webhook-specific failures
Section titled “Webhook-specific failures”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.