Paid keeps three things separate:
That separation is the important part of the model:
If you understand those rules, the API is straightforward.
A user is an identity within a customer account. A seat is a billable slot created from an order’s seat quantity. Users can occupy seats, but they do not create them.
This means:
In practice, most integrations use the API in this order:
The customer is the account that owns the order and is billed for it.
A user is an identity within that customer. Use your own stable external IDs. You do not need to store Paid UUIDs.
A seat is created from an order’s seat-based quantity. Seats are listed and managed separately from the order.
Operationally:
Create the customer that will own the users, seats, and order. See the Customers API for the full reference.
If the customer already exists, use its existing external ID in the endpoints below.
Seats come from order quantity, not from user creation.
For seat-based products, the order is the source of truth for billable seat count. The order creates the pool of seats that can later be assigned to users.
Choose one of these entry points:
When the order is created, Paid creates the seats for that order.
Once the customer exists, create or update users explicitly. This keeps identity lifecycle separate from billing quantity.
Create or update a user with customer external ID and user external ID.
Example response:
This endpoint is idempotent. Re-sending the same user updates the existing record.
If you send "status": "deactivated" for an existing user, that user is deactivated and any current seat assignments are removed automatically.
List seats on the order to find assignable seat IDs.
You can also filter by product and status:
Example seat:
Use the seat id returned here when assigning or unassigning a seat.
After you have both users and seats, connect them by assigning users to seat IDs.
Assign a seat to a user:
Unassign a seat:
Assigning or unassigning a seat changes occupancy only. It does not change the billed quantity on the order.
Use batch assignment for admin tooling, bulk sync jobs, or provisioning workflows.
You can also unassign in batch by setting "userExternalId": null on any entry.
Seat reductions can be scheduled to take effect at the next billing anchor rather than immediately. This avoids mid-cycle proration and gives users time to wind down before they lose access.
When a reduction is scheduled, two things are written:
Until the anchor passes, GET /orders/{id}/seats returns the existing seats (full quantity, current assignments). After the anchor, it returns the new (lower) quantity. Assignments carry forward automatically. Users on the existing line are migrated onto the new line’s seats at the first read past the anchor, so the same users remain assigned without any action from the integrator.
The carry-forward is first-in, first-out by assignment time: users whose updatedAt on the seat is oldest are kept; users whose seat was most-recently assigned are dropped if the new quantity is too small. This protects long-tenured assignments.
To prevent dropped users at the anchor, the schedule endpoint rejects requests where the number of assigned users on the line is already greater than the new quantity. Callers must unassign users themselves before scheduling the reduction. This check is enforced only at schedule time, however: assignments made on the existing line after the reduction is scheduled but before the anchor passes can still push the count above the new quantity. If that happens, the most-recently-assigned users lose their seat at the anchor.
Recommendation: surface the pending reduction in your own UI and discourage assigning new users to seats on an order with a scheduled reduction.
The scheduled-change endpoints are not yet listed in the API reference. Reach out to your account team to enable them on your organisation.
For most integrations, the lifecycle looks like this: