DDroomwork Developers

Webhooks

Every event you can be sent, what it carries, and what you can rely on when it arrives. 69 events across 9 modules. Register an endpoint once; it gets everything unless you name the event_types you want.

What every delivery gives you

One envelope
Every event carries the same outer fields: id, type, sequence, occurred_at, livemode, mocked and data. Branch on type. Read livemode before you act on anything.
A sequence with no gaps
Per organisation and per stream, counting from one. If you hold 41, you know 42 is next. A gap tells you to read the event list from the number you have.
A signature over the exact bytes
Verify before you parse, over the raw body as you received it. The signature header carries a timestamp; reject anything outside your tolerance. Two secrets can be live at once, so a rotation never costs you a delivery.
Retries on a published schedule
Answer 2xx within 5 seconds and you're done. Anything else is retried: ten seconds, a minute, five, thirty, two hours, five, ten, twenty. After that the delivery waits for you to replay it.
A test you can fire yourself
One call sends a test event to any endpoint you've registered. It travels through the same envelope, signature and retry schedule as a real one and is marked is_test, so you can prove your receiver works without changing a record.

Every event, by module

ANCHOR 8 events Reference →

EVENTverification.resolved#

A verification reached an assurance level

You get the level reached and the evidence bundle reference.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Verification required
16 fields of Verification
id string required

The verification's identifier, starting with anchor_pro_verification_. POST /v1/identity/verifications returns it and it never changes; pass it as verification_id wherever a call names this verification.

object always "identity_verification" required

Always identity_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

outcome string required

unresolved is a real outcome and never a rejection. A source that did not answer proves nothing either way. Don't treat silence as a pass.

pendingresolvedunresolvedblocked
origin string required

How the claim arrived. The behaviour is the same whichever way it came in.

partner_apiself_serveassisted_enrolment
subject_ref string required

Your opaque reference to the person being verified, exactly as you sent subject_ref at POST /v1/identity/verifications. Not a name and not an identifier.

consent_token_id string optional

The consent token this verification ran under, as you sent it at POST /v1/identity/verifications: the id starting with anchor_pro_consent_ that POST /v1/identity/consent_tokens returned for this subject, purpose and checks.

target_assurance string optional

What was actually established. DAL-3 and DAL-4 are documented and not delivered in this release.

dal_0dal_1dal_2dal_3dal_4
assurance_reached one of optional

The level the evidence actually established, which can be lower than target_assurance. null when nothing has been established yet.

AssuranceLevelor
passport_id string · nullable optional

The id of the Passport this verification resolved to, starting with anchor_pro_passport_: what every module checks before it acts. Read it at GET /v1/identity/passports/{passport_id}; null until outcome is resolved.

evidence_bundle_id string · nullable optional

The id of the bundle listing every source consulted and what each said. Read it at GET /v1/identity/verifications/{verification_id}/evidence_bundle using this verification's id; null while there is nothing to show yet.

unresolved_source string · nullable optional

Named when the outcome is unresolved. Silence always names its source.

retry_after string · date-time · nullable optional

When to try again, as an RFC 3339 timestamp in UTC. Set when the outcome is unresolved; null otherwise.

blocked_because string · nullable optional

