Monthly payroll cycles
A payroll cycle covers one tenant and one monthly period. The V1 journey supports one company
officer per tenant and the current month or M-1 for writes. Older revisions remain readable.
Approval and finalization do not initiate payment or file a DSN.
The new calculate, operation, abandon and document routes are currently closed by
MANAGED_PAYROLL_V1_ENABLED and return 404 payroll_journey_unavailable until Verso
explicitly activates them. Existing cycle, variables, preview and approval routes remain served.
Use the Gateway paths below, prefixed with
/partners/{partnerId}/tenants/{tenantId}/payroll-cycles. The period uses YYYY-MM.
The API key must belong to the partner and tenant; a different partner or tenant cannot read or
change the cycle. Keep each response's ETag for the next write's If-Match header.
Enter and calculate
PUT /{period}creates the cycle or returns its current revision.PUT /{period}/variableswithIf-Matchsendsremunerationin euros (including zero) anddateDeVersementas an explicitly confirmedYYYY-MM-DDpayment date. OpsService records the write intent before sending it to PayrollEngine; the engine decides whether the date and payroll input are admissible.POST /{period}/calculatewithIf-Matchand a stableIdempotency-Keyreturns202 Acceptedand an operation URL. This starts one persisted real payroll calculation, not a forecast. Repeating the same key for the same request returns the same operation. PollGET /{period}/operations/{operationId}untilcompleted,failedorreconcile_required;202does not mean the calculation succeeded.GET /{period}is the source of truth. A completed calculation exposes acandidatewithid, job and result-set IDs, and aresultfor that exact candidate. The result containsoutcome,warnings,blockersandpersons(one person in V1). Each person has gross, employee and employer contributions, net deductions, net before tax, net social, PAS, net payable, employer cost and the native wage-type details. These amounts are copied from the engine's result set; missing or ambiguous native values arenull. In FR.Salaire 2026, the classic bulletin builds net before tax from native net payable (WT 7025) plus native PAS (WT 5100, mirrored by WT 7005). Verso requires the two PAS values to agree before exposing this amount. Net social is the separate WT 6000. Theresultis absent until an exact candidate is ready.
The cycle states are collecting, preview_ready, approved, finalized, filed, accepted
and correction_required. The operation has its own status and phase, separate from the
cycle state. A failed or reconcile_required operation exposes a blocker code. When the
effect of a lost engine response cannot be identified uniquely, OpsService requires operator
reconciliation and never sends the native command again blindly. A competing calculation or
decision returns 409 payroll_operation_collision with the active operation ID.
Approve the exact result
After reviewing result and the candidate.id, send
POST /{period}/approval with the latest If-Match and body
{"approvalKey":"stable-key","candidateId":"the-reviewed-id"}. The response is 202
with an operation URL. Approval is bound to that candidate, input generation, exact engine job
and result set. The worker finalizes that same job without recalculating. Poll the operation and
then read the cycle; finalized is distinct from approved and from filing.
If the variables or relevant tenant inputs change, the old candidate becomes invalid and a new
calculation and review are required. An old ETag yields 412; an old candidate yields a
conflict. Before approval, including after an input has invalidated a draft,
POST /{period}/abandon with If-Match and body
{"candidateId":"...","idempotencyKey":"stable-key"} explicitly abandons the exact draft.
After finalization, a change creates a new revision for correction; the prior result and
documents are not overwritten. The first input on an empty M-1 cycle remains revision 1;
changing an existing input after cutoff or approval creates the next revision.
When a prior month is corrected while the following month is still open, each input remains
scoped to its own month. Resending the following month's unchanged variables is an idempotent
read of its existing write. A calculation can be requested immediately after a confirmed write;
Ops chooses an engine evaluation instant that includes that write.
For a legacy simulation or draft that has no Ops candidate, use
POST /{period}/drafts/{jobId}/abort with the latest If-Match and a stable UUID
requestKey. Supply reasonCode as simulation_discarded, draft_replaced or
operator_recovery. This queues an audited operation for that exact native Draft; poll
the returned operation URL until completed before requesting a fresh calculation. A repeated
key returns the same operation. An approved, foreign, non-Draft or candidate-owned job is
refused. An ambiguous response keeps the payroll reserved until Ops verifies native history.
This route is off by default (Ops__DraftAbortEnabled=false) until Verso completes its
database, writer and clock checks. Older drafts outside M/M−1 require the authenticated Ops
recovery path; the partner window is unchanged.
POST /{period}/preview remains a compatibility readiness check for legacy cycles. A managed
candidate uses calculate plus GET; preview cannot select a different native result. Legacy
approval without candidateId remains confined to cycles with no managed candidate. New
integrations should always send candidateId.
For an existing legacy cycle, read its ETag, call POST /{period}/preview with If-Match, and
check state=preview_ready, preview.pdfProof=ready, empty preview.blockers, and the exact
payrunJobId and resultSetId. Then call GET /{period}/payslip to obtain a temporary private
PDF URL. Its response includes the beneficiary, period, revision, job, result set and checksum.
Download the URL and compare the PDF with that identity. documentStatus=preview means the
partner has not approved it; approved_preview means the same legacy preview was approved,
but it is not a managed final document. If the PDF is not ready, the API returns 409 payslip_not_ready with cycleState, the last preview including its blockers, and a
nextAction (set_cycle_variables, prepare_preview, wait_for_payrun,
resolve_preview_blockers or contact_verso when rendering is disabled or failed). Do not
attempt to open a missing URL. A genuine unknown cycle still returns 404.
Documents
Generate or retry a document with
POST /{period}/revisions/{revision}/candidates/{candidateId}/documents/{variant} and read it
with GET on the same path. variant is draft or final. The draft is optional and visibly
marked BROUILLON — À VALIDER. Approval does not require it. The final document is available
only after finalization; it has a different identity and no draft mark. In sandbox it carries
TEST — SANS VALEUR. Document generation failure can be retried without recalculating or
refinalizing payroll.
Each document response has its own status, checksum, retention deadline and a temporary private
read URL when ready. Call GET again to renew an expired URL. The document retains its identity
and storage version independently of the URL. Sandbox documents are retained for 180 days;
staging and preproduction use configured retention periods, and production can be configured
without automatic expiration. Once retention expires, PDF and manifest versions and document
metadata are purged; reads and generation return 410 document_retention_expired. Earlier
revisions remain addressable by their original revision and candidate IDs during retention.
Limits and recovery
GET and operation polling remain authoritative even if a client misses a response. When Verso
has configured an optional HTTPS destination for your partner and environment, it sends
payroll_cycle.updated after useful changes: calculation ready/failed/requiring reconciliation,
operator reconciliation resolved,
candidate invalidation or abandonment, finalization, and document ready/failed. There is no
subscription URL in a calculation request. The signal contains references only, so always
re-read the cycle or operation with your API key. Notifications can arrive twice or out of order.
If no destination is configured, polling alone provides the complete journey.
For example, a receiver may see:
Code
The sender includes webhook-id (stable event ID), webhook-timestamp (Unix seconds for this
attempt) and webhook-signature (v1, plus base64 HMAC-SHA256). Verify the signature over the
raw bytes webhook-id.webhook-timestamp.body, reject stale timestamps, persist the ID before
returning 2xx, then call the authenticated GET. During secret rotation the signature header can
contain two space-separated signatures. See the Standard Webhooks specification.
Verso retries transient non-2xx or lost responses with jittered backoff for up to 24 hours, at most one
hour between attempts. After exhaustion, Verso Ops can audit and replay the same event ID and
body; the attempt timestamp and signature change. An unavailable webhook does not cancel or
repeat payroll. HTTP 410 suspends the delivery for operator investigation.
A variable write that may have succeeded downstream is exposed
as 409 variable_write_reconciliation_required, with variableWrite.id, status and
reconciliationRequired: true on the cycle. Do not retry that write until Ops has reconciled
the frozen intent against engine history. The same principle applies to ambiguous calculation
and finalization effects.
The V1 write window is M/M-1. A later managed month must use only earlier finalized results:
prior_month_not_finalized refuses a calculation started too soon. If a later month is
already finalized, R1 returns later_finalized_month_requires_operator_replay for an earlier
correction. Verso Ops must inspect the downstream jobs and explicitly replay their correction
revisions in order; no finalized month is changed automatically. The internal operator route
checks the exact finalized job/result list and opens new, empty revisions; each month needs
a fresh input, calculation and approval backed by recorded partner consent. It is disabled
by default and does not expand the partner M/M−1 window. Inactive or offboarded tenants cannot start partner payroll
mutations. The native write guard remains active for an existing managed payroll even when the
managed journey flag is closed; native writes require the Ops policy lookup and fail closed if
that lookup is unavailable. Verso's legal control and DSN transmission remain separate Ops workflows.