Integrations
Integrate Abuse Checks
There is nothing to switch on. The protections on this page are part of the API: rate limits on the routes your application calls, brute-force protection on the sign-in realm Paycux provisions for each environment, an invitation budget per workspace and idempotency keys on every write that matters. This page shows how to build against them and how to prove they are there.
Available in part — Abuse Checks: The API damps abuse today with per-identity and per-source rate limits that answer 429 with a Retry-After header; risk signals, customer policies and challenge or block decisions are planned. Items listed as planned below cannot be enabled yet. What Paycux provides today.
Before you begin
- A project with an environment whose customer identity setup is ready. End-user calls carry a realm access token issued by that environment's realm.
- A way to mint an Idempotency-Key per logical action in your application. A UUID or a stable key such as <order id>-<step> both work; keys are 8 to 128 characters.
- A retry policy that honours Retry-After. Retrying faster than the header says only lengthens the wait.
- If you operate the API yourself, the proxy topology in PAYCUX_TRUSTED_PROXIES. Without it only identities are counted.
Install
There is nothing to install. Paycux is a plain HTTPS JSON API, so every example on this page uses curl. Console routes send the dashboard session cookie, end-user routes send the application's realm access token as a bearer credential, and agent routes send the short-lived agent token. The routes that exist today are listed on What exists today.
1# Console routes carry the dashboard session cookie set by sign-in2curl https://api.paycux.com/v1/auth/session \3 -H "Cookie: __Host-paycux_session=<session>"45# End-user routes carry the application's realm access token6curl https://api.paycux.com/v1/<end-user route> \7 -H "Authorization: Bearer <realm access token>"
Configure
Your application needs the API address and the scope it calls with. The realm id is shown on the environment's Authentication page once setup is ready; the billing account id comes from the end-user billing routes.
1PAYCUX_API_BASE_URL=https://api.paycux.com2PAYCUX_PROJECT_ID=<project id>3PAYCUX_ENVIRONMENT_ID=<environment id>4PAYCUX_REALM_ID=<realm id>
Then, in the dashboard:
- 1Open the environment's Authentication page and complete identity setup. The realm Paycux provisions has brute-force protection switched on from the first sign-in.
- 2Open Settings and invite your colleagues from Members. The invitation budget applies here; a 429 means wait, not that the invitation failed for good.
- 3Open the environment's Usage page after your first events. A refused request never appears there, because a 429 writes nothing.
- 4Open Billing to see the plan limits on projects, connections, members and usage events. Those answer 402, not 429, and are separate from damping.
Implement
Three habits cover everything: send an Idempotency-Key on every usage event and checkout, treat 429 as a pause rather than an error, and branch on the usage decision rather than on the HTTP status.
Send a usage event with an idempotency key
The key goes in the Idempotency-Key header; if you also put it in the body the two must match. The scope in the body must match the realm access token, and the identity must be an active member of the billing account. The answer is a decision, not just an acknowledgement.
1curl -X POST https://api.paycux.com/v1/usage/events \2 -H "Authorization: Bearer <realm access token>" \3 -H "Idempotency-Key: report-export-<order id>-0001" \4 -H "Content-Type: application/json" \5 -d '{6 "projectId": "<project id>",7 "environmentId": "<environment id>",8 "realmId": "<realm id>",9 "billingAccountId": "<billing account id>",10 "meterKey": "report_exports",11 "quantity": 1,12 "occurredAt": "2026-10-13T09:00:00Z"13 }'1415# 200 OK16{17 "allowed": true,18 "reason": "WITHIN_LIMIT",19 "consumed": 12,20 "limit": 100,21 "eventId": "<event id>",22 "duplicate": false23}
Retry safely
If the response is lost, send the identical request again with the same key. Paycux answers from the first result: the same eventId, duplicate set to true and nothing consumed a second time. A request without the header answers 400 INVALID_INPUT and writes nothing.
1# Identical request, same Idempotency-Key23# 200 OK4{5 "allowed": true,6 "reason": "WITHIN_LIMIT",7 "consumed": 12,8 "limit": 100,9 "eventId": "<event id>",10 "duplicate": true11}
Handle 429
A refusal carries the wait in seconds. Sleep for that long, then retry the same request with the same key; because nothing was written, there is no partial state to reconcile. Never treat 429 as a quota decision: quotas arrive as allowed: false with a reason inside a 200 response.
1HTTP/1.1 429 Too Many Requests2retry-after: 33content-type: application/json45{ "error": "RATE_LIMITED", "message": "Too many requests; retry after 3s" }
Invite within the budget
Invitations are console-session calls by an owner or admin. The budget is checked per workspace before anything is written, then the plan's member limit. The token and accept link are returned exactly once; when e-mail is configured for the deployment the same link is queued as a message.
1curl -X POST https://api.paycux.com/v1/workspace/members/invites \2 --cookie "__Host-paycux_session=<session cookie>" \3 -H "Content-Type: application/json" \4 -d '{ "email": "[email protected]", "role": "developer" }'56# 201 Created7{8 "invite": {9 "id": "<invite id>",10 "email": "[email protected]",11 "role": "developer",12 "status": "pending",13 "invitedByMemberId": "<member id>",14 "expiresAt": "2026-10-20T09:00:00.000Z",15 "createdAt": "2026-10-13T09:00:00.000Z"16 },17 "token": "<invite token>",18 "acceptUrl": "https://paycux.com/auth/start?mode=login&invite=<invite token>",19 "emailQueued": true20}2122# Budget spent: 429 RATE_LIMITED with Retry-After23# Plan limit reached: 402 PLAN_LIMIT_EXCEEDED with featureKey, limit and current
Create a checkout session once
Hosted checkout is damped like usage and requires an Idempotency-Key. An identical retry keeps the same session; a changed payload under the same key answers 409. Return URLs must match the ones registered for the environment.
1curl -X POST https://api.paycux.com/v1/billing/checkout-sessions \2 -H "Authorization: Bearer <realm access token>" \3 -H "Idempotency-Key: checkout-<billing account id>-pro-0001" \4 -H "Content-Type: application/json" \5 -d '{6 "projectId": "<project id>",7 "environmentId": "<environment id>",8 "realmId": "<realm id>",9 "billingAccountId": "<billing account id>",10 "planKey": "pro",11 "successUrl": "https://app.example.com/billing/success",12 "cancelUrl": "https://app.example.com/billing/cancel"13 }'1415# 201 Created16{17 "provider": "paddle",18 "mode": "sandbox",19 "checkoutUrl": "https://paycux.com/checkout/customer/<session token>",20 "expiresAt": "2026-10-13T09:30:00.000Z",21 "billingAccountId": "<billing account id>",22 "planKey": "pro"23}
Verify
The interesting test is the negative one. Confirm that a normal client is never refused before you confirm that a runaway one is.
- 1Send a usage event, then send it again with the same Idempotency-Key. The second answer carries duplicate: true and the same eventId, and the Usage page shows one event.
- 2Send a usage event without an Idempotency-Key and confirm 400 INVALID_INPUT with nothing recorded.
- 3Run a tight loop of usage events for one identity and confirm the first refusal is 429 RATE_LIMITED with a Retry-After header. Wait that long, retry with the same key and confirm it is processed.
- 4Invite addresses in a loop from one workspace and confirm the loop ends in 429 rather than a queue of e-mails. Revoke the test invitations afterwards.
- 5Fail a sign-in on the environment's hosted sign-in page repeatedly and confirm the realm starts refusing attempts temporarily. A correct password after the wait signs in.
Reference
Request and response shapes for each of these live in the API reference. Paths are relative to https://api.paycux.com.
| Method | Path | What it does |
|---|---|---|
| POST | /v1/usage/events | Record a usage event and receive a decision (realm access token + Idempotency-Key). Damped per identity, and per source when declared. |
| POST | /v1/billing/checkout-sessions | Create a hosted checkout session (realm access token + Idempotency-Key). Same damping. |
| POST | /v1/billing/portal-sessions | Open the provider's billing portal (realm access token). Same damping. |
| POST | /v1/billing/subscriptions/:subscriptionId/cancel | Cancel a subscription, at period end by default (realm access token). Same damping. |
| POST | /v1/workspace/members/invites | Invite a member (console session; owner or admin). Per-workspace invitation budget, then the plan's member limit. |
| GET | /v1/workspace/members/invites | List invitations with their status (console session; any role). |
| DELETE | /v1/workspace/members/invites/:inviteId | Revoke a pending invitation (console session; owner or admin). |
| PUT | /v1/environments/:environmentId/auth | Configure customer identity for an environment (console session; owner or admin). The realm is provisioned with brute-force protection on. |