Why outcome is blocked: no_consent (no active token), consent_scope_insufficient (the token doesn't cover what was asked) or subject_blocklisted. The subject is entitled to know which; null for any other outcome.

no_consentconsent_scope_insufficientsubject_blocklistednull
created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTverification.unresolved#

A source did not answer

This isn't a rejection. You get the source named and a retry horizon. Don't treat it as a failed check.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Verification required
16 fields of Verification
id string required

The verification's identifier, starting with anchor_pro_verification_. POST /v1/identity/verifications returns it and it never changes; pass it as verification_id wherever a call names this verification.

object always "identity_verification" required

Always identity_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

outcome string required

unresolved is a real outcome and never a rejection. A source that did not answer proves nothing either way. Don't treat silence as a pass.

pendingresolvedunresolvedblocked
origin string required

How the claim arrived. The behaviour is the same whichever way it came in.

partner_apiself_serveassisted_enrolment
subject_ref string required

Your opaque reference to the person being verified, exactly as you sent subject_ref at POST /v1/identity/verifications. Not a name and not an identifier.

consent_token_id string optional

The consent token this verification ran under, as you sent it at POST /v1/identity/verifications: the id starting with anchor_pro_consent_ that POST /v1/identity/consent_tokens returned for this subject, purpose and checks.

target_assurance string optional

What was actually established. DAL-3 and DAL-4 are documented and not delivered in this release.

dal_0dal_1dal_2dal_3dal_4
assurance_reached one of optional

The level the evidence actually established, which can be lower than target_assurance. null when nothing has been established yet.

AssuranceLevelor
passport_id string · nullable optional

The id of the Passport this verification resolved to, starting with anchor_pro_passport_: what every module checks before it acts. Read it at GET /v1/identity/passports/{passport_id}; null until outcome is resolved.

evidence_bundle_id string · nullable optional

The id of the bundle listing every source consulted and what each said. Read it at GET /v1/identity/verifications/{verification_id}/evidence_bundle using this verification's id; null while there is nothing to show yet.

unresolved_source string · nullable optional

Named when the outcome is unresolved. Silence always names its source.

retry_after string · date-time · nullable optional

When to try again, as an RFC 3339 timestamp in UTC. Set when the outcome is unresolved; null otherwise.

blocked_because string · nullable optional

Why outcome is blocked: no_consent (no active token), consent_scope_insufficient (the token doesn't cover what was asked) or subject_blocklisted. The subject is entitled to know which; null for any other outcome.

no_consentconsent_scope_insufficientsubject_blocklistednull
created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpassport.issued#

A Passport was issued on first successful verification

The Passport is what every other module checks before it acts for you.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Passport required

What every other module checks before it acts. It carries what was established and how, never the underlying record.

14 fields of Passport
id string required

The Passport's identifier, starting with anchor_pro_passport_. It never changes: it's the passport_id on a resolved verification or the id POST /v1/identity/passports returns; pass it to GET /v1/identity/passports/{passport_id}.

object always "passport" required

Always passport. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

Your opaque reference to the person this Passport belongs to: the subject_ref you sent at POST /v1/identity/verifications on the verification that resolved into it. Not a name and not an identifier.

assurance_level string required

What was actually established. DAL-3 and DAL-4 are documented and not delivered in this release.

dal_0dal_1dal_2dal_3dal_4
assurance string optional

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
status string required
livedowngradedrevoked
identifiers_held array of object optional

Which identifiers are on file, masked. Enough to tell a subject what you hold, never enough to use it.

2 fields
kind string required

What a consent token may authorise. Ask for what you need and no more.

government_identifierbank_identifierdate_of_birth_matchname_matchcontactability
masked string required

An identity number, masked. This is the only form in which any identifier leaves ANCHOR. The full value is never returned to you, on any endpoint, at any assurance level, to any credential. There is no parameter that widens this and no scope that reveals it. If you need to show a subject which of their identifiers you hold, show them this.

disqualifying_flag boolean optional

Whether something disqualifying exists. Whether, not what. The detail is not yours to see.

evidence_bundle_ids array of string optional

The evidence bundles behind this level, one per verification that contributed. Each names the sources consulted and what they returned, never the records behind them.

monitoring string optional

Whether this subject is still watched after verification: active means yes, paused means watching has stopped. A change that lowers or revokes the Passport reaches you as a passport.downgraded or passport.revoked event.

activepaused
valid_until string · date-time required

When the supporting evidence lapses and the level falls on its own.

issued_at string · date-time optional

When the Passport was first issued, as an RFC 3339 timestamp in UTC. Normally the moment the first verification resolved.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpassport.downgraded#

Evidence lapsed and the assurance level fell

Nothing went wrong on your side. Evidence has a validity period and this one expired. Re-verify to restore the level.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Passport required

What every other module checks before it acts. It carries what was established and how, never the underlying record.

14 fields of Passport
id string required

The Passport's identifier, starting with anchor_pro_passport_. It never changes: it's the passport_id on a resolved verification or the id POST /v1/identity/passports returns; pass it to GET /v1/identity/passports/{passport_id}.

object always "passport" required

Always passport. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

Your opaque reference to the person this Passport belongs to: the subject_ref you sent at POST /v1/identity/verifications on the verification that resolved into it. Not a name and not an identifier.

assurance_level string required

What was actually established. DAL-3 and DAL-4 are documented and not delivered in this release.

dal_0dal_1dal_2dal_3dal_4
assurance string optional

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
status string required
livedowngradedrevoked
identifiers_held array of object optional

Which identifiers are on file, masked. Enough to tell a subject what you hold, never enough to use it.

2 fields
kind string required

What a consent token may authorise. Ask for what you need and no more.

government_identifierbank_identifierdate_of_birth_matchname_matchcontactability
masked string required

An identity number, masked. This is the only form in which any identifier leaves ANCHOR. The full value is never returned to you, on any endpoint, at any assurance level, to any credential. There is no parameter that widens this and no scope that reveals it. If you need to show a subject which of their identifiers you hold, show them this.

disqualifying_flag boolean optional

Whether something disqualifying exists. Whether, not what. The detail is not yours to see.

evidence_bundle_ids array of string optional

The evidence bundles behind this level, one per verification that contributed. Each names the sources consulted and what they returned, never the records behind them.

monitoring string optional

Whether this subject is still watched after verification: active means yes, paused means watching has stopped. A change that lowers or revokes the Passport reaches you as a passport.downgraded or passport.revoked event.

activepaused
valid_until string · date-time required

When the supporting evidence lapses and the level falls on its own.

issued_at string · date-time optional

When the Passport was first issued, as an RFC 3339 timestamp in UTC. Normally the moment the first verification resolved.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpassport.revoked#

A Passport was revoked

Stop acting on this identity. Every holder gets this within 15 minutes. If you missed it, the reconcile endpoint tells you.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Passport required

What every other module checks before it acts. It carries what was established and how, never the underlying record.

14 fields of Passport
id string required

The Passport's identifier, starting with anchor_pro_passport_. It never changes: it's the passport_id on a resolved verification or the id POST /v1/identity/passports returns; pass it to GET /v1/identity/passports/{passport_id}.

object always "passport" required

Always passport. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

Your opaque reference to the person this Passport belongs to: the subject_ref you sent at POST /v1/identity/verifications on the verification that resolved into it. Not a name and not an identifier.

assurance_level string required

What was actually established. DAL-3 and DAL-4 are documented and not delivered in this release.

dal_0dal_1dal_2dal_3dal_4
assurance string optional

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
status string required
livedowngradedrevoked
identifiers_held array of object optional

Which identifiers are on file, masked. Enough to tell a subject what you hold, never enough to use it.

2 fields
kind string required

What a consent token may authorise. Ask for what you need and no more.

government_identifierbank_identifierdate_of_birth_matchname_matchcontactability
masked string required

An identity number, masked. This is the only form in which any identifier leaves ANCHOR. The full value is never returned to you, on any endpoint, at any assurance level, to any credential. There is no parameter that widens this and no scope that reveals it. If you need to show a subject which of their identifiers you hold, show them this.

disqualifying_flag boolean optional

Whether something disqualifying exists. Whether, not what. The detail is not yours to see.

evidence_bundle_ids array of string optional

The evidence bundles behind this level, one per verification that contributed. Each names the sources consulted and what they returned, never the records behind them.

monitoring string optional

Whether this subject is still watched after verification: active means yes, paused means watching has stopped. A change that lowers or revokes the Passport reaches you as a passport.downgraded or passport.revoked event.

activepaused
valid_until string · date-time required

When the supporting evidence lapses and the level falls on its own.

issued_at string · date-time optional

When the Passport was first issued, as an RFC 3339 timestamp in UTC. Normally the moment the first verification resolved.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTdisclosure.revoked#

A disclosure was revoked or expired

Your access to this subject has ended. Delete what you cached.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Disclosure required
13 fields of Disclosure
id string required

The disclosure's identifier, returned by POST /v1/identity/disclosures. It starts with anchor_pro_disclosure_ and never changes; pass it as disclosure_id to retrieve or revoke the disclosure.

object always "disclosure" required

Always disclosure. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

passport_id string required

The Passport this disclosure is drawn from, as you sent it in passport_id on POST /v1/identity/disclosures. It is the id of a Passport and starts with anchor_pro_passport_.

subject_ref string optional

Your opaque reference to the person the Passport belongs to: the subject_ref you first sent at POST /v1/identity/consent_tokens. Not a name and not an identifier.

purpose string required

The purpose you gave on the request, such as employment_verification. What you may see is scoped to it.

consent_token_id string optional

The consent token this disclosure rests on: the id of a token from POST /v1/identity/consent_tokens, starting with anchor_pro_consent_, as you sent it on the request. If the subject withdraws that consent, the disclosure suspends within 24 hours.

fields_disclosed array of string optional

What you may see. Identifiers appear masked and never in full.

status string required
activerevokedexpiredsuspended
suspended_because string · nullable optional

Why the disclosure is suspended: consent_withdrawn when the subject withdrew consent, passport_revoked when the Passport was revoked, passport_downgraded when its level fell. null unless status is suspended.

consent_withdrawnpassport_revokedpassport_downgradednull
expires_at string · date-time required

Time boxed. There is no open ended disclosure.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTmerge_candidate.raised#

A probable duplicate needs adjudication

The match wasn't strong enough to merge on its own. You decide.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data MergeCandidate required
8 fields of MergeCandidate
id string required

The candidate's identifier. It starts with sub_ and never changes; pass it as merge_candidate_id at GET /v1/identity/merge_candidates/{merge_candidate_id} or on POST /v1/identity/merges when you decide.

object always "merge_candidate" required

Always merge_candidate. Tells you which kind of record you are looking at, so one handler can read any response.

left_ref string required

One of the two subject_ref values that may belong to the same person, as you first sent them at POST /v1/identity/consent_tokens. Pass it as surviving_ref or merged_ref on POST /v1/identity/merges when you decide.

right_ref string required

The other subject_ref that may belong to the same person, as you first sent it at POST /v1/identity/consent_tokens. Pass it as surviving_ref or merged_ref on POST /v1/identity/merges when you decide.

strength string required

Only a strong match merges automatically. Probable is raised for you to decide.

strongprobableweak
signals array of object required

What matched and what did not. Both, so you see the case against as well as the case for.

3 fields
signal string required

Which attribute was compared, such as date_of_birth. One entry per attribute, whichever way it came out.

agrees boolean required

true when this attribute matched across the two records, false when it didn't. Read the false entries as carefully as the true ones.

detail string optional

A plain-words note on how this attribute compared, when there's more to say than agrees. Left out otherwise.

status string optional

Where the candidate stands: open awaits your decision, merged means you merged the two records at POST /v1/identity/merges, rejected means they were judged to be different people.

openmergedrejected
created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTconsent.withdrawn#

A subject withdrew consent

Every disclosure resting on that consent suspends within 24 hours. Stop processing on the withdrawn basis now. Don't wait for the deadline.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data ConsentToken required
12 fields of ConsentToken
id string required

The token's identifier, starting with anchor_pro_consent_. POST /v1/identity/consent_tokens returns it and it never changes; send it as consent_token_id at POST /v1/identity/verifications.

object always "consent_token" required

Always consent_token. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

Your opaque reference to the person, exactly as you sent subject_ref at POST /v1/identity/consent_tokens. Not a name and not an identifier.

purpose string required

What the subject agreed their data may be used for, as you stated it, for example employment_verification. This token covers no other purpose.

checks array of CheckKind required

The checks the subject agreed to, from government_identifier, bank_identifier, date_of_birth_match, name_match and contactability. A check not listed here is not authorised by this token.

status string required

Whether the token still authorises anything: active does, withdrawn means you or the subject took it back, expired means expires_at has passed. Only an active token authorises a check.

activewithdrawnexpired
wording_version string optional

The exact wording the subject saw. What someone agreed to is answered from the record, not from whatever the current page says.

collected_channel string optional

How the consent was collected, as you sent it at POST /v1/identity/consent_tokens: api through your integration, self_serve by the subject themselves, assisted by someone enrolling them, or whatsapp over WhatsApp.

apiself_serveassistedwhatsapp
collected_at string · date-time optional

When the subject gave this consent, as an RFC 3339 timestamp in UTC.

expires_at string · date-time required

When this consent lapses, as an RFC 3339 timestamp in UTC. After that moment the token's status is expired and it authorises nothing.

What you answer

Return 200 within 5 seconds. Anything else is retried.

PROOF 8 events Reference →

EVENTverification.completed#

A verification reached an outcome

Read the outcome before you act. verified, contradicted and unverified all arrive on this event, and each means something different.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Verification required
19 fields of Verification
id string required

The verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.

object always "credential_verification" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

outcome string required

Four values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.

pendingverifiedunverifiedcontradicted
subject_ref string required

The opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.

passport_id string optional

The live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.

identity_assurance string optional

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
issuer_id string required

The issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.

issuer_name string optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.

cached boolean optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTverification.contradicted#

An issuer said the credential is not what was claimed

This is a finding about the person, not about the register. It is not the same as nobody answering, and it calls for a different response from you.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Verification required
19 fields of Verification
id string required

The verification's identifier. It starts with proof_academic_verification_, never changes, is returned by POST /v1/credentials/verifications and listed by GET /v1/credentials/verifications; send it wherever a call names this record.

object always "credential_verification" required

Always credential_verification. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

outcome string required

Four values, and collapsing any two of them destroys the distinction they exist for. verified means the issuer confirmed it. contradicted means the issuer said otherwise, which is a finding about the person. unverified means nobody answered, which is a finding about the register and never a pass. pending means an assisted path is running to its published service level.

pendingverifiedunverifiedcontradicted
subject_ref string required

The opaque reference to the person whose credential this is, exactly as you chose and sent it in subject_ref at POST /v1/credentials/verifications. Not a name and not an identifier.

passport_id string optional

The live ANCHOR Passport this ran against, as the id starting anchor_pro_passport_ that POST /v1/identity/passports or GET /v1/identity/passports returns. Present when identity_assurance is verified.

identity_assurance string optional

How a fact was established. Recorded on the fact rather than in configuration, so an attested identity and a verified one stay distinguishable a year later, which is the distinction that matters when something is disputed.

attestedverified
issuer_id string required

The issuer the credential was checked with, as you sent it: the id of an issuer from GET /v1/credentials/issuers, starting proof_academic_verification_. issuer_name gives the same issuer in words.

issuer_name string optional

The issuer's name, as it appears in GET /v1/credentials/issuers, so you can show who was asked without a second call.

method string required

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
credential_kind string optional

What kind of credential was checked, as you sent it, such as bachelor_degree.

evidence_date string · date · nullable optional

When the evidence was gathered, not when it was read.

unresolved_source string · nullable optional

Named when the outcome is unverified. Silence always names its source.

contradiction_detail string · nullable optional

What the issuer actually said, where it disagreed.

service_level_due string · date · nullable optional

Present while pending. When the assisted path is due to answer.

claim_id string · nullable optional

The competence claim this verification produced: an id starting proof_academic_claim_, readable at GET /v1/credentials/claims/{claim_id}. Set once the outcome is verified; null before then and for every other outcome.

cached boolean optional

True when evidence inside the issuer's validity period was reused. You are not charged twice for the same answer.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTclaim.decayed#

A claim aged into a lower decay state

Nothing was withdrawn. The evidence is older, and the published decay function moved the claim down. Re-attestation resets it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data CompetenceClaim required
16 fields of CompetenceClaim
id string required

The claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.

object always "competence_claim" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

The opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.

claim_code string required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.

level integer · minimum 1 required

How high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.

evidence_class string required

Ordered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.

e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstrated
evidence_date string · date required

The day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.

issuing_method string optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.

superseded_by string · nullable optional

Set when a re-attestation replaced this claim. The old one stays.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTclaim.lapsed#

A claim reached expiry and was downgraded

The claim is still there, marked lapsed. Stop relying on it, but don't assume the person never held it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data CompetenceClaim required
16 fields of CompetenceClaim
id string required

The claim's identifier, starting proof_academic_claim_; it never changes. POST /v1/credentials/claims returns it, a verified verification carries it as claim_id, and GET /v1/credentials/claims/{claim_id} takes it.

object always "competence_claim" required

Always competence_claim. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string required

The opaque reference to the person this claim is about, as you sent it in subject_ref at POST /v1/credentials/wallets or POST /v1/credentials/verifications. Not a name and not an identifier.

claim_code string required

Which competence the claim asserts: the code of one claim in a competency framework, as listed at GET /v1/credentials/frameworks.

framework_version string optional

The framework version the claim was made under, such as 2026.08.1. The claim keeps meaning what it meant under that version, whatever a later one says.

level integer · minimum 1 required

How high the competence was assessed, as a whole number from 1 upward. Read it with evidence_class, which says how far you can rely on it.

evidence_class string required

Ordered by how directly the evidence observes the work, not by how formal it looks. A demonstration outranks a certificate, because watching someone do the thing beats a document saying they once could.

e1_claimede2_attestede3_documentede4_verified_with_issuere5_demonstrated
evidence_date string · date required

The day the evidence was gathered, as a YYYY-MM-DD date, not the day it was read. The claim ages from here on its decay_function.

issuing_method string optional

How the issuer is reached. An assisted path publishes a service level.

register_apiissuer_portalassisted_manual
decay_state string required

Where a claim sits on its published decay function. It moves on its own and cannot be quietly extended.

currentageingstalelapsed
expires_at string · date required

Set when the claim was issued. Every claim has one. A Passport does not, because identity doesn't decay.

decay_function string optional

The published function this claim ages on. Readable so the expiry can be predicted rather than discovered.

verification_id string · nullable optional

The verification this claim rests on: an id starting proof_academic_verification_, from POST /v1/credentials/verifications. null for a competence established elsewhere and recorded directly at POST /v1/credentials/claims.

superseded_by string · nullable optional

Set when a re-attestation replaced this claim. The old one stays.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTlicence.expired#

A licence expired

Where the licence was legally required, this is a hard block from now on. RAIL is told at the same time you are.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Licence required
11 fields of Licence
id string required

This licence's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as licence_id to GET /v1/credentials/licences/{licence_id}.

object always "licence" required

Always licence. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The person who holds the licence, as the opaque subject_ref you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.

licence_kind string required

What the licence is for, as a code such as electrical_installation_practitioner. Read statutorily_required_for for the occupations the law ties it to.

issuer_id string optional

The body that granted the licence, as the id of an issuer from GET /v1/credentials/issuers, starting with proof_academic_verification_. Read it at GET /v1/credentials/issuers/{issuer_id} for its kind and how it's reached.

licence_number_masked string optional

Masked. The number itself belongs to the holder.

status string required
validexpiringexpiredrevoked
issued_on string · date optional

The day the issuer granted the licence, as a YYYY-MM-DD date.

expires_on string · date optional

The day the licence runs out, as a YYYY-MM-DD date. Once it passes, status is expired, the Wallet entry expires on its own, and you're told if you hold a live disclosure.

statutorily_required_for array of string optional

Occupations where the law requires this licence.

statutory_block boolean required

True when this licence is legally required and is not currently valid. Read it as a gate on its own. It is not a factor to weigh against other evidence, and nothing else in the Wallet substitutes for it.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTreattestation.due#

A claim is approaching expiry and can be refreshed

You get this ahead of expiry, with the specific path the worker follows to refresh the claim. Act on it and the claim never lapses.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Reattestation required
9 fields of Reattestation
id string required

This re-attestation's identifier. It starts with proof_academic_claim_, never changes, and is what you pass as reattestation_id to GET /v1/credentials/reattestations/{reattestation_id}.

object always "reattestation" required

Always reattestation. Tells you which kind of record you are looking at, so one handler can read any response.

claim_id string required

The claim being refreshed: the id you sent at POST /v1/credentials/reattestations, starting with proof_academic_claim_. Once the worker finishes, new_claim_id carries the replacement and this claim's superseded_by points to it.

subject_ref string optional

The worker whose claim is being refreshed, as the opaque subject_ref the claim carries: the one you chose and sent at POST /v1/credentials/verifications or POST /v1/credentials/wallets. Not a name and not an identifier.

status string required

Where the refresh stands: notified once the notice is sent, in_progress while the worker follows the path, completed once a new claim replaces the old one, expired if the claim lapsed first.

notifiedin_progresscompletedexpired
path object required

What the worker actually has to do, rather than a warning that something is about to lapse.

3 fields
action string required

What the worker has to do, in words you can show them, such as upload a current licence certificate.

url string · uri required

The link the worker follows to do it. The notice carries it, and you can show it to them yourself as well.

estimated_minutes integer optional

How long the step should take the worker, in minutes. Absent when there is no estimate.

notice_sent_at string · date-time optional

When the worker was sent the notice carrying this path, as an RFC 3339 timestamp in UTC.

claim_expires_at string · date optional

The day the claim being refreshed expires, as a calendar date, YYYY-MM-DD. The refresh has to finish before it, or the claim lapses and the re-attestation is expired.

new_claim_id string · nullable optional

The id of the claim that replaced the old one once status is completed; null until then. It starts with proof_academic_claim_ and is read at GET /v1/credentials/claims/{claim_id}; the old claim stays, with this in its superseded_by.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTappeal.upheld#

An appeal succeeded and standing was restored

Standing is restored, and everyone holding a live disclosure is told, the same as they would be of a lapse.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Appeal required
9 fields of Appeal
id string required

The appeal's identifier, first returned by POST /v1/credentials/appeals and read back at GET /v1/credentials/appeals/{appeal_id}. It starts with proof_academic_claim_ and never changes; pass it as appeal_id.

object always "appeal" required

Always appeal. Tells you which kind of record you are looking at, so one handler can read any response.

subject_ref string required

The opaque reference you chose for the person and sent as subject_ref at POST /v1/credentials/appeals, the same one the result under appeal carries, echoed as you sent it. Not a name and not an identifier.

against object required

The result under appeal, by its kind and id, as you named it when you filed.

2 fields
kind string required

Which kind of result is under appeal, as you named it at POST /v1/credentials/appeals: verification, document_analysis, claim or licence. It says which record id names.

verificationdocument_analysisclaimlicence
id string required

The id of the record under appeal, as you sent it at POST /v1/credentials/appeals, whichever kind names. It starts with proof_academic_verification_ (verification, document analysis) or proof_academic_claim_ (claim, licence).

grounds string optional

Why the subject says the result is wrong, as you stated it when you filed.

status string required

Where the appeal stands: received once filed, in_review while it is being decided, upheld if the result was overturned and standing restored, rejected if the result stands. Only upheld fills restored_claim_ids.

receivedin_reviewupheldrejected
due_by string · date required

The published service level. An appeal has a date, not an intention.

outcome_detail string · nullable optional

The reasons given with the decision, in plain words. null while there is no decision to explain.

restored_claim_ids array of string optional

Set when the appeal was upheld. These propagate to every live disclosure.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTforgery.escalated#

A confirmed forgery was escalated to ANCHOR

The forgery is raised with ANCHOR, with the supporting evidence. ANCHOR decides whether to blocklist, under four eyes.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data DocumentAnalysis required
9 fields of DocumentAnalysis
id string required

The analysis's identifier. It starts with proof_academic_verification_, never changes, and is returned by POST /v1/credentials/document_analyses; send it as document_analysis_id at POST /v1/credentials/verifications.

object always "document_analysis" required

Always document_analysis. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

subject_ref string optional

The opaque reference to the person the document names, exactly as you chose and sent it in subject_ref at POST /v1/credentials/document_analyses. Not a name and not an identifier.

finding string required

The overall finding: consistent with a genuine document, questionable, or confirmed_forgery. Only confirmed_forgery is escalated to ANCHOR, shown by escalated_to_anchor; read factors to see which score raised a concern.

consistentquestionableconfirmed_forgery
factors array of object required

One score per factor, never a single number. A combined figure hides which factor raised the concern and cannot be argued with.

3 fields
factor string required

Five factors, scored separately so a concern can be pointed at.

templatesealtypographyserial_structureissuance_plausibility
score number · minimum 0 · maximum 1 required

Higher means more consistent with a genuine document.

note string required

A remark on this factor, in words, so you can say what raised a concern. It isn't the reasoning behind the score; you don't get that.

escalated_to_anchor boolean optional

True on a confirmed forgery, which is escalated to ANCHOR to decide whether to blocklist.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

RAIL 9 events Reference →

EVENTengagement.classified#

An engagement was classified

Classification alone gives you no rights. Don't act on it yet.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTengagement.executed#

An instrument was executed by both parties

Both parties have executed the instrument. You can dispatch from here. Don't run payroll until the engagement is registered.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTengagement.registered#

An engagement was registered with the relevant authority

The engagement is registered with the relevant authority. You can run payroll against it now.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTengagement.lapsed#

An engagement is no longer compliant and has lapsed

Stop dispatching against it and stop paying on it. If you think you may have missed this event, call the reconcile endpoint to find out.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTengagement.disputed#

A dispute was raised and the evidence is frozen

The evidence is frozen. Termination is suspended until the dispute resolves.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTengagement.terminated#

An engagement was terminated

The relationship has ended. The record stays, and you can still read its history.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Engagement required
15 fields of Engagement
id string required

The engagement's identifier, starting with rail_engagement_. You get it from POST /v1/rail/engagements and pass it as engagement_id on every call about this engagement; it never changes.

object always "engagement" required

Always engagement. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
engaging_party_ref string required

The party engaging the worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the worker.

worker_ref string required

The worker, as the subject_ref you chose for them at POST /v1/identity/consent_tokens and sent when you opened the engagement at POST /v1/rail/engagements. Anchored, the same as the engaging party.

classification one of optional

The classification once classify has run, or null before then: employment, fixed_term_employment, independent_contracting, apprenticeship, casual or task_based. It decides which instrument you can paper.

Classificationor
classification_review_required boolean optional

True when confidence was low or the result was contested. A person decides.

instrument_in_force_id string · nullable optional

The id of the instrument in force, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. null until one is executed; a variation that supersedes it takes its place here once executed.

envelope_id string · nullable optional

The id of the obligation envelope published for this engagement, starting with rail_envelope_; null until one is published. Retrieve it at GET /v1/rail/engagements/{engagement_id}/envelope or list it at GET /v1/rail/envelopes.

starts_on string · date optional

The date the engagement starts, as a calendar date in YYYY-MM-DD. As you sent it when you opened the engagement.

ends_on string · date · nullable optional

The date the engagement ends, as a calendar date in YYYY-MM-DD. null when no end date was set.

as_of string · date · nullable optional

Present when this was answered as at a past date rather than now.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTinstrument.expired#

An unexecuted instrument passed its window

The instrument passed its window without being executed. The lapse is recorded. If the relationship is still on, draft a fresh instrument.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Instrument required
16 fields of Instrument
id string required

The instrument's identifier, starting with rail_instrument_, from rail.instruments.create or, for a variation, rail.instruments.supersede. Pass it as instrument_id on every call about this instrument; it never changes.

object always "instrument" required

Always instrument. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

engagement_id string required

The id of the engagement this instrument papers, starting with rail_engagement_, as returned by POST /v1/rail/engagements. A variation drafted by supersede stays on the same engagement.

status string required
draftedawaiting_assentexecutedexpiredsuperseded
template_code string required

A template from the approved set. Not wording.

clauses array of object optional

The clauses used, by reference. Every one comes from the approved library, as written there.

2 fields
clause_code string required

The code of a clause from the approved library. Look it up by the same clause_code in the clause library to read what the instrument contains.

clause_version string required

The version of that clause as it stood in the library the instrument was built from. The instrument holds this version even after the library moves on.

clause_library_version string required

The clause library version this instrument was assembled from, such as 2026.08.1. Pass it as version to the clause library endpoint to read exactly what was used.

rule_pack_version string optional

The version of the rule pack this instrument rests on, such as 2026.08.1: the rules the classification behind it was judged against. It stays on the instrument once executed, whatever pack comes later.

legal_approval_reference string optional

The reference of the legal approval behind the clause library this instrument was built from. Cite it when you're asked what approved the wording; it matches the library's own.

seal one of optional

The tamper evident seal, applied when the instrument is executed; null until then. Send its hash to the verify endpoint to prove the document you hold is the one that was executed.

Sealor
supersedes string · nullable optional

The id of the instrument this one was drafted as a variation of, starting with rail_instrument_, or null for an original. The earlier instrument is never edited or removed.

superseded_by string · nullable optional

The id of the variation drafted to replace this instrument, starting with rail_instrument_, or null while none has been. Follow it forward to reach the newest version.

expires_at string · date-time · nullable optional

An unexecuted instrument lapses after this, and the lapse is recorded.

executed_at string · date-time · nullable optional

When both parties' assent was complete and the instrument was executed, as an RFC 3339 timestamp in UTC. null until then; nothing binds anyone before it.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTenvelope.published#

An obligation envelope was published for payroll and remittance

You get what's owed and on what basis. It carries no amount. Your payroll computes the figure from it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data ObligationEnvelope required

What this engagement makes owed. No figure appears anywhere in this object. An obligation that does not attach, because a threshold or exemption applies, is absent rather than present and zero.

10 fields of ObligationEnvelope
id string required

The envelope's identifier, starting with rail_envelope_. You get it at GET /v1/rail/engagements/{engagement_id}/envelope, as envelope_id on the engagement and in data on the envelope.published event; it never changes.

object always "obligation_envelope" required

Always obligation_envelope. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

engagement_id string required

The engagement this envelope states the obligations for: its id, starting with rail_engagement_, as returned by POST /v1/rail/engagements. Pass it as engagement_id to retrieve either record.

classification string optional

Decided by the versioned pack. No model decides it and you can't assert one yourself.

employmentfixed_term_employmentindependent_contractingapprenticeshipcasualtask_based
obligations array of object required

One entry per obligation that attaches: to which authority, on what basis, from what date and how often, never an amount. One a threshold or exemption removes is absent, not present and zero.

7 fields
authority string required

The authority the obligation is owed to, by name, such as Rivers State Internal Revenue Service. For people to read; match records on authority_id.

authority_id string optional

The id of the authority the obligation is owed to, as listed at GET /v1/remittance/authorities; it starts with remit_authority_rail_obligation_. Match on it rather than on authority, which is the name.

basis string required

How an amount is worked out, not what it is. Your payroll computes the figure from it.

effective_from string · date required

The date from which the obligation applies, as a calendar date in YYYY-MM-DD. Nothing is owed under it for any earlier date.

effective_to string · date · nullable optional

The last date the obligation applies, as a calendar date in YYYY-MM-DD. null while it has no end date.

frequency string required

How often the obligation falls due: monthly, quarterly, annual, or per_engagement for one that is owed once for the engagement rather than by period.

monthlyquarterlyannualper_engagement
threshold_note string optional

Why a threshold or exemption did or did not apply here.

rule_pack_version string optional

The version of the rule pack these obligations were stated under, such as 2026.08.1. Keep it with the envelope: it names the rules your payroll's figures trace back to.

published_at string · date-time required

When this envelope was published for your payroll and remittance to read, as an RFC 3339 timestamp in UTC.

supersedes string · nullable optional

The id of the envelope this one replaces, starting with rail_envelope_, or null for the first envelope on this engagement. Follow it back to see what was owed before.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTremediation_case.opened#

A rule pack change made a live engagement non compliant

You get the specific change and the action you need to take. Act on it to stop the engagement lapsing.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data RemediationCase required

Opened when a pack publication makes a live engagement non compliant. Names the change and the action, rather than reporting that something is wrong.

10 fields of RemediationCase
id string required

The case's identifier, starting with rail_engagement_, as listed at GET /v1/rail/remediation_cases or in data on the remediation_case.opened event. Pass it as remediation_case_id to retrieve or resolve it; it never changes.

object always "remediation_case" required

Always remediation_case. Tells you which kind of record you are looking at, so one handler can read any response.

engagement_id string required

The id of the engagement the rule pack change made non compliant, starting with rail_engagement_, as returned by POST /v1/rail/engagements. Its status has moved; engagement_moved_to says where.

status string required
openin_progressresolved
pack_change object required

The rule pack change that opened this case: the version the engagement was compliant under, the version it isn't, and a plain words summary of what changed.

3 fields
from_version string required

The rule pack version the engagement was compliant under before the change, such as 2026.08.1. summary says what moved between this and to_version.

to_version string required

The rule pack version whose publication opened this case, such as 2026.08.1. The engagement was compliant under from_version and is not under this one.

summary string required

What actually changed in the rules, in plain words.

why_non_compliant string optional

Why the engagement no longer complies under the new pack version, in plain words. Read it with pack_change.summary to see which rule change caused it.

required_action string required

What you need to do to bring the engagement back into compliance, such as draft a variation reclassifying this engagement as employment. Report what you did as action_taken when you close the case.

engagement_moved_to string optional

Transitions are enforced, and every one is recorded. Classification alone confers no rights and an unexecuted instrument binds no one.

classifiedpaperedexecutedregisteredcurrentlapseddisputedterminated
opened_at string · date-time optional

When the case was opened, as an RFC 3339 timestamp in UTC.

resolved_by string · nullable optional

Who closed the case: the actor that called resolve. null until the case is resolved.

What you answer

Return 200 within 5 seconds. Anything else is retried.

FLOW 8 events Reference →

EVENTrequisition.published#

A requisition was published

Every inference on the requisition was confirmed before this fired. You can broadcast it now.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Requisition required
16 fields of Requisition
id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrequisition.quarantined#

A requisition was held for review

The requisition matched discriminatory criteria, unlawful terms or an advance fee pattern. The rule that fired is named, so you can dispute it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Requisition required
16 fields of Requisition
id string required

The requisition's identifier, starting with flow_enterprise_requisition_; it never changes. You get it from POST /v1/sourcing/requisitions (or in requisition_ids on a bulk upload) and pass it as requisition_id on every call about it.

object always "requisition" required

Always requisition. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

A requisition sits in pending_confirmation while an inferred pay figure, headcount, location or start date is unconfirmed. It cannot leave that state until you confirm.

pending_confirmationdraftunder_reviewquarantinedpublishedclosed
version integer · minimum 1 required

An edit after first broadcast makes a new version. Leads stay on theirs.

job_title string required

The title of the role as you gave it. The occupation code is inferred from it when you don't send one, so a local or colloquial title is fine.

occupation_code string optional

The occupation this role maps to in the taxonomy, such as ng-7412. Inferred from the title when you didn't send one, and listed in inferred_fields until you confirm it.

employment_type string optional
permanentfixed_termcontractcasualapprenticeship
headcount integer · minimum 1 required

How many people you are hiring for this role, at least 1. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

compensation Compensation optional
3 fields of Compensation
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

period string required

The span the amount pays for: monthly, annual, daily or per_task (one payment for each task done). Required whenever you send compensation.

monthlyannualdailyper_task
negotiable boolean optional

Whether the figure is open to negotiation: true when it is, false when it is fixed.

location Location optional
3 fields of Location
description string required

Where the work is, in words a candidate would recognise, such as Ikeja, Lagos. The one part of a location you must give.

state string optional

The Nigerian state the work is in, such as Lagos. Optional; the description alone is enough to create the requisition.

geofence_radius_metres integer · nullable optional

A radius around the location, in whole metres, or null when there is none. If it had to be inferred it comes back in inferred_fields for you to confirm.

starts_on string · date · nullable optional

The date the role starts, as YYYY-MM-DD, or null when none is set. It never broadcasts on inference alone; confirm it if it appears in inferred_fields.

inferred_fields array of string optional

Fields that were inferred rather than read from what you sent. Each has to be confirmed, and compensation, headcount, location and start date can never broadcast while they are here.

quarantine one of optional

Why the requisition was held: the rule it tripped, the rule pack version, the detail and the offending text. null when nothing held it; fix what it names before you publish.

QuarantineFindingor
created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTbroadcast.channel_delivered#

One channel accepted the advert

You get one of these per channel. A broadcast to five channels sends you five.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Delivery required
10 fields of Delivery
id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

Always channel_delivery. Tells you which kind of record you are looking at, so one handler can read any response.

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTbroadcast.channel_failed#

One channel failed terminally

Retries are exhausted for this channel. Your other channels are unaffected. You always hear about a failure; a channel is never dropped silently.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Delivery required
10 fields of Delivery
id string required

The delivery's identifier, starting with flow_enterprise_delivery_; it never changes. It is in a broadcast's deliveries, at GET /v1/sourcing/deliveries and from POST /v1/sourcing/deliveries; pass it as delivery_id to read or retry it.

object always "channel_delivery" required

Always channel_delivery. Tells you which kind of record you are looking at, so one handler can read any response.

broadcast_id string required

The broadcast this delivery is part of: the id, starting with flow_enterprise_broadcast_, from POST /v1/sourcing/requisitions/{requisition_id}/broadcasts, or the one you named as broadcast_id at POST /v1/sourcing/deliveries.

requisition_id string optional

The requisition this delivery carried: the id of the requisition behind the broadcast, starting with flow_enterprise_requisition_, from POST /v1/sourcing/requisitions. Filter GET /v1/sourcing/deliveries by it to see every channel it went to.

channel_id string required

The channel this delivery went to: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. One delivery covers one channel, so a broadcast has one delivery per channel it went to.

state string required

failed is terminal and always reported. A channel you selected is never dropped silently.

queuedsendingdeliveredretryingfailed
attempts integer · minimum 0 required

How many times posting to this channel has been tried so far, 0 before the first try. Read it with state and last_fault to see how a channel is doing.

next_attempt_at string · date-time · nullable optional

Exponential backoff. Absent once the state is terminal.

last_fault string · nullable optional

Why the most recent attempt at this channel failed, in plain words; null while nothing has failed. Read it before you retry.

external_reference string · nullable optional

The channel's own reference for this posting, where the channel gives one. null otherwise, so name the delivery by id, never by this.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTlead.captured#

A candidate responded

Consent was recorded at first contact, with the lawful basis, the channel and the exact wording version the candidate saw.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Lead required
14 fields of Lead
id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTlead.qualified#

A lead passed screening

The lead passed your knockout rules. Each question and the decision on it is on the screening.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Lead required
14 fields of Lead
id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTlead.promoted#

A lead was promoted toward identity

Attribution survives promotion. The channel that produced the lead stays credited with the hire.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Lead required
14 fields of Lead
id string required

The lead's identifier. It starts with flow_enterprise_lead_ and never changes; pass it as lead_id on GET /v1/sourcing/leads/{lead_id} and the /screening, /attribution and /promote routes under it.

object always "lead" required

Always lead. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

requisition_id string required

The requisition this lead answered: its id from POST /v1/sourcing/requisitions, starting with flow_enterprise_requisition_. With requisition_version, it pins the exact version the lead was captured against.

requisition_version integer required

Which version produced this lead. A shortlist is reproducible against it.

channel_id string optional

The channel the lead came through: the id of a channel from GET /v1/sourcing/channels, starting with flow_enterprise_broadcast_. It is what your yield at GET /v1/sourcing/analytics counts against.

screening_outcome string optional
in_progresspassedfailedabandoned
fit_score number · minimum 0 · maximum 100 required

How well the lead fits the requisition, from 0 to 100. The three factors behind it are in top_factors, so a score never arrives without a reason.

top_factors array of object optional

The three factors that most influenced the score. Three, always, so a score is never a number without a reason.

2 fields
factor string required

The name of one of the three things that most influenced fit_score. Read it with contribution to see how far it moved the score.

contribution number required

How much this factor moved fit_score. Read the three together to see why the lead scored as it did.

consent LeadConsent required

Captured at first contact, before anything else is asked.

4 fields of LeadConsent
lawful_basis string required

The lawful basis you process this candidate's data under: consent (they agreed), legitimate_interest (your hiring interest) or contract (steps toward a contract with them). Recorded at first contact, before anything else is asked.

consentlegitimate_interestcontract
channel string required

The channel the candidate gave consent on. The channel agreed to nothing on their behalf; this is where the candidate themselves agreed.

wording_version string required

The exact wording presented. Answered from the record, not the current page.

captured_at string · date-time required

When the candidate gave consent, as an RFC 3339 timestamp in UTC. It comes before every answer the lead gave.

duplicate_of string · nullable optional

Set when this response matched an existing lead on contact identity.

promoted_subject_ref string · nullable optional

Set once you promote the lead at POST /v1/sourcing/leads/{lead_id}/promote, null before: the person's opaque reference, starting with sub_, that you send as subject_ref at POST /v1/identity/consent_tokens and POST /v1/identity/verifications.

captured_at string · date-time optional

When the lead came in, as an RFC 3339 timestamp in UTC. Screening can still be in_progress after this; it says when the candidate was captured, not when they were judged.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTspend_ceiling.blocked#

A broadcast was blocked by a spend ceiling

You were not charged and no spend was calculated. Raise the ceiling or drop the paid channels and try again.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data SpendCeilings required

Checked before any paid channel runs. A breach blocks the broadcast, and no charge is computed or debited when it does.

3 fields of SpendCeilings
object always "spend_ceilings" required

Always spend_ceilings. Tells you which kind of record you are looking at, so one handler can read any response.

organisation object required

Your organisation's ceiling on paid channel spend across every requisition, and what you've consumed against it. Checked before any paid channel runs.

2 fields
ceiling Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

consumed Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

per_requisition array of object optional

One entry per requisition with its own ceiling, and what's been consumed against it. A broadcast is checked against the requisition's ceiling and your organisation's, and either can block it.

3 fields
requisition_id string required

The requisition this ceiling applies to: the id of one of your requisitions, from POST /v1/sourcing/requisitions or GET /v1/sourcing/requisitions. It starts with flow_enterprise_requisition_.

ceiling Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

consumed Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

What you answer

Return 200 within 5 seconds. Anything else is retried.

MATCH 10 events Reference →

EVENTwork_order.opened#

A work order was opened

You get this when a work order opens. The requester was verified before it opened. The cascade starts from here.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data WorkOrder required

One canonical order, whatever route the demand arrived by. It carries no money beyond the ceiling the requester set, because MATCH neither computes nor moves any.

21 fields of WorkOrder
id string required

The order's identifier, returned in id by POST /v1/allocation/work_orders when you opened it. It starts with match_geo_aware_workforce_orchestrator_work_order_, never changes, and is the work_order_id every call on the order takes.

object always "work_order" required

Always work_order. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

An order is open until it is offered, filled, cancelled or declared unfilled. Nothing moves it back.

openofferedfilledcancelledunfilled
requester_id string required

The requester the work is for, as you sent it in requester_id at POST /v1/allocation/work_orders: their sub_ subject reference, the subject_ref ANCHOR verified at POST /v1/identity/verifications before the order opened.

service_category string required

The kind of service the work is, as you named it when you opened the order, such as field_maintenance. It names the service; occupation_code names who can do it.

occupation_code string required

The occupation the work needs, as a code from the occupation taxonomy, such as ng-7412. Any supply gap declared for the order carries it, so sourcing demand names the occupation.

headcount integer · minimum 1 required

The order stays open until this many acceptances are held.

headcount_filled integer · minimum 0 optional

How many acceptances the order holds so far, counted from 0. Read it against headcount to see how far the order is from filling.

urgency_class string required

How hard the cascade pushes, and how quickly an offer expires.

standardpriorityemergency
location GeoPoint required

A position reading. Accuracy is carried because a reading without it cannot be judged.

3 fields of GeoPoint
latitude number · minimum -90 · maximum 90 required

Latitude in decimal degrees, from -90 to 90. Negative values are south of the equator.

longitude number · minimum -180 · maximum 180 required

Longitude in decimal degrees, from -180 to 180. Negative values are west of the Greenwich meridian.

accuracy_metres integer · minimum 0 required

The radius the device reports as its confidence. A large value weakens every inference drawn from the point.

location_description string optional

The place in words a worker would recognise, such as Ikeja industrial estate, gate 3. Optional; location is the position the ranking uses.

time_window TimeWindow required

When the work may be done. A window, never a single instant.

2 fields of TimeWindow
starts_at string · date-time required

When the window opens: the earliest the work may start, as an RFC 3339 timestamp in UTC.

ends_at string · date-time required

When the window closes: the latest the work may finish, as an RFC 3339 timestamp in UTC.

constraints array of Constraint optional

Hard constraints and soft preferences in one list, each naming which it is.

3 fields of Constraint
code string required

What the requirement is, as a code such as licence_electrical. A relaxation names the soft preference it dropped by this code, as its preference_code.

kind string required

hard for a requirement that is never relaxed and gates who can be ranked at all; soft for a preference the cascade may drop to widen the pool, with the drop recorded in the order's relaxations.

hardsoft
detail string optional

What the requirement means in words, such as Current electrical licence. Optional; the code is what a worker has to meet, and the detail is for people reading the order.

relaxations array of Relaxation optional

Every soft preference the cascade dropped, with the round it happened in.

4 fields of Relaxation
preference_code string required

The code of the soft preference that was dropped, as it appears in the order's constraints.

round integer · minimum 1 required

The cascade round the preference was dropped in, counted from 1. Match it to the same round in the ranking to see who the wider pool brought in.

relaxed_at string · date-time required

When the preference was dropped, as an RFC 3339 timestamp in UTC.

reason string required

Why the preference was dropped: pool_exhausted when the eligible pool ran out, expiry_approaching when the order's expires_at was near, headcount_unmet when acceptances fell short of headcount.

pool_exhaustedexpiry_approachingheadcount_unmet
escalation_bounds EscalationBounds optional

How far the search may widen. It's published on the order, so you can read the escalation policy.

3 fields of EscalationBounds
initial_radius_metres integer · minimum 0 required

The radius the first round searches within, in metres from location.

maximum_radius_metres integer · minimum 0 required

The furthest the radius may widen to, in metres from location. No round searches beyond it.

step_metres integer · minimum 1 required

How much the radius widens by on each escalation, in metres, at least 1. A round that widened is marked escalated in the ranking.

budget_ceiling Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

expires_at string · date-time required

On expiry the order is declared unfilled with a reason, never left open.

cancelled_reason string · nullable optional

Why the order was cancelled, as stated when it was cancelled. null while the order has not been cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENToffer.made#

An offer was made to a worker

You get one of these per offer. The worker passed every gate before they were ranked, and the offer carries the ranking round that produced it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Offer required

One offer to one worker, in one round of the cascade.

13 fields of Offer
id string required

The offer's identifier. It starts with match_geo_aware_workforce_orchestrator_offer_ and never changes; pass it as {offer_id} to GET /v1/allocation/offers/{offer_id} and to its /accept and /decline calls.

object always "offer" required

Always offer. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this offer is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order and its ranking.

worker_id string required

The worker this offer went to, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. One offer names one worker.

status string required
madeaccepteddeclinedexpiredwithdrawn
round integer · minimum 1 required

The cascade round this offer belongs to.

rank integer · minimum 1 optional

Where the worker stood in the round's ranking when this offer was made, 1 being the top.

expires_at string · date-time required

An offer that is not answered by this time is treated as a decline.

closed_reason one of optional

Set where the offer ended without acceptance, including where the order filled elsewhere.

NonOfferReasonor
assignment_id string · nullable optional

The id of the assignment this offer became, starting with match_geo_aware_workforce_orchestrator_assignment_: the record POST /v1/allocation/offers/{offer_id}/accept returned. null until the offer is accepted.

made_at string · date-time optional

When the offer was made, as an RFC 3339 timestamp in UTC. Offers list in this order.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENToffer.declined#

An offer was declined or expired

We re-rank against supply as it now is, and widen the radius where the pool is exhausted.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Offer required

One offer to one worker, in one round of the cascade.

13 fields of Offer
id string required

The offer's identifier. It starts with match_geo_aware_workforce_orchestrator_offer_ and never changes; pass it as {offer_id} to GET /v1/allocation/offers/{offer_id} and to its /accept and /decline calls.

object always "offer" required

Always offer. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this offer is for, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order and its ranking.

worker_id string required

The worker this offer went to, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. One offer names one worker.

status string required
madeaccepteddeclinedexpiredwithdrawn
round integer · minimum 1 required

The cascade round this offer belongs to.

rank integer · minimum 1 optional

Where the worker stood in the round's ranking when this offer was made, 1 being the top.

expires_at string · date-time required

An offer that is not answered by this time is treated as a decline.

closed_reason one of optional

Set where the offer ended without acceptance, including where the order filled elsewhere.

NonOfferReasonor
assignment_id string · nullable optional

The id of the assignment this offer became, starting with match_geo_aware_workforce_orchestrator_assignment_: the record POST /v1/allocation/offers/{offer_id}/accept returned. null until the offer is accepted.

made_at string · date-time optional

When the offer was made, as an RFC 3339 timestamp in UTC. Offers list in this order.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTassignment.accepted#

An offer was accepted and an assignment exists

The work order is reserved to this assignment. No other cascade can allocate it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Assignment required

One worker, one work order, and the evidence collected against it.

17 fields of Assignment
id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTassignment.arrived#

A worker arrived at the work location

You get this only when both signals are present: geofence entry and the start passcode.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Assignment required

One worker, one work order, and the evidence collected against it.

17 fields of Assignment
id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTassignment.completed#

An assignment completed

Passcode, dwell duration and telemetry continuity all agreed. The proof of service is sealed and arrives on its own event.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Assignment required

One worker, one work order, and the evidence collected against it.

17 fields of Assignment
id string required

The assignment's identifier. It starts with match_geo_aware_workforce_orchestrator_assignment_ and never changes; pass it as {assignment_id} to GET /v1/allocation/assignments/{assignment_id} and its arrival, completion, cancel and proof calls.

object always "assignment" required

Always assignment. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

work_order_id string required

The id of the work order this assignment fills, as POST /v1/allocation/work_orders returned it, starting with match_geo_aware_workforce_orchestrator_work_order_. Pass it as {work_order_id} to read the order.

worker_id string required

The worker doing the work, as their sub_ subject reference: the subject_ref you chose for them at POST /v1/identity/consent_tokens. Every arrival and completion signal on this record is about this one person.

offer_id string optional

The id of the accepted offer this assignment came from, as listed at GET /v1/allocation/work_orders/{work_order_id}/offers, starting with match_geo_aware_workforce_orchestrator_offer_. Absent when you created it directly for work agreed elsewhere.

status string required

Work that has started resolves through completion or through exception. It is never cancelled, so the evidence of what happened survives.

accepteden_routearrivedin_progresscompletedexceptioncancelled
geofence_entry one of optional

The worker's entry into the geofence around the work location: when, where and the radius, one of the two signals arrival rests on. Null until arrival is confirmed.

GeofenceEntryor
start_passcode_verified_at string · date-time · nullable optional

When the start passcode the requester gave the worker on site was verified, as an RFC 3339 timestamp in UTC: the second of the two arrival signals. Null until arrival is confirmed.

completion_passcode_verified_at string · date-time · nullable optional

When the completion passcode was verified, as an RFC 3339 timestamp in UTC: one of the three signals completion rests on. Null until you confirm completion.

dwell one of optional

How long the worker was at the location, as you sent it on completion: when it started, when it ended and the whole seconds between. null until you confirm completion.

DwellRecordor
telemetry_continuity one of optional

Whether the heartbeat stream held across the working window, as you sent it on completion: continuous or not, how many gaps, and the longest in seconds. null until you confirm completion.

TelemetryContinuityor
proof_of_service_id string · nullable optional

The id of the sealed proof for this assignment, starting with match_geo_aware_workforce_orchestrator_proof_, as GET /v1/allocation/assignments/{assignment_id}/proof returns it. null until the bundle is sealed.

exception_id string · nullable optional

The id of the exception raised on this assignment, starting with match_geo_aware_workforce_orchestrator_exception_, as listed at GET /v1/allocation/exceptions. null unless the signals disagreed and a person was asked.

cancelled_reason string · nullable optional

Why the assignment was cancelled: the reason sent with the cancellation, or worker_removed_from_cascade when ANCHOR or RAIL removed the worker. null unless status is cancelled.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTassignment.exception_raised#

A completion could not be verified

The signals disagreed. A person has to decide, and nothing settles against this assignment until they do.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data AssignmentException required

A completion whose signals did not agree, waiting for a person. Nothing settles against an assignment while one of these is open.

9 fields of AssignmentException
id string required

The exception's identifier. It starts with match_geo_aware_workforce_orchestrator_exception_ and never changes; pass it as exception_id at POST /v1/allocation/exceptions/{exception_id}/adjudicate, or as starting_after to page past it.

object always "assignment_exception" required

Always assignment_exception. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The assignment whose completion is in question, by its id from POST /v1/allocation/assignments or POST /v1/allocation/offers/{offer_id}/accept, prefix match_geo_aware_workforce_orchestrator_assignment_. Nothing settles against it while open.

status string required
openupheldrejected
conflicting_signals array of object required

Which signals disagreed, named rather than summarised as a failure.

2 fields
signal string required

Which signal disagreed: geofence_entry or start_passcode for arrival; completion_passcode, dwell_duration or telemetry_continuity for completion. Name it in accepted_signals at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

geofence_entrystart_passcodecompletion_passcodedwell_durationtelemetry_continuity
detail string required

What this signal said, in plain words, and where it parted from the others. Read every one before you decide: the conflict is stated for you, never resolved by a default.

decision one of optional

The ruling once a person has made one: the outcome, who decided, when, their reason and the signals they accepted, or null while the exception is open. It's never edited; a later disagreement is a new decision.

Decisionor
raised_at string · date-time optional

When the exception was raised, as an RFC 3339 timestamp in UTC. Nothing has settled against the assignment since this moment, and nothing will until a person decides.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTproof_of_service.sealed#

A proof of service was sealed

This is the event settlement runs from. You get evidence, never an amount.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data ProofOfService required

The sealed evidence that work happened, and the object Droomwork Pay settles against. It carries no amount, because MATCH computes no money and moves none.

11 fields of ProofOfService
id string required

The proof's identifier. It starts with match_geo_aware_workforce_orchestrator_proof_ and never changes; it matches proof_of_service_id on the assignment, so you can tie a sealed bundle back to the work it proves.

object always "proof_of_service" required

Always proof_of_service. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

assignment_id string required

The id of the assignment this proof was sealed for, starting with match_geo_aware_workforce_orchestrator_assignment_. Pass it to GET /v1/allocation/assignments/{assignment_id} to read the signals as they were collected.

work_order_id string optional

The work order the assignment filled, by its id from POST /v1/allocation/work_orders, starting match_geo_aware_workforce_orchestrator_work_order_. Use it to match the proof to the order when you reconcile what settled, without reading the assignment.

worker_id string optional

The worker the proof is for, as the sub_ subject reference you chose for them and first sent as subject_ref at POST /v1/identity/consent_tokens. It is the worker_id on the assignment this proof was sealed for.

signals array of string required

Never fewer than two. One signal cannot establish that work occurred, so a bundle resting on one is not a bundle.

established_by string optional

How the proof was established: signals_agreed when the collected signals agreed on their own, adjudication when a person upheld the completion at POST /v1/allocation/exceptions/{exception_id}/adjudicate.

signals_agreedadjudication
seal Seal required

The cryptographic seal over the bundle as it stood. Anything added afterwards is a new bundle, so a sealed proof cannot be edited into agreement with a later claim.

4 fields of Seal
algorithm string required

The algorithm behind digest, such as sha256. Keep it with digest and key_reference: together they say exactly how this bundle was sealed.

digest string required

The seal itself: the digest over the bundle as sealed, prefixed with its algorithm, such as sha256:9f2c1e0043a1b8. Any change to the bundle gives a different digest.

sealed_at string · date-time required

When the bundle was sealed, as an RFC 3339 timestamp in UTC. Nothing added after this moment is inside the seal.

key_reference string optional

Which signing key sealed it, so a seal stays verifiable across a key rotation.

settlement_event_id string optional

The id of the proof_of_service.sealed event that carried this proof to Droomwork Pay, the event settlement runs from. It starts with evt_; read it at GET /v1/allocation/events/{event_id}.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTwork_order.unfilled#

A work order went unfilled

You get this with a specific reason when the cascade is exhausted or the work order expires. The gap also reaches FLOW as sourcing demand.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data SupplyGap required

An unfilled order, declared rather than left quiet, and emitted to FLOW as sourcing demand.

9 fields of SupplyGap
object always "supply_gap" required

Always supply_gap. Tells you which kind of record you are looking at, so one handler can read any response.

work_order_id string required

The unfilled order, by its id from POST /v1/allocation/work_orders, starting match_geo_aware_workforce_orchestrator_work_order_. Read it at GET /v1/allocation/work_orders/{work_order_id} for the occupation, location, window and constraints.

reason string required

Why nobody took the work. Specific, because a gap without a reason cannot be acted on.

cascade_exhaustedexpiredno_eligible_supplyall_offers_declined
occupation_code string required

The occupation the order asked for, as its taxonomy code such as ng-7412. Filter the list on it to see where one trade keeps going unfilled.

zone one of optional

The operational zone the order's location falls in, with its id and name, or null when it falls in none. Pass the id as zone to filter the list to it.

Zoneor
eligible_considered integer · minimum 0 optional

How many workers the gates let through before the cascade ran out.

offers_made integer · minimum 0 optional

How many offers went out before the gap was declared, from 0 up. Read it beside eligible_considered: many eligible and few offers is a different problem from nobody eligible at all.

emitted_to_flow boolean optional

Whether the sourcing demand event was accepted by FLOW.

declared_at string · date-time required

When the order was declared unfilled, as an RFC 3339 timestamp in UTC: the moment the cascade ran out or the order expired, not the end of a reporting period.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTworker.removed_from_cascade#

A worker was removed from every live cascade

You get this when a worker's ANCHOR verification is revoked or their RAIL legal standing lapses. It arrives within 60 seconds of the originating event, and any unstarted assignment is already cancelled by then.

If you miss this event, a revocation can be reconciled afterwards. A lapse of legal standing cannot.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data CascadeRemoval required

A worker removed from every live cascade because their identity was revoked or their legal standing lapsed. The deadline is 60 seconds from the originating event.

8 fields of CascadeRemoval
object always "cascade_removal" required

Always cascade_removal. Tells you which kind of record you are looking at, so one handler can read any response.

worker_id string required

The worker removed, as the sub_ subject reference you chose and sent as subject_ref at ANCHOR's POST /v1/identity/consent_tokens. Every offer to them is already withdrawn and every unstarted assignment cancelled.

source string required

Where the removal came from: anchor when the worker's Passport was revoked (the passport.revoked event), rail when their engagement lapsed or was terminated (engagement.lapsed or engagement.terminated). reason says which.

anchorrail
reason string optional

What triggered the removal: passport_revoked when ANCHOR revoked the worker's Passport, engagement_lapsed when their RAIL engagement stopped being compliant, engagement_terminated when it ended. source says which of the two it came from.

passport_revokedengagement_lapsedengagement_terminated
removed_at string · date-time required

When the worker was out of every live cascade, as an RFC 3339 timestamp in UTC. At most 60 seconds after the originating event; latency_ms gives the exact gap.

latency_ms integer · minimum 0 optional

Time from the originating event to removal.

offers_withdrawn integer · minimum 0 required

How many offers made to this worker were withdrawn by the removal, from 0 up. Each one now shows withdrawn as its status.

assignments_cancelled integer · minimum 0 optional

Unstarted assignments only. Work already in progress resolves through exception.

What you answer

Return 200 within 5 seconds. Anything else is retried.

RUN 8 events Reference →

EVENTrun.simulated#

A run was simulated

You get this when a run has been simulated. It carries the totals the run would produce.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Run required
14 fields of Run
id string required

The run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.

object always "payroll_run" required

Always payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

You move a run between states with endpoints, not by setting this field.

draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelled
run_type string required

Fixed at creation and immutable afterwards.

regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlement
period string required

The pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.

payee_count integer · minimum 0 optional

How many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

totals RunTotals optional

Each figure is the sum of its payslip lines, never a percentage of an aggregate.

5 fields of RunTotals
gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

total_deductions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_contributions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payslip_count integer · minimum 0 optional

How many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.

prepared_by string · nullable optional

Who prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.

approved_by string · nullable optional

Never the same actor as prepared_by above the maker-checker threshold.

created_at string · date-time required

When the record was created, as an RFC 3339 timestamp in UTC.

executed_at string · date-time · nullable optional

When the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrun.approved#

A run was approved

You get this when a run is approved and ready for you to execute.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Run required
14 fields of Run
id string required

The run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.

object always "payroll_run" required

Always payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

You move a run between states with endpoints, not by setting this field.

draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelled
run_type string required

Fixed at creation and immutable afterwards.

regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlement
period string required

The pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.

payee_count integer · minimum 0 optional

How many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

totals RunTotals optional

Each figure is the sum of its payslip lines, never a percentage of an aggregate.

5 fields of RunTotals
gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

total_deductions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_contributions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payslip_count integer · minimum 0 optional

How many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.

prepared_by string · nullable optional

Who prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.

approved_by string · nullable optional

Never the same actor as prepared_by above the maker-checker threshold.

created_at string · date-time required

When the record was created, as an RFC 3339 timestamp in UTC.

executed_at string · date-time · nullable optional

When the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrun.completed#

A run completed and every payslip was written

You get this when every payslip on a run has been written. Fetch the instruction set now.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Run required
14 fields of Run
id string required

The run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.

object always "payroll_run" required

Always payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

You move a run between states with endpoints, not by setting this field.

draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelled
run_type string required

Fixed at creation and immutable afterwards.

regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlement
period string required

The pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.

payee_count integer · minimum 0 optional

How many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

totals RunTotals optional

Each figure is the sum of its payslip lines, never a percentage of an aggregate.

5 fields of RunTotals
gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

total_deductions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_contributions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payslip_count integer · minimum 0 optional

How many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.

prepared_by string · nullable optional

Who prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.

approved_by string · nullable optional

Never the same actor as prepared_by above the maker-checker threshold.

created_at string · date-time required

When the record was created, as an RFC 3339 timestamp in UTC.

executed_at string · date-time · nullable optional

When the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrun.partially_settled#

Some payouts for this run failed to settle

You get this when some payouts for a run failed to settle. The run's totals don't change.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Run required
14 fields of Run
id string required

The run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.

object always "payroll_run" required

Always payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

You move a run between states with endpoints, not by setting this field.

draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelled
run_type string required

Fixed at creation and immutable afterwards.

regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlement
period string required

The pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.

payee_count integer · minimum 0 optional

How many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

totals RunTotals optional

Each figure is the sum of its payslip lines, never a percentage of an aggregate.

5 fields of RunTotals
gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

total_deductions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_contributions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payslip_count integer · minimum 0 optional

How many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.

prepared_by string · nullable optional

Who prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.

approved_by string · nullable optional

Never the same actor as prepared_by above the maker-checker threshold.

created_at string · date-time required

When the record was created, as an RFC 3339 timestamp in UTC.

executed_at string · date-time · nullable optional

When the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrun.failed#

A run failed, with the reason

You get this when a run fails. The reason is on the run record.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Run required
14 fields of Run
id string required

The run's identifier. It starts with run_enterprise_, is assigned when you create the run at POST /v1/payroll/runs and never changes; pass it as run_id wherever a call names this run.

object always "payroll_run" required

Always payroll_run. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

You move a run between states with endpoints, not by setting this field.

draftsimulatedpending_approvalapprovedexecutingcompletedpartially_settledfailedcancelled
run_type string required

Fixed at creation and immutable afterwards.

regularoff_cyclebonusthirteenth_monthcorrectionfinal_settlement
period string required

The pay period this run covers, as year and month in YYYY-MM form, for example 2026-09.

payee_count integer · minimum 0 optional

How many payees are on this run, one for each roster entry it covers. A whole number, 0 or more.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

totals RunTotals optional

Each figure is the sum of its payslip lines, never a percentage of an aggregate.

5 fields of RunTotals
gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

total_deductions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_contributions Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payslip_count integer · minimum 0 optional

How many payslips these totals are summed from. On a simulation, how many the run would produce; nothing is written.

prepared_by string · nullable optional

Who prepared this run, as their user identifier. Above the configured maker-checker threshold the same person can't approve it, so their approve call is refused.

approved_by string · nullable optional

Never the same actor as prepared_by above the maker-checker threshold.

created_at string · date-time required

When the record was created, as an RFC 3339 timestamp in UTC.

executed_at string · date-time · nullable optional

When the run was executed, as an RFC 3339 timestamp in UTC. null until it has been.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrun_finding.raised#

Anomaly Shield raised a finding

You get this when Anomaly Shield raises a finding on a run. A finding of severity p0 blocks approval until you resolve it.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data RunFinding required
13 fields of RunFinding
id string required

The finding's identifier, as it reads on each finding from GET /v1/payroll/runs/{run_id}/findings. It never changes; pass it as finding_id in the path when you retrieve or resolve the finding.

object always "run_finding" required

Always run_finding. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

run_id string required

The id of the run this finding was raised on, starting with run_enterprise_, as returned by POST /v1/payroll/runs. A p0 finding blocks that run's approval and execution until you resolve it.

severity string required

Only p0 blocks approval and execution.

p0p1p2
status string required
openresolved
code string required

A stable code naming what was found, for example net_pay_variance_above_threshold. Match on this in your own handling; detail is for people.

detail string required

A sentence for a person saying what was found and why it matters, for example that a payee has no verified destination. Don't match on it; use code.

subject_id string · nullable optional

Who the finding is about, when it concerns one payee: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. null when it concerns the run as a whole.

resolved_by string · nullable optional

Who resolved the finding, as their user identifier, kept permanently with the reason code. null while it's still open.

reason_code string · nullable optional

The reason code sent when the finding was resolved, for example no_payee_destination. null while it's still open.

resolved_at string · date-time · nullable optional

When the finding was resolved, as an RFC 3339 timestamp in UTC. null while it's still open.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpayslip.issued#

A payslip was delivered to a worker

You get this when a payslip has been delivered to a worker on a channel.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Payslip required
12 fields of Payslip
id string required

The payslip's identifier, starting with run_enterprise_payslip_. It's written when the run executes and first reaches you on GET /v1/payroll/payslips; it never changes, so pass it as payslip_id wherever a call names this payslip.

object always "payslip" required

Always payslip. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

run_id string required

The id of the run that wrote this payslip, starting with run_enterprise_, as returned by POST /v1/payroll/runs. Pass it as run_id on GET /v1/payroll/payslips to list every payslip from the same run.

subject_id string optional

Who this payslip is for: their subject identifier, starting with sub_, as you sent it on their roster entry at POST /v1/payroll/roster_entries. The same value is on their pay profile.

period string optional

The pay period this payslip covers, as year and month, for example 2026-09. It matches the period of the run that wrote it.

gross Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

net Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

lines array of PayslipLine required

Every line on this payslip, each with its own calculation trace. Read kind to tell earnings from deductions, employer contributions and information lines.

7 fields of PayslipLine
id string required

The line's identifier, as it reads on each entry in lines on the payslip from GET /v1/payroll/payslips/{payslip_id}. Quote it when you ask about one figure on this payslip.

object always "payslip_line" required

Always payslip_line. Tells you which kind of record you are looking at, so one handler can read any response.

code string required

Which pay element this line is, as a code such as pension_employee. Use it to match lines across payslips and against the explanation.

kind string required
earningdeductionemployer_contributioninformation
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

trace CalculationTrace required

The formula, the inputs, the intermediate values and the pack version behind one line. Every payslip line carries one, and any historical line replays to the kobo from it.

6 fields of CalculationTrace
formula string required

The calculation behind this line, written as its named inputs and operators, for example pensionable_earnings x pension_employee_rate. Each name appears in inputs.

inputs array of object required

The values the formula was evaluated with, each named as it appears in formula. An amount is Money in whole minor units and a rate is an exact fraction, so you can replay the line.

exact Rate required

An exact fraction of two whole numbers. Never a decimal, and it carries no currency.

2 fields of Rate
numerator integer · int64 required

The top of the fraction, a whole number. 8 over 100 is eight percent.

denominator integer · int64 · minimum 1 required

The bottom of the fraction, a whole number of 1 or more. Never zero, so every rate can be evaluated.

rounding Rounding required
3 fields of Rounding
scale string required

The unit rounded to, always minor_unit: the kobo for NGN, so a rounded figure is always a whole number of minor units.

minor_unit
mode string required

How a value between two minor units is settled: half_up takes a half up, half_down takes it down, half_even takes it to the even neighbour, ceiling always rounds up, floor always down, truncate drops the fraction. The rule pack sets it for each line; there's no default.

half_uphalf_evenhalf_downceilingfloortruncate
point string required

Where rounding was applied: per_line rounds each line on its own, per_total rounds once on the total. The rule pack sets it for each line, and the two can differ by real money.

per_lineper_total
result Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

rule_pack RulePackReference required
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

rule_pack RulePackReference optional
3 fields of RulePackReference
pack_id string required

The family of rules the pack belongs to, such as ng-payroll. It's pinned when you create the run at POST /v1/payroll/runs and, with version, names exactly which rates, bands and thresholds were applied.

version string required

The pack version that was applied, in the form 2026.08.1. A version never changes once active, so the same version always means the same rules.

content_hash string required

A digest of the pack's content, prefixed sha256:. It proves which exact rules produced the figure, so an edited pack can never pass as the one you ran under.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTinstruction_set.emitted#

The statutory instruction set is ready for REMIT

You get this when a run's statutory instruction set is ready for REMIT to take in.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data InstructionSet required

What is owed to whom for one run. This shape is owned jointly by RUN and REMIT. RUN row 19 emits it and REMIT row 1 ingests it, from RUN and from external payroll alike. It lives here rather than in either module document so the two cannot drift, which is the failure that would otherwise surface at M3 with RUN already accepted and paid. Totals reconcile to run totals to the kobo, because they sum payslip lines rather than recompute from an aggregate. This is the object REMIT ingests.

11 fields of InstructionSet
id string required

The instruction set's identifier, which never changes: read it at GET /v1/payroll/runs/{run_id}/instruction_set when Droomwork ran the payroll, and send the set on unchanged to POST /v1/remittance/instruction_sets. Quote it when you ask about the set.

object always "instruction_set" required

Always instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

run_id string required

The payroll run this set is owed for, by its id from POST /v1/payroll/runs, which starts with run_enterprise_. The set's totals reconcile to that run's totals to the kobo.

period string optional

The pay period the amounts are owed for, as year and month, for example 2026-09.

paye array of object required

Grouped by the State IRS of the employee's residence, not the employer's location.

5 fields
authority_id string required

The State Internal Revenue Service this PAYE is owed to, by its id from GET /v1/remittance/authorities, which starts with remit_authority_rail_obligation_. Use it wherever a call names the authority.

jurisdiction string required

The State Internal Revenue Service this line is grouped under, named in full, for example Rivers State Internal Revenue Service. Read it to label the line; authority_id names the authority in a call.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payee_count integer optional

How many employees this line's PAYE covers. Optional; when it is present, check it against your own headcount for that state.

reconciliation_reference string required

The reference that identifies this line when you reconcile it, for example paye-2026-09-rivers. Keep it with your own records so you can trace the line later.

pension array of object required

Grouped by administrator and retirement savings account number.

6 fields
administrator_id string required

The Pension Fund Administrator these contributions are owed to, by its id from GET /v1/remittance/authorities with kind=pension_administrator, which starts with remit_authority_rail_obligation_. Use it wherever a call names the administrator.

administrator_name string optional

The administrator's name in full, so you can label the line without a second call. Optional; administrator_id is what names the administrator in a call.

employee_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this line when you reconcile it. Keep it with your own records so you can trace the line later.

levies array of object required

NHF, NSITF, ITF and NHIA totals, each with a reconciliation reference.

3 fields
code string required

Which levy this line is for: nhf the National Housing Fund, nsitf the Nigeria Social Insurance Trust Fund, itf the Industrial Training Fund, nhia the National Health Insurance Authority. One line per levy.

nhfnsitfitfnhia
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this levy line when you reconcile it. Keep it with your own records so you can trace the line later.

totals object required

The set's grand total, which its lines sum to. From a Droomwork run it reconciles to the run's totals to the kobo.

1 field
total Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

REMIT 8 events Reference →

EVENTinstruction_set.validated#

An instruction set finished validation

You get the validation report. Any discrepancy blocks acceptance until you resolve it or override it with a reason code.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data IngestedInstructionSet required
13 fields of IngestedInstructionSet
id string required

The set's identifier, starting remit_authority_rail_instruction_, as listed at GET /v1/remittance/instruction_sets. It never changes; pass it as instruction_set_id to retrieve, accept, reject or remit the set.

object always "ingested_instruction_set" required

Always ingested_instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Nothing is committed until accepted.

receivedvalidatingpending_acceptanceacceptedrejected
source string required

Where the set came from. Droomwork payroll or your own, by API or by file. Everything downstream behaves identically whichever it was.

runexternal_apiexternal_file
period string required

The month the set covers, as YYYY-MM, such as 2026-09. Filter the list by it with the period query.

run_id string · nullable optional

The Droomwork payroll run the set came from, by its id from POST /v1/payroll/runs or GET /v1/payroll/runs, starting run_enterprise_. Present when source is run; null when the set came from your own payroll.

instructions InstructionSet optional

What is owed to whom for one run. This shape is owned jointly by RUN and REMIT. RUN row 19 emits it and REMIT row 1 ingests it, from RUN and from external payroll alike. It lives here rather than in either module document so the two cannot drift, which is the failure that would otherwise surface at M3 with RUN already accepted and paid. Totals reconcile to run totals to the kobo, because they sum payslip lines rather than recompute from an aggregate. This is the object REMIT ingests.

11 fields of InstructionSet
id string required

The instruction set's identifier, which never changes: read it at GET /v1/payroll/runs/{run_id}/instruction_set when Droomwork ran the payroll, and send the set on unchanged to POST /v1/remittance/instruction_sets. Quote it when you ask about the set.

object always "instruction_set" required

Always instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

run_id string required

The payroll run this set is owed for, by its id from POST /v1/payroll/runs, which starts with run_enterprise_. The set's totals reconcile to that run's totals to the kobo.

period string optional

The pay period the amounts are owed for, as year and month, for example 2026-09.

paye array of object required

Grouped by the State IRS of the employee's residence, not the employer's location.

5 fields
authority_id string required

The State Internal Revenue Service this PAYE is owed to, by its id from GET /v1/remittance/authorities, which starts with remit_authority_rail_obligation_. Use it wherever a call names the authority.

jurisdiction string required

The State Internal Revenue Service this line is grouped under, named in full, for example Rivers State Internal Revenue Service. Read it to label the line; authority_id names the authority in a call.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payee_count integer optional

How many employees this line's PAYE covers. Optional; when it is present, check it against your own headcount for that state.

reconciliation_reference string required

The reference that identifies this line when you reconcile it, for example paye-2026-09-rivers. Keep it with your own records so you can trace the line later.

pension array of object required

Grouped by administrator and retirement savings account number.

6 fields
administrator_id string required

The Pension Fund Administrator these contributions are owed to, by its id from GET /v1/remittance/authorities with kind=pension_administrator, which starts with remit_authority_rail_obligation_. Use it wherever a call names the administrator.

administrator_name string optional

The administrator's name in full, so you can label the line without a second call. Optional; administrator_id is what names the administrator in a call.

employee_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this line when you reconcile it. Keep it with your own records so you can trace the line later.

levies array of object required

NHF, NSITF, ITF and NHIA totals, each with a reconciliation reference.

3 fields
code string required

Which levy this line is for: nhf the National Housing Fund, nsitf the Nigeria Social Insurance Trust Fund, itf the Industrial Training Fund, nhia the National Health Insurance Authority. One line per levy.

nhfnsitfitfnhia
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this levy line when you reconcile it. Keep it with your own records so you can trace the line later.

totals object required

The set's grand total, which its lines sum to. From a Droomwork run it reconciles to the run's totals to the kobo.

1 field
total Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

validation_report ValidationReport required

The result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.

4 fields of ValidationReport
entry_count integer · minimum 0 required

How many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.

error_count integer · minimum 0 required

How many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.

warning_count integer · minimum 0 required

How many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.

entries array of object optional

One item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.

4 fields
index integer · minimum 1 required

The row in a file, or the line in an instruction set.

severity string required

error means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.

errorwarning
code string required

Why the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.

detail string required

What was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.

jurisdiction_pack object optional

The jurisdiction pack the set is pinned to, by identifier and version. Every line was recomputed against it, so its rules are the ones the validation report reflects.

2 fields
pack_id string optional

Which jurisdiction pack the set is pinned to, such as ng-paye. We publish the packs and you never send this; with version, it names exactly which rules every line was checked against, and it stays with the set so you can show which rules applied.

version string optional

The pack version the set was checked against, such as 2026.08.1. It's pinned, so a later pack release doesn't change what this set was judged by.

accepted_by string · nullable optional

Who accepted the set: the actor that called accept. null until the set is accepted.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTinstruction_set.accepted#

An instruction set was accepted and is ready to remit

Sent once you have resolved or overridden every discrepancy. You can create a remittance from this instruction set now.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data IngestedInstructionSet required
13 fields of IngestedInstructionSet
id string required

The set's identifier, starting remit_authority_rail_instruction_, as listed at GET /v1/remittance/instruction_sets. It never changes; pass it as instruction_set_id to retrieve, accept, reject or remit the set.

object always "ingested_instruction_set" required

Always ingested_instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required

Nothing is committed until accepted.

receivedvalidatingpending_acceptanceacceptedrejected
source string required

Where the set came from. Droomwork payroll or your own, by API or by file. Everything downstream behaves identically whichever it was.

runexternal_apiexternal_file
period string required

The month the set covers, as YYYY-MM, such as 2026-09. Filter the list by it with the period query.

run_id string · nullable optional

The Droomwork payroll run the set came from, by its id from POST /v1/payroll/runs or GET /v1/payroll/runs, starting run_enterprise_. Present when source is run; null when the set came from your own payroll.

instructions InstructionSet optional

What is owed to whom for one run. This shape is owned jointly by RUN and REMIT. RUN row 19 emits it and REMIT row 1 ingests it, from RUN and from external payroll alike. It lives here rather than in either module document so the two cannot drift, which is the failure that would otherwise surface at M3 with RUN already accepted and paid. Totals reconcile to run totals to the kobo, because they sum payslip lines rather than recompute from an aggregate. This is the object REMIT ingests.

11 fields of InstructionSet
id string required

The instruction set's identifier, which never changes: read it at GET /v1/payroll/runs/{run_id}/instruction_set when Droomwork ran the payroll, and send the set on unchanged to POST /v1/remittance/instruction_sets. Quote it when you ask about the set.

object always "instruction_set" required

Always instruction_set. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

run_id string required

The payroll run this set is owed for, by its id from POST /v1/payroll/runs, which starts with run_enterprise_. The set's totals reconcile to that run's totals to the kobo.

period string optional

The pay period the amounts are owed for, as year and month, for example 2026-09.

paye array of object required

Grouped by the State IRS of the employee's residence, not the employer's location.

5 fields
authority_id string required

The State Internal Revenue Service this PAYE is owed to, by its id from GET /v1/remittance/authorities, which starts with remit_authority_rail_obligation_. Use it wherever a call names the authority.

jurisdiction string required

The State Internal Revenue Service this line is grouped under, named in full, for example Rivers State Internal Revenue Service. Read it to label the line; authority_id names the authority in a call.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

payee_count integer optional

How many employees this line's PAYE covers. Optional; when it is present, check it against your own headcount for that state.

reconciliation_reference string required

The reference that identifies this line when you reconcile it, for example paye-2026-09-rivers. Keep it with your own records so you can trace the line later.

pension array of object required

Grouped by administrator and retirement savings account number.

6 fields
administrator_id string required

The Pension Fund Administrator these contributions are owed to, by its id from GET /v1/remittance/authorities with kind=pension_administrator, which starts with remit_authority_rail_obligation_. Use it wherever a call names the administrator.

administrator_name string optional

The administrator's name in full, so you can label the line without a second call. Optional; administrator_id is what names the administrator in a call.

employee_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

employer_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this line when you reconcile it. Keep it with your own records so you can trace the line later.

levies array of object required

NHF, NSITF, ITF and NHIA totals, each with a reconciliation reference.

3 fields
code string required

Which levy this line is for: nhf the National Housing Fund, nsitf the Nigeria Social Insurance Trust Fund, itf the Industrial Training Fund, nhia the National Health Insurance Authority. One line per levy.

nhfnsitfitfnhia
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

reconciliation_reference string required

The reference that identifies this levy line when you reconcile it. Keep it with your own records so you can trace the line later.

totals object required

The set's grand total, which its lines sum to. From a Droomwork run it reconciles to the run's totals to the kobo.

1 field
total Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

validation_report ValidationReport required

The result of checking a submission before anything is committed. Names every failing entry rather than stopping at the first, so one submission tells you everything to fix. The same shape wherever the platform ingests, validates and reports, which is every bulk intake in every module.

4 fields of ValidationReport
entry_count integer · minimum 0 required

How many entries the submission held: every row in the file or line in the instruction set, whether it passed or not.

error_count integer · minimum 0 required

How many entries failed with an error. Each is named in entries with its position and why, so one submission tells you everything to fix.

warning_count integer · minimum 0 required

How many entries passed with a warning, each named in entries. A warning does not fail the entry; it points at something to confirm before you go ahead.

entries array of object optional

One item per error or warning found, with the row or line it sits on, its severity, a code and what was wrong. Empty or absent when every entry passed clean.

4 fields
index integer · minimum 1 required

The row in a file, or the line in an instruction set.

severity string required

error means the entry failed and must be fixed. warning means it passed, but points at something you should confirm before you go ahead.

errorwarning
code string required

Why the entry was flagged, as a code you can branch on. detail says the same in words and may change; the code does not.

detail string required

What was wrong with the entry, in plain words. Show it beside the row; it may change, so branch on code.

jurisdiction_pack object optional

The jurisdiction pack the set is pinned to, by identifier and version. Every line was recomputed against it, so its rules are the ones the validation report reflects.

2 fields
pack_id string optional

Which jurisdiction pack the set is pinned to, such as ng-paye. We publish the packs and you never send this; with version, it names exactly which rules every line was checked against, and it stays with the set so you can show which rules applied.

version string optional

The pack version the set was checked against, such as 2026.08.1. It's pinned, so a later pack release doesn't change what this set was judged by.

accepted_by string · nullable optional

Who accepted the set: the actor that called accept. null until the set is accepted.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTobligation.due_soon#

A filing deadline is approaching

Sent at the lead time configured for the authority. Carries the penalty formula so you can see what a late filing costs.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Obligation required
12 fields of Obligation
id string required

The obligation's identifier, assigned when you accept an instruction set or record one at POST /v1/remittance/obligations, starting remit_authority_rail_obligation_. It never changes; pass it as obligation_id to retrieve it.

object always "obligation" required

Always obligation. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

authority_id string required

The authority the amount is owed to: its id from GET /v1/remittance/authorities, starting remit_authority_rail_obligation_. GET /v1/remittance/authorities/{authority_id} gives its name, tier and capabilities; filter obligations by it.

authority_name string optional

The authority's name as you'd write it, such as Rivers State Internal Revenue Service. For display; authority_id is what you filter and match on.

period string required

The month the liability arises from, as YYYY-MM, such as 2026-09. One obligation covers one authority for one period; filter the list by it with the period query.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

settled Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

balance Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

deadline string · date required

Derived from the pack calendar rules, never entered by hand.

receipt_ids array of string optional

The id of each vaulted receipt that settles against this obligation, empty until one is vaulted. Retrieve a receipt at GET /v1/remittance/receipts/{receipt_id} for the authority's reference and the document.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTremittance.leg_completed#

One authority leg completed and its receipt is vaulted

Sent only once the receipt for this leg is in your vault. A filing reported as successful does not trigger this on its own.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Leg required
11 fields of Leg
id string required

The leg's identifier, first returned in legs on the remittance from POST /v1/remittance/remittances; it never changes. Pass it as leg_id, with the remittance's id as remittance_id, to retrieve or retry the leg.

object always "remittance_leg" required

Always remittance_leg. Tells you which kind of record you are looking at, so one handler can read any response.

remittance_id string optional

The id of the remittance this leg belongs to, from POST /v1/remittance/remittances; it starts with remit_authority_rail_remittance_. Every call that names the leg names it too, at /v1/remittance/remittances/{remittance_id}/legs/{leg_id}.

authority_id string required

The id of the authority this leg pays, as listed at GET /v1/remittance/authorities; it starts with remit_authority_rail_obligation_. Look it up at GET /v1/remittance/authorities/{authority_id} for its name, tier and capabilities.

authority_name string optional

The authority's name as you'd write it, such as Rivers State Internal Revenue Service. For display; authority_id is what you match on.

tier string optional

How the filing reaches the authority. Readable so you know what to expect on timing. Never something to code against, since all three present the same contract.

direct_apiportal_automationassisted_manual
status string required

A leg reaches completed only once its receipt is vaulted. quarantined means a receipt arrived but disagreed with the leg.

pendingexecutingawaiting_receiptcompletedquarantinedfailed
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

receipt_id string · nullable optional

The id of the receipt that is this leg's proof of payment, once vaulted; it starts with remit_authority_rail_receipt_ and you retrieve it at GET /v1/remittance/receipts/{receipt_id}. null until a receipt is vaulted.

attempts integer · minimum 0 optional

How many times filing this leg with its authority has been tried, retries included. 0 before the first try; read it with last_fault before you retry.

last_fault string · nullable optional

Why the latest attempt failed, in words for a person to read, such as The register did not answer within the timeout. null when there is no fault to report; read it before you retry.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTremittance.leg_failed#

One authority leg failed

The other legs of the remittance are unaffected. Retry this one on its own.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Leg required
11 fields of Leg
id string required

The leg's identifier, first returned in legs on the remittance from POST /v1/remittance/remittances; it never changes. Pass it as leg_id, with the remittance's id as remittance_id, to retrieve or retry the leg.

object always "remittance_leg" required

Always remittance_leg. Tells you which kind of record you are looking at, so one handler can read any response.

remittance_id string optional

The id of the remittance this leg belongs to, from POST /v1/remittance/remittances; it starts with remit_authority_rail_remittance_. Every call that names the leg names it too, at /v1/remittance/remittances/{remittance_id}/legs/{leg_id}.

authority_id string required

The id of the authority this leg pays, as listed at GET /v1/remittance/authorities; it starts with remit_authority_rail_obligation_. Look it up at GET /v1/remittance/authorities/{authority_id} for its name, tier and capabilities.

authority_name string optional

The authority's name as you'd write it, such as Rivers State Internal Revenue Service. For display; authority_id is what you match on.

tier string optional

How the filing reaches the authority. Readable so you know what to expect on timing. Never something to code against, since all three present the same contract.

direct_apiportal_automationassisted_manual
status string required

A leg reaches completed only once its receipt is vaulted. quarantined means a receipt arrived but disagreed with the leg.

pendingexecutingawaiting_receiptcompletedquarantinedfailed
amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

receipt_id string · nullable optional

The id of the receipt that is this leg's proof of payment, once vaulted; it starts with remit_authority_rail_receipt_ and you retrieve it at GET /v1/remittance/receipts/{receipt_id}. null until a receipt is vaulted.

attempts integer · minimum 0 optional

How many times filing this leg with its authority has been tried, retries included. 0 before the first try; read it with last_fault before you retry.

last_fault string · nullable optional

Why the latest attempt failed, in words for a person to read, such as The register did not answer within the timeout. null when there is no fault to report; read it before you retry.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTreceipt.vaulted#

A receipt was hash chained into the vault

This receipt is your proof of payment for the leg it belongs to.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Receipt required
17 fields of Receipt
id string required

The receipt's identifier, starting with remit_authority_rail_receipt_; it never changes. It is the receipt_id on the leg it proves and in receipt_ids on its obligation; pass it as receipt_id to GET /v1/remittance/receipts/{receipt_id}.

object always "remittance_receipt" required

Always remittance_receipt. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

leg_id string · nullable optional

The id of the leg this receipt proves paid, from legs on a remittance or GET /v1/remittance/remittances/{remittance_id}/legs; vaulting the receipt takes that leg to completed. null when the receipt is not tied to a leg.

obligation_id string · nullable optional

The id of the obligation this receipt settles against, from GET /v1/remittance/obligations, starting with remit_authority_rail_obligation_; the receipt also appears in its receipt_ids. null when not tied to an obligation.

authority_id string required

The id of the authority that issued this receipt, from GET /v1/remittance/authorities, starting with remit_authority_rail_obligation_; filter the list by it. It must agree with the leg's authority_id, or the receipt is quarantined.

period string required

The month the payment covers, as YYYY-MM, such as 2026-09; filter the list by it with the period query. It must agree with the leg's period, or the receipt is quarantined.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

authority_reference string optional

The reference the authority itself issued.

evidence_class string required

The same classes apply whichever tier filed. An assisted manual filing carries a receipt reference, a document scan and an operator attestation under dual control, which is the same evidence class as an API filing.

authority_api_receiptportal_receiptassisted_manual_attested
document_url string · uri · nullable optional

Where to fetch the receipt document the authority issued, as a URL. Present for an assisted manual filing, where you supplied the scan; null when there is no document.

hash string required

This receipt position in the append only chain.

previous_hash string required

The hash of the receipt vaulted just before this one. Each receipt carries the last one's hash, so you can walk the chain back and confirm nothing was changed or removed.

trace_references array of string optional

The payroll trace references this receipt covers.

attested_by array of string optional

Two actors for an assisted manual filing, under dual control.

vaulted_at string · date-time optional

When the receipt was hash chained into the vault, as an RFC 3339 timestamp in UTC. From this moment it never changes, and the leg it belongs to can reach completed.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTreceipt.quarantined#

A receipt disagreed with its leg and was quarantined

The parsed amount, period or authority did not match the leg. The leg does not complete, and the receipt is held for your review rather than discarded.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Receipt required
17 fields of Receipt
id string required

The receipt's identifier, starting with remit_authority_rail_receipt_; it never changes. It is the receipt_id on the leg it proves and in receipt_ids on its obligation; pass it as receipt_id to GET /v1/remittance/receipts/{receipt_id}.

object always "remittance_receipt" required

Always remittance_receipt. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

leg_id string · nullable optional

The id of the leg this receipt proves paid, from legs on a remittance or GET /v1/remittance/remittances/{remittance_id}/legs; vaulting the receipt takes that leg to completed. null when the receipt is not tied to a leg.

obligation_id string · nullable optional

The id of the obligation this receipt settles against, from GET /v1/remittance/obligations, starting with remit_authority_rail_obligation_; the receipt also appears in its receipt_ids. null when not tied to an obligation.

authority_id string required

The id of the authority that issued this receipt, from GET /v1/remittance/authorities, starting with remit_authority_rail_obligation_; filter the list by it. It must agree with the leg's authority_id, or the receipt is quarantined.

period string required

The month the payment covers, as YYYY-MM, such as 2026-09; filter the list by it with the period query. It must agree with the leg's period, or the receipt is quarantined.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

authority_reference string optional

The reference the authority itself issued.

evidence_class string required

The same classes apply whichever tier filed. An assisted manual filing carries a receipt reference, a document scan and an operator attestation under dual control, which is the same evidence class as an API filing.

authority_api_receiptportal_receiptassisted_manual_attested
document_url string · uri · nullable optional

Where to fetch the receipt document the authority issued, as a URL. Present for an assisted manual filing, where you supplied the scan; null when there is no document.

hash string required

This receipt position in the append only chain.

previous_hash string required

The hash of the receipt vaulted just before this one. Each receipt carries the last one's hash, so you can walk the chain back and confirm nothing was changed or removed.

trace_references array of string optional

The payroll trace references this receipt covers.

attested_by array of string optional

Two actors for an assisted manual filing, under dual control.

vaulted_at string · date-time optional

When the receipt was hash chained into the vault, as an RFC 3339 timestamp in UTC. From this moment it never changes, and the leg it belongs to can reach completed.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTreturn.approved#

An employer return was approved by a named person

A named person approved the return. Submission is the next step, and you trigger it as a separate action.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data EmployerReturn required
13 fields of EmployerReturn
id string required

The return's identifier, first given by POST /v1/remittance/returns and on every record of GET /v1/remittance/returns. It starts with remit_authority_rail_return_ and never changes; pass it as return_id to retrieve, approve or submit.

object always "employer_return" required

Always employer_return. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

kind string required
monthly_payeannual_payepension_schedulenhf_schedulensitf_returnitf_returnnhia_schedule
status string required
draftpending_approvalapprovedsubmittedacceptedrejected
authority_id string required

The authority this return is filed with: the id of an authority from GET /v1/remittance/authorities, which starts with remit_authority_rail_obligation_. It is the authority_id you sent when you generated the return.

period string required

The period the return covers, as you named it: YYYY-MM for a month, YYYY for a whole year. For example 2026-09.

generated_from array of string optional

The vaulted receipts this return was generated from.

totals object optional

What the return declares, summed from the vaulted receipts it was generated from: how many employees and the total amount.

2 fields
employee_count integer optional

How many employees the return declares for the period.

total Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

approved_by string · nullable optional

A named person. Required before submission.

submitted_at string · date-time · nullable optional

When the return was filed with the authority, as an RFC 3339 timestamp in UTC. null until you submit it.

authority_reference string · nullable optional

The reference the authority gave your filing, such as paye-2026-09-rivers. null until the authority has issued one.

What you answer

Return 200 within 5 seconds. Anything else is retried.

ROUTE 8 events Reference →

EVENTinstruction.quarantined#

An instruction failed its integrity check

Your instruction was tampered with or is a duplicate. We hold it as evidence. It is never discarded.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data PayoutInstruction required
15 fields of PayoutInstruction
id string required

The instruction's identifier, starting with route_multi_rail_payout_; it never changes. You get it from POST /v1/payouts/instructions when you submit, or from GET /v1/payouts/instructions, and pass it as instruction_id in a path.

object always "payout_instruction" required

Always payout_instruction. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

status string required
receivedquarantinedfunding_pendingplannedexecutingsettledpartially_settledfailed
source string required

Which of these signed the amounts. You never supply an amount yourself.

runremit
run_id string · nullable optional

The payroll run these payouts came from, when source is run: the run's id from POST /v1/payroll/runs, starting with run_enterprise_, as you sent it in run_id. null when the instruction came from a remittance.

remittance_id string · nullable optional

The remittance these payouts came from, when source is remit: the remittance's id from POST /v1/remittance/remittances, starting with remit_authority_rail_remittance_, as you sent it. null when they came from a payroll run.

total Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

line_count integer · minimum 1 required

How many lines the instruction carries, at least 1. Each line names one payee, so this is how many people it pays, whatever splits they carry.

idempotency_key string optional

Derived from the instruction itself, so the same payment always produces the same key. This is what makes a retry after a timeout safe.

signature_verified boolean optional

true when the signature from payroll or remittance checked out, false when it didn't. A failed signature quarantines the instruction with quarantine_reason signature_invalid.

quarantine_reason string · nullable optional

Why the instruction was held, null unless status is quarantined: signature_invalid, duplicate_instruction (already submitted) or integrity_mismatch (a line failed its check). A held instruction is kept as evidence and never executed.

signature_invalidduplicate_instructionintegrity_mismatchnull
route_plan_id string · nullable optional

The plan that will carry this instruction: the id, starting with route_multi_rail_route_plan_, of the plan made by POST /v1/payouts/instructions/{instruction_id}/plan. null until you plan it, and execute is refused while it is null.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTfunding.insufficient#

The funding account cannot cover the instruction

We attempted nothing. No part of your file executed.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data FundingCheck required
7 fields of FundingCheck
object always "funding_check" required

Always funding_check. Tells you which kind of record you are looking at, so one handler can read any response.

instruction_id string required

The instruction this check covers: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. It is the instruction_id you passed in the path, returned so the record stands on its own.

sufficient boolean required

true when your account covers required_amount in full, false when it doesn't. On false nothing executes: no part of the instruction is paid, and shortfall says what's missing.

required_amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

available_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

shortfall Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

checked_at string · date-time optional

When the check ran, as an RFC 3339 timestamp in UTC. A balance moves, so check again if time has passed before you plan.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpayout.attempted#

An attempt was made on a rail

You get one of these per attempt, including each failover.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Payout required
13 fields of Payout
id string required

The payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.

object always "payout" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

instruction_id string required

The instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.

subject_id string required

Who is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

status string required

unknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.

5 fields of Destination
bank_code string required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.

account_number_masked string required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.

validated boolean optional

true once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.

attempt_count integer · minimum 0 optional

How many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.

unknown_since string · date-time · nullable optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpayout.unknown#

A payout timed out and its outcome is not yet known

Do not treat this as a failure and do not resend. We are polling the provider. A resolution follows.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Payout required
13 fields of Payout
id string required

The payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.

object always "payout" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

instruction_id string required

The instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.

subject_id string required

Who is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

status string required

unknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.

5 fields of Destination
bank_code string required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.

account_number_masked string required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.

validated boolean optional

true once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.

attempt_count integer · minimum 0 optional

How many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.

unknown_since string · date-time · nullable optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpayout.settled#

A payout settled and was independently validated

The amount, destination and reference all agree with your originating instruction.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Payout required
13 fields of Payout
id string required

The payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.

object always "payout" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

instruction_id string required

The instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.

subject_id string required

Who is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

status string required

unknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.

5 fields of Destination
bank_code string required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.

account_number_masked string required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.

validated boolean optional

true once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.

attempt_count integer · minimum 0 optional

How many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.

unknown_since string · date-time · nullable optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTpayout.failed#

A payout failed on every rail available to it

Your originating run moves to partially settled. Its totals do not change.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Payout required
13 fields of Payout
id string required

The payout's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/payouts, and pass it as payout_id to retrieve or resolve the payout or list its attempts.

object always "payout" required

Always payout. Tells you which kind of record you are looking at, so one handler can read any response.

livemode boolean required

Which realm this record is in: false is the sandbox, true is live. Read it before you act on anything.

mocked boolean required

Where these figures came from: true when they were mocked, false when they were computed for real. Not the inverse of livemode: each record carries the answer that was true for it.

instruction_id string required

The instruction this payout belongs to: its id, starting with route_multi_rail_payout_, from POST /v1/payouts/instructions. One instruction yields one payout per payee, or one per sub-leg where a line splits across destinations.

subject_id string required

Who is being paid: their subject identifier, starting with sub_, exactly as you sent subject_id on the instruction line at POST /v1/payouts/instructions. Where the money goes is resolved from their records, not from anything you send.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

status string required

unknown is a real state. A payout that timed out resolves by asking the provider, never by assuming failure. Treating unknown as failed is how someone gets paid twice.

pendingattemptingunknownsettledfailed
destination Destination optional

Resolved from the payee's identity and engagement records, never supplied on the instruction. The account number is masked.

5 fields of Destination
bank_code string required

The code that identifies the payee's bank to the rail. Read bank_name for the name a person would recognise.

bank_name string optional

The payee's bank, under the name a person would recognise. bank_code is the same bank as the rail identifies it.

account_number_masked string required

The payee's account number with all but its last four digits hidden, such as ******4821. The full number is never returned.

account_name string required

The name the account is held in, from the payee's own records rather than anything you sent. validated says whether the bank has confirmed it.

validated boolean optional

true once an account validation came back valid for this account. false when it hasn't been checked, or the outcome was a name mismatch, not found or unresolved.

attempt_count integer · minimum 0 optional

How many attempts have been made against this payout so far, from 0 before the first. Each one is listed at GET /v1/payouts/payouts/{payout_id}/attempts with its rail and outcome.

unknown_since string · date-time · nullable optional

Set while the provider is being polled. Cleared on resolution.

settlement_id string · nullable optional

The settlement that closed this payout, once there is one: the id, starting with route_multi_rail_settlement_, of a settlement at GET /v1/payouts/settlements. null until a settlement, paid or failed, has been recorded.

created_at string · date-time optional

When the record was created, as an RFC 3339 timestamp in UTC.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTrail.degraded#

A rail is degraded and traffic is moving to fallbacks

Your payouts that do not need this rail are unaffected.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data Rail required
7 fields of Rail
id string required

The rail's identifier, starting with route_multi_rail_payout_; it never changes. You get it from GET /v1/payouts/rails, and it is the primary_rail_id and fallback_rail_ids on a route plan and the rail_id on an attempt.

object always "rail" required

Always rail. Tells you which kind of record you are looking at, so one handler can read any response.

name string required

The rail's name as people know it, such as NIP instant transfer. For display; name the rail by id in a call.

kind string required

What sort of rail it is: instant_transfer (such as NIP), direct_bank (a direct bank integration), wallet or mobile_money. All four sit behind the same interface.

instant_transferdirect_bankwalletmobile_money
health string required
healthydegradedunavailable
capabilities object optional

What the rail can do: the largest amount it will carry, whether it can confirm an account name before paying, and how soon it settles.

3 fields
max_amount Money optional
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

supports_name_enquiry boolean optional

true when the rail can return the name on an account before any money moves, which is what a name mismatch check needs. false when it can't.

settlement_window string optional

How soon money sent on this rail lands, in words such as near instant, same day or next business day.

credentials_held boolean optional

Whether your credentials for this rail are in place: true when your payouts can go through it, false when it can't carry your money yet. GET /v1/payouts/readiness names the rails still without them.

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTreconciliation.exception_raised#

A break was found between a provider statement and the ledger

It is raised for an operator to answer. Nothing is resolved automatically.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type string required

What happened, as module.resource.past_tense_verb, for example run.payslip.calculated. Pick your handler on it; data takes the shape this type promises.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data ReconciliationException required
9 fields of ReconciliationException
id string required

The exception's identifier, as id on each break listed at GET /v1/payouts/reconciliations/{reconciliation_id}/exceptions. It never changes; use it to follow one break from open to resolved.

object always "reconciliation_exception" required

Always reconciliation_exception. Tells you which kind of record you are looking at, so one handler can read any response.

reconciliation_id string optional

The day's reconciliation this break belongs to: the id of a reconciliation from GET /v1/payouts/reconciliations, starting route_multi_rail_settlement_, the same reconciliation_id you listed the exceptions under.

code string required

Which kind of break: missing_in_ledger (the provider shows a payment we don't), missing_at_provider (we show one the provider doesn't), amount_mismatch (the amounts differ) or duplicate_at_provider (paid twice). detail says it in words.

missing_in_ledgermissing_at_provideramount_mismatchduplicate_at_provider
detail string required

What the break is, in words, written for a person to read. Match on code, not on this.

amount Money required
2 fields of Money
amount integer · int64 required

A whole number of the currency's minor unit, as defined by the ISO 4217 exponent. For NGN that is kobo, so 1234567 is twelve thousand three hundred and forty five naira and sixty seven kobo. A fractional value is refused with the code invalid_money_amount. Call GET /v1/currencies for the exponent of any currency. Never divide by a hundred by hand.

currency string required

ISO 4217 code.

status string required

open until somebody answers the break, resolved once they have, with resolved_by and reason_code saying who and why. A break is never resolved automatically.

openresolved
resolved_by string · nullable optional

Who resolved the break, as the identifier of the person who did. null while the break is still open: it is never resolved automatically, so this is always somebody's decision.

reason_code string · nullable optional

The reason recorded against the break, as a code you can match on, such as no_payee_destination. null while none has been recorded.

What you answer

Return 200 within 5 seconds. Anything else is retried.

IAM 2 events Reference →

EVENTiam.support_access.recorded#

Droomwork looked at your data

We send this when one of our staff reads your organisation's data, at the moment it happens. You're told; you don't have to go and check.

You get what GET /v1/support_access shows and nothing more: when, what was read, and what it was for. Not who. If you need to take something further, quote the access identifier from that endpoint.

The record lives at GET /v1/support_access; this delivery is a notice of it. If your endpoint is down, the read still happened and you can still read it there. Support can still answer your question whether or not this delivery arrives.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type always "iam.support_access.recorded" required

Always iam.support_access.recorded. Route on it: one of our staff read your organisation's data, and this is the notice.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data object required

When the read happened, what was read and what it was for, as at, action and purpose. It never names who; the record with its id is at GET /v1/support_access.

3 fields
at string · date-time required

When the read happened, as an RFC 3339 timestamp in UTC. The same moment GET /v1/support_access shows as at.

action string required

What was read, as the method and the path.

purpose one of optional

Why access was granted, from a fixed list: support_ticket, incident, data_request, billing_query, onboarding or unspecified. null for a look recorded before purposes were kept.

SupportAccessPurposeor

What you answer

Return 200 within 5 seconds. Anything else is retried.

EVENTiam.support_action.taken#

Droomwork acted inside your organisation

We send this when one of our staff changes something in your organisation, at the moment it happens.

This is a separate event from iam.support_access.recorded, which tells you somebody looked. Subscribing to one doesn't subscribe you to the other.

The record names Droomwork, not you. Your audit trail never shows one of your own people as having done this: the actor on every one of these is the staff member, alongside your organisation.

It's never an approval. Approving a payroll run moves salaries and approving a remittance discharges an obligation to a regulator; those stay yours whatever a support case needs. Our staff can create, correct and reproduce.

You get when, what was done, and what it was for, in the same vocabulary GET /v1/support_access uses. The record lives there; this delivery is a notice of it. If your endpoint is down, the action still happened and you can still read it there.

What you receive

id string required

The event's identifier, starting with evt_, the same on a webhook delivery and on the module's events list, such as GET /v1/payroll/events. It never changes: a redelivery carries the same id, so you can recognise an event you have already handled.

type always "iam.support_action.taken" required

Always iam.support_action.taken: one of our staff changed something in your organisation. A look is a separate event, iam.support_access.recorded; subscribing to one doesn't subscribe you to the other.

schema_version integer · minimum 1 required

The version of the shape data takes for this type, starting at 1. A change to the shape raises it, so check it before you read data.

org_id string required

The organisation the event belongs to, by its id, which starts with org_: the one POST /v1/registrations gave you and GET /v1/me returns. You only ever receive events for your own organisation.

sequence integer · minimum 0 required

Per organisation and per stream. It is how a consumer tells a replay from a new event, and it is what the delivery guarantee rests on.

occurred_at string · date-time required

When the event happened, as an RFC 3339 timestamp in UTC. Not when it was delivered: a redelivery carries the original value.

request_id string optional

The request that caused this event, where one did: the Droomwork-Request-Id that request returned, starting with req_. Absent for an event a schedule raised, such as an engagement lapsing on its end date.

livemode boolean required

Which realm the event happened in. False is the sandbox.

mocked boolean required

Whether a mock produced this fact, rather than an engine computing it. Recorded on the event when it was appended and never worked out afterwards from the realm: the two answers agree while every module is on its mock and part on the day the first engine ships. See ADR-0011.

source string required

Which part of Droomwork is the authority for this fact: anchor (identity), proof (credentials), rail (engagements), flow (sourcing), match (allocation), run (payroll), remit (remittance), route (payouts), gateway (the API's front door), iam (accounts and API keys), ledger (the books), registry (rule packs), delivery (webhooks and messages), documents (rendered payslips and instruments) or intelligence (AI decisions). Read the fact from there when it matters; your own copy is never the authority.

anchorproofrailflowmatchrunremitroutegatewayiamledgerregistrydeliverydocumentsintelligence
data object required

When it happened, what was done as the method and the path, and what it was for, never who. The record lives at GET /v1/support_access, in the same vocabulary; this delivery is a notice of it.

3 fields
at string · date-time required

When the action was taken, as an RFC 3339 timestamp in UTC.

action string required

What was done, as the method and the path.

purpose one of optional

What the action was for, from a fixed list, never free text: support_ticket, incident, data_request, billing_query, onboarding or unspecified. null when no purpose was recorded.

SupportAccessPurposeor

What you answer

Return 200 within 5 seconds. Anything else is retried.