Employee seat billing
The per-employee billing add-on — a dedicated monthly subscription of the account that pays, whose quantity is the contracted seats; creating or reactivating an employee takes a free seat at no charge, and only buying seats charges.
Employees are billed through contracted seats, not the plan's users limit —
an employee never counts against that limit. The add-on is a dedicated
monthly subscription (employee-seats), fully separate from your plan
subscription: its quantity is the number of contracted seats of the account,
and they are billing only: the control_horario module comes from the plan
(Professional, Advanced and Enterprise; a superadmin can also grant it by hand),
and without it seats cannot be bought (403 employee_seats_not_in_plan). The subscription
belongs to the account that pays, not to one of its tax IDs, so the endpoints
live under https://api.factuarea.com/v1/accounts/{account}/employee-seats —
{account} is the account.id you read from GET /v1/me — and use
employees:read (status, preview) or employees:write (buy or reduce seats,
subscribe, change quantity, cancel). Who pays and who can operate
explains which account that is.
Contracted, occupied and free seats
The account chooses how many seats it pays for, and that number is what the subscription bills:
- Contracted seats are the seats the account has bought and is billed for
(
quantityof theemployee-seatssubscription). - Occupied seats are the
activeemployees of all the tax IDs of the account. - Free seats are the contracted ones minus the occupied ones.
In an account that is charged there are always at least as many contracted seats as occupied ones. Deactivating an employee frees a seat but does not change the contracted seats or the invoice: to pay less, reduce the seats.
How seats are billed
Creating, activating or reactivating an employee takes a free seat: no charge, no payment method and no call to the payment provider. Nothing is ever charged when an employee is created; a charge happens only when you buy seats.
- Creating or reactivating an employee occupies a free seat. If there is none, see what happens without a free seat.
- Deactivating an employee frees the seat and leaves the contracted seats and the invoice as they are. The seat stays available for the next hire.
- Each renewal bills the contracted seats, whether or not they are occupied.
An account that already had the add-on starts with the seats it was paying for, so its invoice does not change.
When seats are charged
Seats are charged only when you buy them, never when an employee is created or reactivated. Buying applies whenever your plan bills:
- a paid plan subscription, also when it is
past_due(a pending payment does not suspend seat billing); - an Esencial, Profesional or Avanzado plan granted by Factuarea without an open subscription.
Only these accounts are not charged and their employees neither consume nor buy seats:
| Exempt case | Why |
|---|---|
| Enterprise and managed companies | Billed by contract, outside the self-service checkout. |
| An active trial | Nothing is charged; see Employees created during the trial. |
| Sandbox (test keys) and accounts with live mode off | No real charge is made. |
| A managed account that its provider pays | The seats are on the provider's invoice; see who pays. |
GET /v1/accounts/{account}/employee-seats reports this in seats_billable: true means your plan
must pay per seat, false is a legitimate no-charge state.
For an enterprise account billed by contract (no Stripe subscription), the add-on is granted for free: no charge, no payment method required.
The first purchase of seats
You do not need to subscribe before adding employees. In an account that is
charged and has no employee-seats subscription, the first purchase of seats
creates it with the new quantity and charges the first period.
Employees who were already active when the subscription was created are not charged on top: in an existing account they are covered by the seats it was already paying for, and an account with active employees and no seats contracted starts with none until it buys them.
Buy seats
PATCH /v1/accounts/{account}/employee-seats with {"contracted_seats": N} sets
the contracted seats to an absolute value. When N is greater than the
current ones, the missing seats are bought now:
- The charge is immediate and prorated for the rest of the period, with the card of the account.
Idempotency-Keyis mandatory: a retry with the same key does not charge twice.- The response is the status with its
seatsblock. - Without a payment method the call answers
402 employee_seat_payment_method_requiredand with a declined charge402 employee_seat_charge_failed; in both cases nothing is bought.
curl -X PATCH https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"contracted_seats": 8}'From MCP, the update_employee_seats tool takes the same contracted_seats and an
idempotency_key. Preview the charge first with contracted_seats.
Any purchase cancels a pending reduction.
Reduce seats
The same PATCH with a smaller number schedules a reduction. Any amount is
allowed, from 0: there is no floor.
- No call to the payment provider and no refund. The reduction is a pending reduction that takes effect at the renewal, and the seats stay available until then.
- Below the occupied seats, send
deactivate: a list of UUIDs of exactlyoccupied − amountactive employees of the account. If it does not match, the answer is422 contracted_seats_selection_requiredwithparam=deactivateanddetailsproduct,requested,occupied,required,selectedandinvalid. - Offer package. If, at the renewal, the reduction leaves the product below
the minimum of a line of an applied offer package,
the whole package would end. The preview publishes
offer_package_lossand the write needsaccept_offer_package_loss: true; without it the answer is422 offer_package_loss_unconfirmedwithparam=accept_offer_package_lossanddetailsproduct,package_id,name,effective_atandlines(each withconcept,from_price,to_priceandinterval). The agreed minimum never limits the reduction; it only decides whether the package ends. - Pending reduction. The status shows
pending_reductionwithcontracted,effective_atanddeactivations(idandname). A few days before the renewal the owner of the account gets a notice with those names, and at the renewal a task deactivates the chosen employees and sets the quantity. - Cancelling or replacing it. A purchase, a
PATCHto the current quantity, or a creation, activation, reactivation, batch or tax ID transfer that occupies what the reduction was going to remove, cancels it; theaccount.seats_updatedevent then carriescause=reduction_superseded. Another, smallerPATCHreplaces it. - Reducing to 0 needs
deactivatewith all the occupied seats (and the loss of the package, if any) and cancels the add-on at the end of the period.
curl -X PATCH https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"contracted_seats": 3, "deactivate": ["0198f2d4-1b7e-7c3a-9d41-6a5e8b2c4f10"]}'Creating an employee without a free seat
When the account is charged and has no free seat, the surface decides:
- The web application answers
422 contracted_seats_exhaustedwithdetailsproduct,contracted,occupied,free,requiredandmissing, without creating or charging anything. The app then leads to buying seats first, and then creating. - The v1 API and MCP buy the missing seats with a prorated charge and then
create the employee, with the same
402errors as always when there is no card or the charge fails (see payment errors). The purchased seat stays contracted. A retry does not charge twice.
Send an Idempotency-Key in the creations: without it, a retry after a creation
that did go through creates another employee, because the API cannot tell a retry
from a new one.
Employees created during the trial
During an active trial nothing is charged and the employees you create do not consume seats. You can choose the seats you will contract without a charge: they are charged when the company starts paying.
When billing starts, the active employees occupy the contracted seats that exist.
The oldest ones stay active and the ones that do not fit become inactive
with no charge; with 0 contracted seats, all of them. Reactivate the ones you
want to keep once you have bought seats.
Adding a card during the trial does not deactivate anyone; only the start of billing does.
Payment errors
A purchase of seats is atomic: when it fails, nothing is bought, created or charged.
| Response | Meaning |
|---|---|
402 employee_seat_payment_method_required | No payment method on file. error.details.payment_setup_url links to where the card can be added; then retry. |
402 employee_seat_charge_failed | The payment provider declined the charge. |
403 employee_seats_not_in_plan | The plan does not include time tracking (Professional or higher), so seats cannot be bought. Nothing is charged; lowering or keeping the seats stays allowed. |
503 employee_batch_quote_unavailable | Only when employee batches are enabled for the account: a reliable seat quote is not available. Retry the same request. |
Who pays and who can operate
The subscription is held by the account that pays, which is usually the account itself. It is charged to the payment method of the account, and only an owner or admin operates it:
- To buy or reduce seats, subscribe, cancel or change the quantity, the person who issued the key has to be an owner or admin of the account today.
- To read or write, the scope of the key has to include the titular company of the account.
Otherwise the call answers 403 forbidden_action and
nothing changes. A fact_test_ key reads the status and the preview, but
the update, subscribe, cancel and change-quantity answer
403 live_credential_required with nothing
changed: see test mode.
A managed account that its provider pays. For a live managed account that its
provider pays, the seats of its employees are not bought on the account. They go
on the provider's managed-accounts subscription,
at the price of the seat, and their quantity follows the active employees of the
account whatever the access level of the provider. The client cannot operate the
add-on, because what it would change is on the provider's invoice:
- The update,
subscribe,cancelandchange-quantityanswer403 billing_managed_by_providerto its keys, and to its people in the app and in MCP, with no charge and no change. statusandpreviewanswer the same403, because the subscription they would read is the provider's.- The provider does not buy those seats either:
{account}of a managed account answers404 account_not_foundon these routes.
When the client takes over the payment, the account pays for itself and its owner and admins operate the add-on like those of any other account. Managed account pricing and payer change lists what else the client cannot buy while the provider pays.
Check the billing status
GET /v1/accounts/{account}/employee-seats returns the add-on status: whether
the subscription is active ( subscribed ), how many seats are billed (
quantity, the contracted seats), how many employees are active, and the
recurring per-seat cost including VAT. Amounts are euros as a two-decimal
string (for example "1.82") and are null — never a misleading 0 — when the
cost is not resolvable (not subscribed, no active plan, enterprise outside Stripe,
sandbox).
The seats block adds the detail of the contracted seats:
| Field | Meaning |
|---|---|
contracted, occupied, free | Seats the account pays for, those taken by active employees, and the difference. |
agreed_minimum, agreed_minimum_ends_at | The minimum and its end date of the line of the applied offer package; 0 and null without a package. It is not a lock on reducing. |
reducible_to | The lowest amount a reduction can reach. Always 0. |
pending_reduction | null, or the pending reduction with contracted, effective_at, deactivations and offer_package_loss. |
enforced | true when new employees consume seats; false in trial, sandbox and exempt accounts. |
seats is null for a managed account that its provider pays.
curl https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats \
-H "Authorization: Bearer $FACTUAREA_API_KEY"Preview the charge
GET /v1/accounts/{account}/employee-seats/preview returns the prorated
amount for the seats an operation would buy, computed from the Stripe upcoming
invoice, without charging. Apart from the rejections of who pays and the
validation of its parameters, it never fails — it degrades to a neutral preview.
| Parameter | Notes |
|---|---|
count | Batch preview for N employees (≥1, up to 1000). |
employee_ids | Preview by UUID v7 of the employees to activate. |
contracted_seats | Preview of setting the contracted seats to that value. Exclusive with count and employee_ids. |
amount is the taxable base in euros as a two-decimal string, and tax_amount
and total use the same format; recurring_base_cents and
recurring_total_cents stay in cents, as their names say.
requires_payment_method is true when no payment method is on file.
The preview also says how the contracted seats would cover the operation:
free_seats_used,seats_to_buyandincluded_in_seats(truewhen the free seats cover everything, soamountis0and no card is needed).seats: theseatsblock after the operation.- With
contracted_seats,amountandtotalare today's charge andrecurring_quantitythe quantity of the next invoice. - For a reduction, the amount is
0and the preview addsdeactivations_required(how many employees you must choose),cancels_pending_reductionandoffer_package_loss. already_coveredis alwaysfalse: coverage by period no longer decides any charge.
curl -G https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats/preview \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
--data-urlencode "contracted_seats=8"Subscribe to the add-on
POST /v1/accounts/{account}/employee-seats/subscribe makes sure the monthly
employee-seats subscription exists with the contracted seats of the account and
charges the first period with the payment method on file. It is not required:
the first purchase of seats creates the subscription on its own. It
takes no quantity: to choose how many seats you want, use PATCH. The
charge is atomic — if it does not go through, nothing is subscribed:
- No payment method →
402 employee_seat_payment_method_required; the error envelope carrieserror.details.payment_setup_urlto complete card setup. - A declined charge →
402 employee_seat_charge_failed.
curl -X POST https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats/subscribe \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"If a request returns 402 employee_seat_payment_method_required, send the user
to the payment_setup_url from the error, let them add a card, then retry.
Nothing is charged, created or subscribed until the charge succeeds.
Sync the quantity and cancel
POST /v1/accounts/{account}/employee-seats/change-quantity brings the quantity of
the subscription back to the contracted seats of the account (a SET with no
proration and no invoice). It no longer follows the active employees. It is
idempotent — a no-op when the quantity already matches.
POST /v1/accounts/{account}/employee-seats/cancel is the same as reducing to
0: it cancels the add-on at period end and accepts the same deactivate and
accept_offer_package_loss as the reduction.
- Without
deactivate, every active employee is deactivated at the renewal (no422), and the owner gets the notice a few days before. - A
deactivatelist that does not match all of them answers422 contracted_seats_selection_required. - If cancelling breaks an offer package that was not confirmed, the answer is
422 offer_package_loss_unconfirmed.
The current month is already paid, so subscribed stays true until the period
ends. The plan subscription is never touched.
curl -X POST https://api.factuarea.com/v1/accounts/$ACCOUNT_ID/employee-seats/cancel \
-H "Authorization: Bearer $FACTUAREA_API_KEY" \
-H "Idempotency-Key: $(uuidgen)"Every change of the contracted seats or of the pending reduction is announced with
the account.seats_updated event.
See the schemas in the
API Reference, and
the operation to set the seats in
public-api.v1.employee-seats.update.
Typical flow
- Preview the cost of the seats you want with
contracted_seats. - Make sure a payment method is on file, so the purchase is not rejected with
402. - Buy the seats with
PATCHand anIdempotency-Key. - Create or reactivate employees: each one takes a free seat, with no charge.
- Read status to show contracted, occupied and free seats.
- Reduce the seats when you need fewer, choosing who leaves if there are more employees than seats; it takes effect at the renewal.
Next steps
the portal-only employee role and the whole system.
the tax IDs of your account: the additional tax ID charge, their state and their statistics.
what a provider pays for the employees of its managed accounts.