Factuarea APIDevelopers

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 (quantity of the employee-seats subscription).
  • Occupied seats are the active employees 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 caseWhy
Enterprise and managed companiesBilled by contract, outside the self-service checkout.
An active trialNothing is charged; see Employees created during the trial.
Sandbox (test keys) and accounts with live mode offNo real charge is made.
A managed account that its provider paysThe 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-Key is mandatory: a retry with the same key does not charge twice.
  • The response is the status with its seats block.
  • Without a payment method the call answers 402 employee_seat_payment_method_required and with a declined charge 402 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 exactly occupied − amount active employees of the account. If it does not match, the answer is 422 contracted_seats_selection_required with param=deactivate and details product, requested, occupied, required, selected and invalid.
  • 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_loss and the write needs accept_offer_package_loss: true; without it the answer is 422 offer_package_loss_unconfirmed with param=accept_offer_package_loss and details product, package_id, name, effective_at and lines (each with concept, from_price, to_price and interval). The agreed minimum never limits the reduction; it only decides whether the package ends.
  • Pending reduction. The status shows pending_reduction with contracted, effective_at and deactivations (id and name). 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 PATCH to the current quantity, or a creation, activation, reactivation, batch or tax ID transfer that occupies what the reduction was going to remove, cancels it; the account.seats_updated event then carries cause=reduction_superseded. Another, smaller PATCH replaces it.
  • Reducing to 0 needs deactivate with 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_exhausted with details product, contracted, occupied, free, required and missing, 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 402 errors 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.

ResponseMeaning
402 employee_seat_payment_method_requiredNo payment method on file. error.details.payment_setup_url links to where the card can be added; then retry.
402 employee_seat_charge_failedThe payment provider declined the charge.
403 employee_seats_not_in_planThe 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_unavailableOnly 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, cancel and change-quantity answer 403 billing_managed_by_provider to its keys, and to its people in the app and in MCP, with no charge and no change.
  • status and preview answer the same 403, because the subscription they would read is the provider's.
  • The provider does not buy those seats either: {account} of a managed account answers 404 account_not_found on 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:

FieldMeaning
contracted, occupied, freeSeats the account pays for, those taken by active employees, and the difference.
agreed_minimum, agreed_minimum_ends_atThe 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_toThe lowest amount a reduction can reach. Always 0.
pending_reductionnull, or the pending reduction with contracted, effective_at, deactivations and offer_package_loss.
enforcedtrue 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.

ParameterNotes
countBatch preview for N employees (≥1, up to 1000).
employee_idsPreview by UUID v7 of the employees to activate.
contracted_seatsPreview 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_buy and included_in_seats (true when the free seats cover everything, so amount is 0 and no card is needed).
  • seats: the seats block after the operation.
  • With contracted_seats, amount and total are today's charge and recurring_quantity the quantity of the next invoice.
  • For a reduction, the amount is 0 and the preview adds deactivations_required (how many employees you must choose), cancels_pending_reduction and offer_package_loss.
  • already_covered is always false: 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 carries error.details.payment_setup_url to 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 (no 422), and the owner gets the notice a few days before.
  • A deactivate list that does not match all of them answers 422 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

  1. Preview the cost of the seats you want with contracted_seats.
  2. Make sure a payment method is on file, so the purchase is not rejected with 402.
  3. Buy the seats with PATCH and an Idempotency-Key.
  4. Create or reactivate employees: each one takes a free seat, with no charge.
  5. Read status to show contracted, occupied and free seats.
  6. 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

TicketPercent UsersRound

On this page

Need a hand?Contact support