# API reference

> Every endpoint of the Open ADMS API, grouped by path, with what each one does: generated at build time from the API’s own OpenAPI document.

Source: /docs/reference/api/

The API is the only way into an installation: the field app, the back office and the viewer
portal all reach the server through it, over HTTPS. This page lists its 233
endpoints, grouped by the first part of their path. Each line's description is the summary the
API itself states, so this page is generated, never edited by hand.

> [!NOTE]
> **The full contract**
>
> The complete OpenAPI 3.1.0 document, with every request and response schema, is
> published beside these pages: [openapi.json](/docs/openapi.json) (Open ADMS API,
> version 0.0.0, Apache-2.0).
> Load it into any OpenAPI viewer or client generator.

## How to call it

- **Over HTTPS only.** Plain HTTP is refused at the API’s address. Locally the API answers at
  `https://api.adms.localhost`.
- **A person** (admin, manager/analyst, read-only viewer) signs in with `POST /sessions`; the
  session is held in `__Host-` cookies, and every call that is not a `GET` carries the CSRF value
  from `GET /sessions/csrf`.
- **A phone** proves itself by signing a challenge with its device key and calls with a
  short-lived bearer token; a monitor signs in on it beside that token.
- **Browsers** may call it only from the exact origins on the installation’s CORS allow-list.
- Every body is checked against the shared schema; unknown fields are refused, errors are plain
  codes, and a caller over its rate limit is answered `429`.

See [Security](/docs/security.md) for the sign-in rules and [Architecture](/docs/reference/architecture.md) for
how the API sits beside the worker and the database.

## /assignments

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/assignments` | The dispatch board searched: a person’s or a phone’s assignments (or a project’s), from one day to another (62 days at most), standing or withdrawn, a page at a time by day and then in the order written; each with what the board warns of beside it (a second assignment for the person or the phone that day, a day outside the project’s active window, a closed project, unit rate pay items named with no unit rate work, a site not yet active or closed). A warning never stops anything. |
| `PATCH` | `/assignments/{assignmentId}` | An assignment’s work, site or phone is changed; only what is stated changes, and a list is stated whole. Its person, day and project never change. Refused for a withdrawn one and for one made on a phone. |
| `POST` | `/assignments/{assignmentId}/day-approvals` | A worker’s day on the time clock is approved, and so locked: nothing of its entries or activity lines changes until it is unlocked. A phone’s entry that arrives after is still taken, and the hours read say so. Refused for one’s own day and for a day already approved. |
| `POST` | `/assignments/{assignmentId}/day-unlocks` | An approved day is unlocked, with a note saying why; the approval is kept and says who unlocked it and when, and the day is approved again as a new approval. Refused for a day that isn’t approved. |
| `POST` | `/assignments/{assignmentId}/reinstatements` | A withdrawn assignment is put back on the board, as it was; its note is cleared (the audit keeps it). Refused for one that stands, one made on a phone, an archived person and a phone out of service. |
| `POST` | `/assignments/{assignmentId}/withdrawals` | An assignment is withdrawn from the board, from this moment, with a note if the dispatcher writes one. It is kept, and so is everything recorded under it. Refused for one already withdrawn and for one made on a phone. |

## /audit-records

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/audit-records` | What the audit log holds of one record, by its table and id, newest first, a page at a time. Read by the area it belongs to: a ticket’s records with view review, setup with view setup, people, phones and the board with view dispatch; sign-in’s and the firm’s security records by an admin alone. A person’s phone number is shown to an admin alone. |

## /brand

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/brand` | The name the firm calls its ADMS and the Open ADMS release it is built on, for the pages that brand themselves. Anybody may ask. |

## /certifications

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/certifications/{certificationId}` | One certification whole: its boxes (those taken off kept, marked), helpers, photographs’ records, QC mark and its own flags, where it came from and any revocation. Never the driver’s phone or licence number. |
| `PATCH` | `/certifications/{certificationId}` | A certification is corrected in place: its prime and subs (typed by name, placed under the prime as at certifying, a name already placed elsewhere on the project saved as a separate contractor with a warning), what was certified (picked or typed) or the crew type, the driver, the tag, the representatives and notes; only what is stated changes, and `null` empties a detail that may be empty. With the prime changed, the subs it had are placed again under the new prime by name. Every ticket that scanned it is queued, with the tickets that read them; one locked as invoiced is left as it is. Refused for a prime that isn’t one of the project’s and a second tier with no first. |
| `POST` | `/certifications/{certificationId}/boxes` | A box is added to a container certification after its last, as the sheet has it: a feature (picked or typed), a shape with its own measurements in whole inches, and whether it adds or subtracts. The capacity is worked out again from the boxes, never sent, and every ticket that scanned the certification is queued where it changed; one locked as invoiced is left as it is. Refused for a crew, which is never measured, and for measurements that give no capacity. |
| `PATCH` | `/certifications/{certificationId}/boxes/{position}` | A mistake on one of a container certification’s boxes is corrected in place (box 0 is the primary): its measurements in whole inches, its shape (with the measurements that shape holds, and no others), whether it adds or subtracts, its feature (picked or typed), or the box taken off or put back; only what is stated changes, and the capacity is worked out again from the boxes, never sent. Every ticket that scanned the certification is queued where the capacity changed; one locked as invoiced is left as it is. Refused for a box the certification doesn’t have, for the primary taken off, for measurements that give no capacity and for a measurement the box’s shape doesn’t hold. |
| `POST` | `/certifications/{certificationId}/revocations` | A certification is revoked from a day, in its project’s time zone, with a reason; it keeps the day, the reason and the admin. Nothing else on it changes. Every ticket that scanned it is queued; one locked as invoiced is left as it is. |
| `PUT` | `/certifications/{certificationId}/signatures-on-file` | Whether a certification’s signatures are on file is marked, yes or no. It changes nothing a ticket reads and queues nothing. |
| `POST` | `/certifications/{certificationId}/unrevocations` | A certification’s revocation is taken back, with a reason: its day, reason and admin are cleared, the audit log keeping them, and every ticket that scanned it is queued, flagged no more for it; one locked as invoiced is left as it is. Refused for a certification that isn’t revoked. |

## /clients

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/clients` | The firm’s clients whose name holds what was typed; none archived. |
| `POST` | `/clients` | Creates a client: the jurisdiction or agency paying for the work. |
| `PATCH` | `/clients/{clientId}` | Changes a client’s name, contact or notes; one left out stays as it is, and a contact or the notes are cleared with null. Audited with the value before. |

## /contractors

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/contractors` | The firm’s contractors whose name holds what was typed; none archived. |
| `POST` | `/contractors` | Creates a contractor record, firm-wide. Whoever adds it says its kind. |
| `PATCH` | `/contractors/{contractorId}` | Changes a contractor’s name, kind or contact; one left out stays as it is, and a contact is cleared with null. Audited with the value before. |

## /contracts

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/contracts/{contractId}` | Changes a contract’s name or the link to its signed copy (cleared with null); its prime stays as entered (a contract has one prime; another prime has a contract of its own). Audited with the value before. |

## /control-ranges

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/control-ranges/{controlRangeId}/archivings` | Takes off a control range entered by mistake. It is archived, never deleted. |

## /debris-types

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/debris-types` | Adds a debris type to the firm’s library, named as its contracts name the work. |

## /declaration-events

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/declaration-events` | The declaration events whose name or reference holds what was typed; none archived. |
| `POST` | `/declaration-events` | Creates a declaration event: the disaster and its reference as written, its reference type picked from the firm’s list. |
| `PATCH` | `/declaration-events/{declarationEventId}` | Changes a declaration event’s name, its reference as written or its reference type; one left out stays as it is. Audited with the value before. |

## /drop-off-logs

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/drop-off-logs/{dropOffLogId}` | A citizen drop-off log is corrected in place: its site, the resident’s name, where the debris came from, its notes and the answers to its form’s own fields; only what is stated changes. It is on no ticket, so nothing is queued. Refused for a site of another project and answers that don’t fit their version. |

## /field-events

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/field-events/{fieldEventId}` | One field event as recorded and corrected, with its status, its item lines and the tickets it carries (those taken off kept, marked), its photographs’ records (each with its view, when it was taken and when its file reached storage) and the scale receipt it names. Never where a file is kept. |
| `PATCH` | `/field-events/{fieldEventId}` | A field event’s captured details are corrected in place: only what is stated changes, and `null` empties a detail that may be empty. A corrected cert number finds its certification again, an ROE number its ROE, a debris type its form. Its ticket and the tickets that read it are queued. Never its number (a renumber), its kind, its workflow or its project. Refused for a ticket locked as invoiced, and for a detail its kind of field event doesn’t record. |
| `PUT` | `/field-events/{fieldEventId}/carried-tickets` | A ticket a load’s collection lists as carried is added, taken off (it stays, marked, and counts for nothing) or put back. Refused for a field event that isn’t a load’s collection and for a ticket locked as invoiced. |
| `PUT` | `/field-events/{fieldEventId}/item-lines/{itemNameId}` | A line of an itemised unit rate ticket is corrected: its count or hours stated, a line it didn’t have added, or the line taken off (`TAKEN_OFF`: it stays, marked, and counts for nothing); stating its quantity again puts it back. Refused for a field event that isn’t unit rate work and for a ticket locked as invoiced. |
| `PUT` | `/field-events/{fieldEventId}/number` | A field event is given another ticket number, or a split’s collection another suffix (left out, its suffix stays). The ticket it leaves and the one it joins are both queued. Nothing asks whether the work is real. Refused for the number it already has, a number another field event of its kind holds, a suffix on a kind that takes none and a ticket locked as invoiced. |
| `PATCH` | `/field-events/{fieldEventId}/waypoints/{position}` | A waypoint a collection’s monitor marked is corrected in place: when it was marked and its fix (`null` for none), only what is stated changing; or it is taken off (it stays, marked) or put back. Nothing is queued: no ticket’s conclusions read a waypoint. Refused for a waypoint the field event doesn’t have, a fix that can’t be one and a ticket locked as invoiced. |

## /firm-settings

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/firm-settings` | The firm’s own settings: how many hours a phone marked lost or retired keeps its key (or off), whether read-only viewers must use two-step sign-in, how many hours a monitor’s sign-in on a phone lasts, and the email address a reader of the firm’s reports writes to. |
| `PATCH` | `/firm-settings/brand-name` | Sets what the firm calls its ADMS: plain text of one to forty characters, shown on every screen and in what the server writes for the firm’s people and clients, beside the Open ADMS release it is built on. Audited. |
| `PATCH` | `/firm-settings/monitor-sessions` | An admin sets how many whole hours (1 to 24) a monitor’s sign-in on a phone lasts, online or made with no connection, and a session under the master passcode at most. It applies to sign-ins made from then on; a session already open keeps its end. Audited. |
| `PATCH` | `/firm-settings/phone-key` | Sets how many whole hours (1 to 8,760) a phone marked lost or retired keeps its key, counted from its last mark, before the worker revokes it; null switches it off. A phone revoked so may still sync and be turned in under the master passcode. Audited. |
| `PATCH` | `/firm-settings/report-contact` | Sets the email address printed on the last page of the firm’s reports, for a reader who wants to be added to their list; null prints none. Audited. |
| `PATCH` | `/firm-settings/viewer-two-step` | An admin sets whether read-only viewers must use two-step sign-in. Once it is required, a viewer with no authenticator sets one up before anything else, at their very next request. Turning it off clears nobody’s. Audited. |

## /flag-acceptances

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/flag-acceptances/{flagAcceptanceId}/withdrawals` | A flag’s acceptance is taken back, with the reviewer’s note: who and when are kept on it, and the flag stands again on the run it queues. Accepting the flag again is a new acceptance. Refused for an acceptance already taken back and a ticket locked as invoiced. |

## /flags

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/flags/{flagId}/acceptances` | A flag that stands is accepted, with a reason picked from the firm’s list and the reviewer’s own note. Refused for a flag the worker has cleared, one already accepted, a reason the firm doesn’t offer for accepting a flag and a ticket locked as invoiced. |

## /form-debris-types

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/form-debris-types/{formDebrisTypeId}` | Changes a debris type’s entry on a form: Reduced, its reduction method (cleared with null) or whether phones hide it; one left out stays as it is. One not Reduced on a haul-out form is saved with a warning. |

## /forms

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/forms/{formId}` | Changes a form’s name, "GPS required" (the ROE form’s) or what an incident offers the monitors (the incident form’s); one left out stays as it is. |
| `POST` | `/forms/{formId}/debris-types` | Adds a debris type from the firm’s library to a load or haul-out form. One not marked Reduced on a haul-out form is saved with a warning. |
| `POST` | `/forms/{formId}/definitions` | Saves the firm’s own fields on an ROE, survey, incident or drop-off log form as its next version. A version is never edited. |

## /health

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/health` | That the API answers. Anybody may ask. |

## /held-records

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/held-records` | What a phone sent that the server couldn’t save, held for a person, a page at a time, oldest first: by project (as sent) or by phone, waiting or resolved. Each exactly as sent, with what it was sent as, why it is held, the words its phone was told, the photographs kept with it and how it was resolved. |
| `POST` | `/held-records/{heldRecordId}/closings` | A held record is closed, never to be saved: nothing is made from it, and what was sent stays, with its photographs, marked with who closed it, when and why. Resolved once. |
| `POST` | `/held-records/{heldRecordId}/savings` | A held record is resolved by saving it as corrected: the record as it should be, in the sync’s own shape, under the id its phone made and of the kind it was sent as. It is written as the reviewer, as the phone’s record it was, with the photographs kept with it; its tickets are queued. Resolved once. Refused for a held record with no id, another id, another kind, a report (closed only), a number another record holds, what the database refuses and a ticket locked as invoiced. |

## /incidents

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/incidents/{incidentId}` | An incident is corrected in place: its type and sub-type, what happened, who was involved, where, the contractor, cert number and ROE number it names, and its notes; only what is stated changes. A corrected cert number finds its certification again, an ROE number its ROE. The answers to its form’s own fields are given whole, against the version they answer. It is on no ticket, so nothing is queued. Refused for answers that don’t fit their version. |

## /invoice-batches

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/invoice-batches/{invoiceBatchId}` | One invoice batch and what it holds: each lock in force that names it, with the ticket it locks. No amount: a batch is a name and its tickets. |
| `PATCH` | `/invoice-batches/{invoiceBatchId}` | An invoice batch is renamed: a name used once on its project, never blank. Its locks and tickets are as they were. |

## /invoice-locks

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/invoice-locks` | A ticket is locked as invoiced, in a batch of its project where one is named. Refused for a ticket that isn’t whole, COMPLETED and QC Approved, one already locked and a batch of another project. |
| `PUT` | `/invoice-locks/batch` | A locked ticket is put in an invoice batch of its project, moved to another or taken out of one. Refused for a ticket that isn’t locked and a batch of another project. |
| `POST` | `/invoice-locks/several` | Many tickets locked as invoiced at once, up to 200, each in the batch named where one is: in the order sent, each its own piece of work and each answered, locked or not with its reason and words (not QC Approved, not whole, flagged, already locked). One refused never stops the rest. |

## /invoice-unlocks

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/invoice-unlocks` | A ticket locked as invoiced is unlocked, and queued to be worked again as the setup now stands. Refused for a ticket that isn’t locked. |

## /lists

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/lists/{list}/values` | One of the firm’s lists, a page at a time, in its order: its values in service, or the archived ones apart, each with what its list asks beside its name (a field tag its project). |
| `POST` | `/lists/{list}/values` | Adds a value of the firm’s own to one of its lists, at the end, with a name and what its list asks beside it: a program type whether it requires ROEs, a kind of unit rate work the shape of its count (and, if it says so, that its photographs are taken by stage: before the work, its measurement and after), an item name its kind of work, a vehicle type what it certifies, an incident sub-type its incident type, a reason where it is offered. A field tag is added on its project. |
| `PATCH` | `/lists/{list}/values/{valueId}` | Renames a value of one of the firm’s lists, or a project’s field tag; a built-in value too. Nothing the system does follows a name. |
| `POST` | `/lists/{list}/values/{valueId}/archivings` | Archives a value of one of the firm’s lists, or a project’s field tag: off new picks, kept on old records. A built-in value is never archived. Each project whose rules pick it has its tickets not locked as invoiced queued. |
| `PATCH` | `/lists/{list}/values/{valueId}/settings` | Changes what a value of one of the firm’s lists states beside its name, where its list lets it change once added: a kind of unit rate work whether its photographs are taken by stage (before the work, its measurement and after it). Phones are shown it with their next lists; nothing is queued, since no rule reads it. |
| `POST` | `/lists/{list}/values/{valueId}/unarchivings` | Puts an archived list value back, as it was. Each project whose rules pick it has its tickets not locked as invoiced queued. |

## /map-versions

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/map-versions/{mapVersionId}` | One map version with its boundary and each zone’s shape, as they were loaded. |
| `POST` | `/map-versions/{mapVersionId}/esri-refreshes` | Refresh from Esri: loads a project’s map again from the layers a version fetched from Esri named, as its next version. The version itself never changes. The project’s tickets not locked as invoiced are queued. |
| `GET` | `/map-versions/{mapVersionId}/files/{file}` | A map version’s file as it was kept, or the copy of its zones’ own Esri layer: the bytes that were stored, under the type they were kept as, sent as a download and never as a page. |

## /master-passcode

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/master-passcode/versions` | An admin changes the firm’s master passcode, giving their own password again. A new version is kept, as a slow hash only; each phone’s check moves to it at its next sync. Refused for a passcode shorter or longer than the policy allows (8 and 128 characters as shipped), or known from breaches. |

## /password-changes

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/password-changes` | The signed-in person sets a new password, giving their current one. Their other sessions end. |

## /password-resets

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/password-resets` | Asks for a password reset to be sent to an email address. Anybody may ask; the answer is the same whether or not a person has that address. |
| `POST` | `/password-resets/completions` | Sets a new password with the code a reset message carried. The code is good once. |

## /pay-items

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/pay-items/{payItemId}` | Changes a pay item: its code, description, Unit of Measure, size band, crew-time conversion or capture settings; one left out stays as it is. The rules its "Pays when" wrote are renamed with it. The project’s tickets not locked as invoiced are queued. |
| `PUT` | `/pay-items/{payItemId}/crew-day-steps` | States a pay item’s crew day steps whole: hours from and to, both included, and the quantity they bill. One not named is taken off. Two that share an hour aren’t saved. The project’s tickets not locked as invoiced are queued. |
| `PUT` | `/pay-items/{payItemId}/pays-when` | Saves a pay item’s "Pays when": for each form it pays on, the criteria and the transaction # it adds. A rule is written and kept for each of the project’s contracts whose prime has a rate on it; the answer says which rules were added, changed, archived and left alone. |
| `POST` | `/pay-items/{payItemId}/pays-when/previews` | What saving a pay item’s "Pays when" would do, before it is saved: the record as it would be, its warnings and how many tickets it would queue (and pass by, locked as invoiced), or why it wouldn’t be saved. Nothing is kept. |
| `POST` | `/pay-items/{payItemId}/previews` | What a change to a pay item would do, before it is saved: the record as it would be, its warnings and how many tickets it would queue (and pass by, locked as invoiced), or why it wouldn’t be saved. Nothing is kept. |
| `PUT` | `/pay-items/{payItemId}/rates/{primeId}` | Sets a prime’s rate on a pay item: for the first time, changed (audited with the rate before), or put back. The project’s tickets not locked as invoiced are queued. |
| `POST` | `/pay-items/{payItemId}/rates/{primeId}/previews` | What a prime’s rate would do, before it is saved: the record as it would be, its warnings and how many tickets it would queue (and pass by, locked as invoiced), or why it wouldn’t be saved. Nothing is kept. |

## /payable-types

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/payable-types` | Adds a payable type to the firm’s list: the work a pay item prices. |

## /people

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/people` | The firm’s people, a page at a time, in the order they were added or by name: those in service, or the archived ones, of one role, or those whose name holds what is searched. A monitor’s phone number is in an admin’s answer only. |
| `POST` | `/people` | An admin creates an admin, a manager/analyst or a read-only viewer (a client user, a contractor user with its contractor, or an auditor). Never a monitor. The person has no password until an admin sets one. |
| `GET` | `/people/{userId}` | One person, as an admin or a manager/analyst reads them. A monitor’s phone number is in an admin’s answer only. |
| `PATCH` | `/people/{userId}` | An admin changes a person’s name, email address or phone number, their employer and its ID for them, and a read-only viewer’s kind, contractor and whether they see private citizens by name. Only what is stated changes; an address is cleared only on a monitor. A changed sign-in address is told to the old one where the firm has mail. |
| `POST` | `/people/{userId}/archivings` | An admin archives a person. Their sessions end and they are refused at once. The only admin can’t be archived. |
| `POST` | `/people/{userId}/passwords` | An admin sets a person’s password. Their sessions end, and they set one of their own at their next sign-in. |
| `PUT` | `/people/{userId}/permissions` | An admin states the permissions a manager/analyst holds from now on. One taken away refuses that person’s very next request. |
| `PUT` | `/people/{userId}/role` | An admin makes a manager/analyst an admin, or an admin a manager/analyst. Their very next request is asked as the new role. The only admin stays one; a monitor’s and a read-only viewer’s role never changes. |
| `POST` | `/people/{userId}/session-endings` | An admin ends every session a person has open. |
| `POST` | `/people/{userId}/two-step-clearings` | An admin clears another person’s authenticator. Their sessions end; it is a security event. |
| `POST` | `/people/{userId}/unarchivings` | An admin takes a person out of the archive. They are in service again with what they held, and sign in again; refused while somebody in service signs in with their email address. |
| `PUT` | `/people/{userId}/viewer-projects` | An admin states the projects a read-only viewer sees from now on. One not named is taken away. |

## /phones

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/phones` | The firm’s phones, a page at a time, in the order they were enrolled: those in service, or those taken out of it. Each says which version of the master passcode’s check it holds, when it was marked lost or retired and why, and which revoked its key. |
| `PATCH` | `/phones/{deviceId}` | A phone is given another name, as the dispatch board calls it. Nothing else of it changes. |
| `POST` | `/phones/{deviceId}/challenges` | Makes a challenge for a phone to sign with its device key. Nobody is signed in; one is made for any id, and the server keeps nothing of it. It may be sent back signed for the policy’s time, once. |
| `POST` | `/phones/{deviceId}/proofs` | A phone proves itself: it sends back the challenge it was made and its signature over it. The answer is a token for that phone, good for the policy’s minutes, which the calls of its sync carry as Authorization: Bearer. A challenge is good once. A proof that isn’t one is refused by one code, whatever the reason; a phone taken out of service still proves itself, one whose key its turn-in revoked does not. Whoever holds the master passcode may send it beside the signature: the master user is then at the phone, and its token opens what a proof by the key opens and counts as somebody signed in on the phone for what it sends. A phone whose key the firm’s time revoked proves itself only so, and its token opens only its sync, its photographs’ files, its reports, its turn-in and its read of itself; without the passcode it is told the passcode is needed. Each passcode taken is a security event; one that isn’t is refused by the proof’s one code, and counts as a wrong try as it does when a phone is enrolled. |
| `POST` | `/phones/{deviceId}/removals` | A phone is taken out of service, as lost or retired, from this moment or from a removal time stated by hand (never after now, never before it was enrolled), with a reason from the firm’s list where one is picked. A phone marked lost may then be marked retired, its removal time kept unless one is stated. Its record is kept; what it made later is received and flagged, never refused. Each mark starts the firm’s time again, after which the worker revokes its key. Refused for a phone already out of service otherwise, and for one whose key is revoked. |
| `POST` | `/phones/{deviceId}/returns` | A phone found again is put back in service, with a note saying why. Refused for a phone in service, and for one whose key is revoked: that one is wiped and enrolled anew. |
| `GET` | `/phones/{deviceId}/sessions` | A phone’s sessions, a page at a time, newest first: whose, when made, until when and whether ended, which way one opened under the master passcode was, and whether it came from a sign-in made on the phone offline. Never a token. |
| `GET` | `/phones/current` | The phone a proof’s token is of, as the server has it now: its name, whether it is in service, the version of the master passcode whose check it holds and the version in force. Open to a phone that has proved itself, by its token. |
| `POST` | `/phones/current/address-lookups` | The address of a GPS point, for a collection’s "Look up address": the street number and name our own address server finds there, or none with the reason (the map has no street there; the address server gave no answer in time). The monitor then types it, as with no connection. The phone asks the API and the API asks the address server, which no phone and no browser reaches. The point is sent in the body, never in the address of the request; neither the point nor the address is logged or kept. Nothing is written. Open to a proven phone with its monitor’s session token. |
| `GET` | `/phones/current/lists` | The lists a phone holds for its work, asked for at any time it has a connection: the assignment the phone takes now for the monitor signed in (the sign-in’s own rule, asked again) and that assignment’s lists, or neither with the reason. The lists are the blocks of numbers the phone holds, the forms’ debris types without those hidden on phones, the field tags, the reasons for a typed number, the unit rate pay items the assignment names, what its incidents and surveys pick from, and the setup version they are, which every field event the phone makes records. Never a rate, a rule, a transaction or a load’s pay item. Nothing is written but a new setup version, made when what the project’s phones are shown differs from the newest one. Open to a proven phone with its monitor’s session token. |
| `POST` | `/phones/current/login-list` | The login list and the master passcode’s check a phone holds for signing in with no connection, asked for at any time it has one. The phone says what it holds: the version of the master passcode whose check it has, and a mark for each monitor’s entry. The answer is the version in force, with its check where the phone holds another or none; and the login list of the project of the assignment the phone takes now for the monitor signed in: that monitor and every monitor in service with an assignment that stands on the project for its today or later, each with the mark of their entry, and the entry itself where the phone didn’t send that mark. An entry is a slow hash made for this phone alone, kept nowhere on the server; a password set anew makes its mark good no more. One call makes a bounded number of entries and says there are more. With no assignment there is no list and the phone keeps what it holds. Never a password or a hash the server checks a sign-in against. Nothing is written but which version of the master passcode’s check the phone was sent. Open to a proven phone with its monitor’s session token. |
| `GET` | `/phones/current/monitor` | The monitor signed in on the phone, as the server has them now, and how long their sign-in has left. Open to a proven phone with its monitor’s session token. |
| `POST` | `/phones/current/monitors` | A field monitor is created on an enrolled phone, under the firm’s master passcode: their name, their password and, if the firm keeps one, their phone number. The answer is the Monitor ID the system issued, which is their username. Open to a phone in service that has proved itself, by its token. A passcode that isn’t taken is refused by one code; wrong tries count as they do when a phone is enrolled. |
| `POST` | `/phones/current/offline-sign-ins` | A phone reports a sign-in made on it with no connection, once it has one: the monitor’s own password checked against its login list, or a session opened under the master passcode as the phone’s monitor or the temporary one. It says who, how, when by its own clock with the offset, the version of the master passcode whose check it held (under the passcode only), and its proof: an HMAC-SHA256 over what the sign-in says, keyed by the first slow hash of what was typed, which the server checks against the monitor’s offline seed or its own hash of the passcode in force. Taken, the answer is a session’s token for the x-adms-monitor-session header and how long it has left, counted from the sign-in by the firm’s hours (a temporary session also ends with the project’s day); any other session on the phone ends, and it is a security event. Refused by one code, and written as a security event the back office is shown, where the proof isn’t one, the password was set anew or the passcode changed since, the monitor is not in service, the session has already run out, or the server knows of a sign-in or sign-out on the phone after it: the phone keeps everything and its monitor signs in online. The same sign-in reported again by the phone gives a new token and ends the one before. Open to a phone in service that has proved itself, by its token. |
| `POST` | `/phones/current/passcode-sessions` | A session is opened on an enrolled phone under the firm’s master passcode, for a monitor in service: as the phone’s monitor, or the temporary session for a monitor picked from the roster. The answer is what a monitor’s own sign-in is answered (the session’s token for the x-adms-monitor-session header, how long it lasts, the assignment the phone takes or why none) and which way it was opened. The session is marked as the master passcode’s and is no sign-in of the monitor’s. Neither lasts longer than a monitor’s own sign-in; the temporary one also ends at the end of the day of the project whose assignment the phone takes, by that project’s time zone. Any other session on the phone ends. Open to a phone in service that has proved itself, by its token. A passcode that isn’t taken is refused by one code; wrong tries count as they do when a phone is enrolled. |
| `POST` | `/phones/current/passcode-uses` | A phone reports a use of the firm’s master passcode it logged with no connection: the phone passed to a monitor who has their own assignment, a session opened on it as the phone’s monitor or the temporary session for a monitor picked from the roster, its sign-in screen unlocked after too many wrong tries at it, or the passcode typed wrong. It says when by the phone’s own clock, with the clock’s offset, and for which monitor; an unlock and a wrong try name nobody. The id is the phone’s own: the same report sent again is answered as already saved and kept once. It is kept as the phone reports it, for a monitor archived since and from a phone out of service too, and is not saved only where it names somebody who is no monitor or its id is another record’s. The passcode itself is never sent: the phone checked it, and a wrong try reported here does not count toward the lock on the server’s own check. Each one saved is a security event at the phone’s own time: a phone switched and a use of the master passcode; a use that says which way a session was opened; a use for the unlock; or a failed try. A switch or a session saved ends what anybody else had open on that phone before it, by the phone’s clock. A setup, a site move and a switch to a monitor who has no assignment are reported as an unplanned assignment instead. Open to a phone that has proved itself, by its token, with somebody signed in on it: the monitor, by their session’s token in the x-adms-monitor-session header, or the master user, by the master passcode sent beside the phone’s proof. A phone taken out of service sends it too. |
| `GET` | `/phones/current/photos/{photoId}/files/{file}` | Where a photograph’s file stands: kept (nothing more to send), or how many of its bytes the server has taken, of a file how long, so the phone sends the rest from there (none, where it holds none). The photograph is named by its own id, and the file is the photograph itself or the depth file beside it; only the phone that took it may ask, once its record is saved. Open to a phone that has proved itself, by its token, with somebody signed in on it: the monitor, by their session’s token in the x-adms-monitor-session header, or the master user, by the master passcode sent beside the phone’s proof. A phone taken out of service sends it too. |
| `PATCH` | `/phones/current/photos/{photoId}/files/{file}` | A part of a photograph’s file, sent by the phone that took it, once its record is saved: the bytes in the body, the offset they go at and the whole file’s length in the query. Taken only where it goes on from the last part the server took, or starts the file; otherwise the answer says where to go on from. The file is at most the policy’s size and a part at most the policy’s; its first part is told to be an image by its own first bytes (JPEG, HEIC or HEIF, PNG, TIFF), or refused. When the last part arrives the server reads the whole file’s SHA-256: the one the phone gave in the photograph’s record (or the depth file’s) and the file is kept, once, where the server placed it, never overwritten, and its upload time written; any other, and the file is set aside on the server for a person, never kept as the photograph’s, and the phone sends its file again from the first byte. A file kept already is answered kept, and nothing changes. Open to a phone that has proved itself, by its token, with somebody signed in on it: the monitor, by their session’s token in the x-adms-monitor-session header, or the master user, by the master passcode sent beside the phone’s proof. A phone taken out of service sends it too. |
| `POST` | `/phones/current/setup-choices` | What a phone being set up under the firm’s master passcode picks from, with a connection and nobody signed in: the firm’s open projects; the monitors in service by name and Monitor ID, a thousand a call at most (the answer says where to ask on from); and, where an open project is named, that project’s live sites and its unit rate pay items in service, as a phone’s lists show them. A project that is closed or is none is answered no lists. Never a rate, a rule or a load’s pay item; never a password or a phone number. Nothing is written but the use of the passcode, a security event naming the phone. Open to a phone in service that has proved itself, by its token. A passcode that isn’t taken is refused by one code; wrong tries count as they do when a phone is enrolled. |
| `POST` | `/phones/current/sign-ins` | A field monitor signs in on an enrolled phone, online, with their Monitor ID and password. The answer is their session’s token, which the phone’s calls carry in the x-adms-monitor-session header beside the phone’s own token, how long it lasts, and the assignment the phone takes: theirs for the day, by its project’s own time zone, or none with the reason (none that day; several that day and nothing to say which is this phone’s). Nobody enters the master passcode and nobody picks a project. Open to a phone in service that has proved itself, by its token. Whatever is wrong is refused by one code; wrong tries in a row make the next wait longer, and at the policy’s count lock the login until an admin sets the password anew. |
| `POST` | `/phones/current/sign-outs` | The monitor signed in on the phone signs out: their session ends on the server. Open to a proven phone with its monitor’s session token. |
| `POST` | `/phones/current/syncs` | A phone syncs: it sends its clock, the unplanned assignments made on it, the uses of the master passcode it logged, its records (field events, scale receipts, incidents, drop-off logs, surveys, ROEs and certifications, each under the id the phone made for it, 200 at most) and the last number it used of each series. The server keeps the sync with both clocks and their difference, then reads each thing in that order and gives each its own answer, in the order sent. A record is saved; already saved, where it was sent before (never stored twice, and never taken for a duplicate ticket number); kept in quarantine, not saved, where another record holds its number, with the reason and the record that holds it; or not saved, where it can’t be read, names something the server doesn’t have, was made under an assignment that was not saved, or is a certification its own rules refuse: what was sent is then held on the server exactly as sent, for a person, and the answer says so. One record never stops the others. Nothing is refused over eligibility, and no answer carries a status, a flag or money: reconciliation is queued for what arrived and never waited for. For each series reported the answer lists the blocks of numbers the phone holds now, topped up to a full block where the project has such a series and the phone is in service. A phone in service is brought the master passcode’s version in force, with its check where the phone holds another, and the setup version of the project whose lists it holds; a phone out of service, or proven with the passcode because the firm’s time revoked its key, is given no numbers and brought nothing. A phone with more than one sync’s worth sends another. Open to a phone that has proved itself, by its token, with somebody signed in on it: the monitor, by their session’s token in the x-adms-monitor-session header, or the master user, by the master passcode sent beside the phone’s proof. A phone taken out of service sends it too. |
| `POST` | `/phones/current/turn-ins` | A phone is turned in ("Sync and turn in") under the firm’s master passcode, after it has synced every queue and sent every photograph’s file. It sends the passcode, everything it still holds by the ids it made (its unplanned assignments, the uses of the master passcode it logged, its records, its photographs) and by fingerprint what it sent with no id the server could read, and the last number it used of each series it holds numbers of. Only when the server has all of it from that phone (a record saved, kept in quarantine, or held for a person; a photograph saved with every file kept) is the phone turned in: the unused rest of each series it reported is handed back for the next phone, and a series it doesn’t report, or every block of a phone marked lost, is retired whole; a phone in service is taken out of service as retired; its sessions end; its key is revoked, so it proves nothing again; and the answer tells it to wipe. Otherwise nothing is changed and nothing is wiped, and the answer names what the server doesn’t have. A phone out of service is turned in the same way. Each use of the passcode is a security event, and a turn-in another. Open to a phone that has proved itself, by its token; nobody need be signed in. A passcode that isn’t taken is refused by one code; wrong tries count as they do when a phone is enrolled. |
| `POST` | `/phones/current/unplanned-assignments` | A phone reports an unplanned assignment made on it under the firm’s master passcode, when its monitor had none: the project, the monitor, their work (the kinds of field event, certifying, the forms, the unit rate pay items), one site where the work is at one, and when it was made by the phone’s own clock, with the clock’s offset. The id is the phone’s own: the same report sent again is answered as already saved and kept once. It is kept as the phone reports it, marked unplanned on the dispatch board and never flagged, for a phone out of service too; its day is the day the phone’s clock said, in the project’s time zone. It is not saved only where something it names is no record of the kind it must be, or its id is another record’s. The passcode itself is never sent: the phone checked it. It says what it was made at: the phone set up (a phone moved to another of its project’s sites is set up anew there, and the earlier assignment stands with everything recorded under it), or the phone passed to another monitor, who had no assignment it could take. Each one saved is two security events at the phone’s own time: a phone set up or a phone switched, and a use of the master passcode. Where several of a monitor’s assignments for a day name one phone, their sign-in on it takes the one made on it last. Open to a phone that has proved itself, by its token, with somebody signed in on it: the monitor, by their session’s token in the x-adms-monitor-session header, or the master user, by the master passcode sent beside the phone’s proof. A phone taken out of service sends it too. |
| `POST` | `/phones/enrolments` | A phone is enrolled, once, under the firm’s master passcode: it sends its name on the dispatch board and the public half of the key it made. The answer is the phone’s id and the version of the master passcode it was enrolled under. Nobody is signed in. A passcode that isn’t taken is refused by one code; wrong tries in a row make the next wait longer, and at the policy’s count lock enrolment until an admin changes the passcode. |

## /photographs

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/photographs` | A photograph is added in the back office to a record made there (a field event, a certification, an ROE, a survey, an incident or a drop-off log), its file sent whole as bytes, at most the policy’s size: what it is on, a certification’s view where it has one, and when it was taken with its clock’s offset, in the query. Its kind is told by its own first bytes (JPEG, HEIC or HEIF, PNG, TIFF); it is kept once where the server places it, its fingerprints the server’s reading of the bytes. The ticket it is on is queued, or a certification’s own flags. Refused for a record a phone made, a view on anything but a certification and a ticket locked as invoiced. |
| `PATCH` | `/photographs/{photoId}` | A photograph is removed from the record or put back, and labelled in the firm’s own words: only what is stated changes, and a `null` label takes the label off. Nothing is deleted: its record, its file and its fingerprints stay. The ticket it is on is queued. Refused for a ticket locked as invoiced. |
| `GET` | `/photographs/{photoId}/files/{file}` | A photograph’s file as it was kept: the photograph itself or the depth file beside it, the bytes that were stored, under the type their own first bytes said, sent as a download and never as a page. A photograph removed from the record is read all the same. One whose file hasn’t arrived, or that has no depth file, is not found. |

## /projects

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/projects` | The firm’s projects, a page at a time, in name order: each with its settings, its active window and whether it is closed. |
| `POST` | `/projects` | Creates a project under a client and a declaration event, with its time zone, active window, ticket-number source, ticket types and kinds of unit rate work. |
| `GET` | `/projects/{projectId}` | One project’s page: its settings, active window and whether it is closed, its kinds of work, each contractor’s place on it by name, and its contracts. |
| `PATCH` | `/projects/{projectId}/active-window` | Changes a project’s active window: either end; the end may be emptied. Its tickets not locked as invoiced are queued. |
| `GET` | `/projects/{projectId}/assignments` | One project’s dispatch board for one day, a page at a time, in the order the assignments were written: those that stand, or the withdrawn ones. One made on a phone under the master passcode is among them, marked unplanned. |
| `POST` | `/projects/{projectId}/assignments` | An assignment is written on a project’s board: a person, a day, their work, one site where the work is at one, optionally a phone. Planned, and it says who wrote it. Refused for a read-only viewer or an archived person, a phone out of service, and a pay item that is no unit rate pay item. |
| `GET` | `/projects/{projectId}/audit-records` | What the audit log holds of a project, newest first, a page at a time: every change to its records, each with the values before and after, who made it, from where and why. Narrowed by the log’s domain (security, billing, operations, records) and by who made the change. Each reader is shown what their areas let them read, as a record’s history is: a ticket’s records with view review, setup with view setup, the board with view dispatch, the security domain an admin alone. |
| `GET` | `/projects/{projectId}/certifications` | A project’s certifications, a page at a time, by cert number as written: each whole, with its boxes, helpers, photographs’ records, QC mark and its own flags, where it came from (its phone, or the paper sheet with who entered it and why) and any revocation. |
| `POST` | `/projects/{projectId}/certifications` | A certification sheet whose phone failed is entered in the back office: its number as the sheet has it, a container or a crew, the prime picked and the subs typed, what was certified, the boxes measured (the primary first; the capacity is worked out, never sent), the driver and the tag, when it was certified by the sheet, the monitor who certified it and the helpers, the representatives, the certification it replaces where a modified truck was certified again, notes and a reason picked from the firm’s list. It names the person who entered it. The field events already on its number are linked to it, and their tickets queued (the replaced one’s too). Refused for a number another certification of the project has, a reason the firm doesn’t offer for this and measurements that give no capacity. |
| `GET` | `/projects/{projectId}/closing-check` | What is still open on a project before an admin closes it: phones that took its work and weren’t turned in, tickets not QC Approved or not locked as invoiced, records waiting for a person. It never stops the close. |
| `POST` | `/projects/{projectId}/closings` | An admin closes a project: its assignments are withdrawn and its phones’ unused numbers retired, and nothing in it changes after; what was still open is said with it. Field work for it that arrives later is held for a person. A project closed already is answered as it stands. Refused only while the worker is still working its tickets. |
| `POST` | `/projects/{projectId}/contracts` | Creates a contract on a project: its name, one of the project’s primes, and an optional link to the signed copy. |
| `POST` | `/projects/{projectId}/control-ranges` | Enters a run of pre-printed control tickets on a project that numbers its tickets from them. |
| `GET` | `/projects/{projectId}/daily-report` | The daily report as a landscape PDF, for the same days and filters as the debris totals: a title page; the email’s outline without its figures; a page per operation (loads, haul-outs, unit rate) with its details and a map of the boundary, zones, sites and field events drawn as plain lines, no map tiles; the project’s other stats with each day; its estimates and objectives (none is recorded, and the page says so); and an end page with the firm’s address for its list. Sent as a download, never as a page. Never a cost. |
| `GET` | `/projects/{projectId}/debris-totals` | What a project moved over a range of days (a day, a week, a month, any range up to 366 days) in its time zone, never a cost: completed loads by debris type (loads, CY, tons), completed haul-outs by the site they were dumped at and their debris type, unit rate tickets by payable type and pay item (tickets and units), the trucks, trailers and crews whose badges were scanned, incidents filed and open, monitors on shift, and each day’s counts. Narrowed by workflow, debris type, site and contractor. Read each time, kept nowhere. |
| `GET` | `/projects/{projectId}/drop-off-logs` | A project’s citizen drop-off logs, a page at a time, in the order they were made: each with its photographs’ records and its QC mark. |
| `POST` | `/projects/{projectId}/drop-off-logs` | A citizen drop-off log kept on paper is entered in the back office: the monitor whose log it was, when and where, the site, the resident’s name and where the debris came from, notes and the answers to its form’s own fields, with a reason picked from the firm’s list. It is on no ticket, so nothing is queued. Refused for a site of another project, a reason the firm doesn’t offer for this, a fix that can’t be one and answers that don’t fit their version. |
| `GET` | `/projects/{projectId}/email-summary` | The debris totals as the email summary’s plain text, to paste: the project’s reference and name, the days, a section per operation headed and lined with the project’s own forms, debris types, sites, payable types and lists, its totals, the project’s other stats, and "All figures in this report are pending final reconciliation". Never a cost. |
| `POST` | `/projects/{projectId}/esri-map-versions` | Loads a project’s map from Esri feature layers as its next version: the boundary’s layer and, if it has any, the zones’ layer (the same or another), named in the field picked. The API fetches each as GeoJSON in WGS84 and keeps the copy with the version. An earlier version and its zones stay as they were. The project’s tickets not locked as invoiced are queued. |
| `GET` | `/projects/{projectId}/exports/{dataset}` | A project’s tickets, transactions (the ledger, line by line, with its rates and amounts), certifications or audit history as a file to take away: CSV, Excel-compatible CSV (UTF-8 with its byte order mark) or JSON. Narrowed as the ledger is (workflow, pay item, prime, invoice batch, locked or not, days); the audit history as the reader may read it, the security domain an admin’s alone. A cell beginning =, +, -, @, a tab or a carriage return is written as text. Sent as a download, never as a page; made each time, kept nowhere. Never a driver’s phone or licence number. |
| `POST` | `/projects/{projectId}/field-events` | A field event is created in the back office because a phone failed: its kind and number, the monitor whose work it was, when and where, the cert number and what its kind records, with a reason picked from the firm’s list. A detail left out is empty. It is a field event like any other: its ticket is queued, pairs by its number and is flagged as created in the back office. Refused for a number another field event of its kind holds, a fix that can’t be one, a reason the firm doesn’t offer for this, a detail its kind doesn’t record and a ticket locked as invoiced. |
| `POST` | `/projects/{projectId}/field-tags` | Adds a field tag at the end of the project’s own list. |
| `GET` | `/projects/{projectId}/flags` | A project’s flags, a page at a time, oldest first: on a ticket or on another record (a certification, a site, a survey), of one cause or every one, open or cleared, each with every acceptance it had and whether it was taken back. |
| `GET` | `/projects/{projectId}/forms` | A project’s forms, one for each document type at most: each with its debris types’ entries (those archived kept, marked) and the newest version of its own fields. |
| `POST` | `/projects/{projectId}/forms` | Creates a project’s form for one document type. "GPS required" is stated on an ROE form, and what an incident offers its monitors on an incident form; on no other. |
| `GET` | `/projects/{projectId}/hours` | A project’s workers’ days on the time clock, from one day to another (62 days at most), a page at a time by day and then by assignment: each assignment with an entry, standing or withdrawn; its entries and the activity lines its clock-out carried, as the worker’s phone sent them; what its hours come to (worked and on a break inside the day, which ends before midnight in the project’s time zone, and what fell outside it by the day it counts on (past midnight, the next), whether still clocked in, entries that fit no stretch), in whole minutes, with no limit on hours; and its approval in force, with whether anything of it arrived after. What the export of hours reads. Never a rate or a pay, and never crew time’s hours. |
| `GET` | `/projects/{projectId}/incidents` | A project’s incidents, a page at a time, in the order they were made: each with its photographs’ records and its QC mark. |
| `POST` | `/projects/{projectId}/incidents` | An incident written on paper is entered in the back office: the monitor whose record it was, when and where, its type and sub-type, what happened, who was involved, the address, the contractor, cert number and ROE number it names (each finds its record), notes and the answers to its form’s own fields, with a reason picked from the firm’s list. It is on no ticket, so nothing is queued. Refused for a reason the firm doesn’t offer for this, a fix that can’t be one and answers that don’t fit their version. |
| `GET` | `/projects/{projectId}/invoice-batches` | A project’s invoice batches, a page at a time, in name order: each with how many locks in force it holds. |
| `POST` | `/projects/{projectId}/invoice-batches` | An invoice batch is named on a project. Refused for a name another batch of the project has. |
| `GET` | `/projects/{projectId}/ledger` | A project’s ledger as it stands, a page at a time in the order the worker wrote its lines: each line with its ticket (and property or pile), workflow, # slot, pay item, unit, quantity, rate, amount, contract, prime, operating sub, rule, ticket date, the ticket’s status, QC status and lock. Narrowed by workflow, pay item, prime, invoice batch, locked or not, and the days its work counts on (the project’s time zone). With the page, what every line the filters match comes to: how many, and their sum to the cent, added by the database. |
| `POST` | `/projects/{projectId}/map-files` | Sends a map’s file for a project, whole, as bytes: a zipped shapefile (its .shp, .dbf and .prj; a projected one is converted to longitude and latitude) or a GeoJSON file, at most the policy’s size. The server tells its kind by its own first bytes, reads every layer it holds and keeps it once, where it places it; a file it can’t read is not kept, and the answer says why. The answer is the file’s id and, for each layer, its features, its fields and whether it is one shape. The same file sent again is the same file. Nothing is loaded yet. |
| `POST` | `/projects/{projectId}/map-files/{mapFileId}/map-versions` | Loads a project’s map from a map’s file sent before, as its next version: the layer that is its boundary and, if it has any, the layer of its zones, each named in the field picked. A layer left unnamed is the file’s only one. The file is kept with the version. An earlier version and its zones stay as they were. The project’s tickets not locked as invoiced are queued. |
| `GET` | `/projects/{projectId}/map-versions` | A project’s map versions, a page at a time, newest first: each with how it came, its file’s name or the layer’s address, the zones’ own layer and whether its copy was kept, and its zones’ names. |
| `POST` | `/projects/{projectId}/map-versions` | Loads a project’s map as its next version: its boundary and, if it has any, its zones, each named in the field picked. An earlier version and its zones stay as they were. The project’s tickets not locked as invoiced are queued. |
| `POST` | `/projects/{projectId}/number-series` | Starts a number series the project doesn’t have yet (its cert numbers), from the number the firm says. |
| `GET` | `/projects/{projectId}/pay-item-rates` | A project’s rates, a page at a time, by pay item and then prime: each an exact decimal, those archived kept, marked. Read only by whoever may set one. |
| `GET` | `/projects/{projectId}/pay-items` | A project’s pay items, a page at a time, by code: each as the firm entered it, with its crew day’s steps in force. Never a rate: a pay item’s rates are a read that takes Setup. |
| `POST` | `/projects/{projectId}/pay-items` | Enters a pay item read off the contract: its code, unique on the project, its description, payable type, Unit of Measure, size band, capture settings, and a rate for each prime. A band that overlaps another of its payable type isn’t saved. The project’s tickets not locked as invoiced are queued. |
| `GET` | `/projects/{projectId}/photo-pdf` | A PDF of photographs built for an auditor, in one of two layouts: several items a page (each a row of its details and up to three photographs, three rows a page, never cropped), or one item a page with its receipt beside its details. Items are tickets (each field event with its photographs, narrowed by workflow and days, or by the ticket numbers given), certifications (all, or the cert numbers given) or incidents (by days). A photograph removed from the record is left out; one the PDF can’t show (HEIC, HEIF, TIFF) is named in its place. Refused for more items than one document draws. Sent as a download, never as a page. |
| `POST` | `/projects/{projectId}/primes` | Adds a contractor to a project as a prime. |
| `GET` | `/projects/{projectId}/quantity-lookup` | What each site’s phones ask for: the load call, the scale weight, a count of the items carried or nothing more, for each site, debris type and prime. Worked out from the setup as it stands. |
| `GET` | `/projects/{projectId}/receipt-batch` | A PDF of the receipts of one or more ticket numbers, pasted with any separator (spaces, commas, semicolons, lines): just their receipts, each field event’s as its phone prints it, or the full record of each with its photographs and its receipt. Its first page names every number the project doesn’t have; none is dropped. Narrowed to one workflow where asked. Refused for more field events than one document draws. Sent as a download, never as a page. |
| `POST` | `/projects/{projectId}/reopenings` | An admin reopens a closed project, with a reason, audited. Its withdrawn assignments and retired numbers stay as they are. |
| `POST` | `/projects/{projectId}/reprocesses` | Tickets of the project are queued to be worked again as their records and the setup stand, nothing changed: those named, or the tickets of a job the worker set aside, by its id. One job, naming the person; a ticket locked as invoiced is left off it and counted. Refused for more tickets than the installation’s policy lets one job name, a number no field event of the project is under, and a job that isn’t one of the project’s set aside. |
| `GET` | `/projects/{projectId}/review-names` | The names a reviewer reads a project’s tickets by: its people (a monitor with their Monitor ID), sites, the debris types of its forms, pay items by code and description, contractors, contracts, rules and zones, and the firm’s lists a ticket names (payable types, kinds of work and their item names, field tags, reasons with where each is offered, survey work types, incident types and sub-types); each archived one said so. Never a rate. |
| `GET` | `/projects/{projectId}/review-queue` | The review queue of one workflow on a project: the tickets whose field events stand pending or in review, a page at a time by number, each with its open flags (not cleared, no acceptance in force); and the project’s open flags counted by cause. Narrowed to one cause, only the tickets with an open flag of it. |
| `GET` | `/projects/{projectId}/roes` | A project’s ROEs, a page at a time, in the order they were made: each with its scope, its photographs’ records and its QC mark. |
| `POST` | `/projects/{projectId}/roes` | An ROE made on paper is entered in the back office: its number off the paper, parcel, address, owner, whether and when it was signed, where, its notes and the answers to its form’s own fields; it names the person who entered it. What was already waiting for its number points at it, and those tickets are queued. Refused for a number another ROE of the project holds, a fix that can’t be one and answers that don’t fit their version. |
| `GET` | `/projects/{projectId}/rules` | A project’s rules, a page at a time, in the order written: each whole as the rule builder states it (each picked value with its name and whether it is gone), the pay item whose "Pays when" wrote it, and whether it is valid now and why not. |
| `POST` | `/projects/{projectId}/rules` | Writes a rule on one of the project’s load, haul-out or unit rate forms. An exact copy of another rule isn’t saved. Its form’s tickets not locked as invoiced are queued. |
| `POST` | `/projects/{projectId}/rules/previews` | What a new rule would do, before it is saved: the record as it would be, its warnings and how many tickets it would queue (and pass by, locked as invoiced), or why it wouldn’t be saved. Nothing is kept. |
| `GET` | `/projects/{projectId}/set-aside-jobs` | A project’s jobs set aside after failing too often, newest first, a page at a time: when each was queued and set aside, what queued it and who asked, its tries, and its tickets, each said to be waiting where no job has worked it since. Put one back with a reprocess naming it. Never why it failed. |
| `PATCH` | `/projects/{projectId}/settings` | Changes a project’s own settings; one left out stays as it is. Its tickets not locked as invoiced are queued. |
| `GET` | `/projects/{projectId}/setup-check` | The setup check: everything a project’s setup leaves open, in plain words; none when it is whole. It lists and never refuses. |
| `GET` | `/projects/{projectId}/sites` | A project’s sites, a page at a time, in name order: each with its labels, its tipping fee links and its own shape’s newest version. |
| `POST` | `/projects/{projectId}/sites` | Enters a disposal site on a project: its site type, the four site facts, its GPS, a permit number (empty: permit outstanding) and its dates. "Haul-outs leave here" is saved only on a TDSR/DMS site. |
| `GET` | `/projects/{projectId}/surveys` | A project’s surveys, a page at a time, in the order they were made: each with its own flags, its photographs’ records and its QC mark. |
| `POST` | `/projects/{projectId}/surveys` | A survey made on paper is entered in the back office: the monitor whose work it was, when and where, its work type, survey number, the ROE number it names (which finds its ROE), notes and the answers to its form’s own fields, with a reason picked from the firm’s list. The project’s surveys are checked on the next run. Refused for a reason the firm doesn’t offer for this, a fix that can’t be one and answers that don’t fit their version. |
| `POST` | `/projects/{projectId}/ticket-unvoids` | A void ticket is unvoided: the unvoid is a record of its own, and a person’s void is kept, marked lifted. Refused for a ticket that isn’t void. |
| `POST` | `/projects/{projectId}/ticket-voids` | A ticket is voided, with a reason picked from the firm’s list and the reviewer’s own note. Nothing asks whether the choice is right. Refused for a number no field event of the project is under, a ticket already void, a reason the firm doesn’t offer for voiding and a ticket locked as invoiced. |
| `GET` | `/projects/{projectId}/tickets` | A project’s tickets of one workflow, any status, by number a page at a time: each with its statuses, ticket date, field events, what a QC mark or a lock names, its QC status, its lock and batch, what its lines come to, and why it can’t be locked as invoiced now where it can’t, in plain words. Narrowed by status, QC status (NONE: no mark yet), locked or not, an invoice batch, and the days it counts on. |
| `GET` | `/projects/{projectId}/tickets/{workflow}/{ticketNumber}` | One ticket whole, as the back office reads it: its field events in field time (as recorded and corrected, with their status, zone and worked-out pay item), the completed record that stands with its collection rows, its lines with their rates and amounts, its flags with their acceptances, its QC mark, every lock it had, its voids and unvoids. A ticket with no field event is not found. |
| `GET` | `/projects/{projectId}/tickets/{workflow}/{ticketNumber}/decisions` | What each reconciliation run decided for one ticket and why, newest first, a page at a time: the run (what started it, when), the status it left, its words, the field events it grouped and the kinds still to arrive, the lines it priced with the rule that made each, the flags it raised, and the rule or person that voided it; whether the ticket could be processed. A ticket with no field event is not found. |
| `GET` | `/projects/{projectId}/tickets/{workflow}/{ticketNumber}/history` | What the audit log holds of one ticket, newest first, a page at a time: every change to its field events and what is recorded beside them (item lines, carried tickets, photographs’ records), the completed record and its rows, its lines (those the worker removed included, each copied whole first), its flags and their acceptances, and its QC marks, locks, voids and unvoids; each with the values before and after, who made it, from where and why. A ticket with no field event is not found. |

## /qc-marks

| Method | Path | What it does |
| --- | --- | --- |
| `PUT` | `/qc-marks` | The QC status of a completed load or haul-out, a unit rate ticket, a certification, an ROE, a survey, an incident or a drop-off log is set. Refused for a load’s or a haul-out’s single field event and for a ticket locked as invoiced. |
| `PUT` | `/qc-marks/several` | QC statuses set on many records at once, up to 200, each as one would be: in the order sent, each its own piece of work and each answered, saved with its mark or not with its reason and words (a ticket locked as invoiced among them). One refused never stops the rest. |

## /quarantined-records

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/quarantined-records` | The records a phone sent whose number another record holds, a page at a time, oldest first: by project or by phone, waiting or resolved. Each with what the phone sent, its phone and monitor, why, the record that holds its number and how it was resolved. |
| `POST` | `/quarantined-records/{quarantinedRecordId}/renumberings` | A quarantined record is resolved by giving it the number it should have had: a field event a ticket number (and a split’s collection its suffix; left out, the suffix it was sent with stays), an ROE an ROE number. It is saved as a record of its own with the id its phone made and its photographs, written as the reviewer; a field event’s ticket is queued, and the tickets that were waiting for an ROE of that number. Resolved once. Refused for a number another record of its kind holds, the number it was sent with, the other kind of number and a ticket locked as invoiced. |
| `POST` | `/quarantined-records/{quarantinedRecordId}/second-copies` | A quarantined record is resolved as a second copy of the record that holds its number: nothing is made from it, and it stays, marked with who said so and when. Resolved once. |

## /readiness

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/readiness` | That the API answers and its database answers it: status 503 UNAVAILABLE otherwise. Anybody may ask. |

## /roes

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/roes/{roeId}` | An ROE is corrected in place: its number, parcel, address, owner, whether and when it was signed, its notes and the answers to its form’s own fields; only what is stated changes, and `null` empties a detail that may be empty. A corrected number is found by what names it, and what named the old one finds no ROE; what each says it named never changes. Every ticket on it, before and after, is queued; one locked as invoiced is left as it is. Refused for a number another ROE of the project holds and answers that don’t fit their version. |
| `PUT` | `/roes/{roeId}/scope` | A pick of an ROE’s scope, a debris type of the project’s load form or a payable type, is added, taken off (it stays, marked, and counts for nothing) or put back, with how many where the ROE says. Every ticket on the ROE is queued; one locked as invoiced is left as it is. Refused for a debris type that isn’t on the project’s load form. |

## /rules

| Method | Path | What it does |
| --- | --- | --- |
| `PUT` | `/rules/{ruleId}` | States a rule anew, whole: its form, name, contract, Active tick, criteria and actions. A rule a pay item’s "Pays when" wrote is changed only there. The tickets of its form, and of the form it had, not locked as invoiced are queued. |
| `POST` | `/rules/{ruleId}/previews` | What a change to a rule would do, before it is saved: the record as it would be, its warnings and how many tickets it would queue (and pass by, locked as invoiced), or why it wouldn’t be saved. Nothing is kept. |

## /scale-receipts

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/scale-receipts/{scaleReceiptId}` | One scale receipt as the tower copied it, with its photographs’ records and the field events that name it. |
| `PATCH` | `/scale-receipts/{scaleReceiptId}` | A scale receipt is corrected in place: the site’s number as written, the site that issued it, its weights and its notes; only what is stated changes. Every ticket that points at it is queued; one locked as invoiced is left as it is. |

## /sessions

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/sessions` | The signed-in person’s own sessions open now, newest first: when each was opened, when it ends at the latest, and which is this one. |
| `POST` | `/sessions` | Signs a back-office or outside user in with their email address and password, and their authenticator code where they have one. The tokens are set as cookies; the answer holds none. |
| `GET` | `/sessions/csrf` | The CSRF value of the session the refresh token belongs to, for a page that was loaded anew. |
| `GET` | `/sessions/current` | Who is signed in. |
| `POST` | `/sessions/endings` | The signed-in person ends one of their own other sessions (this one ends by signing out). Not found for a session that isn’t theirs or has ended. |
| `POST` | `/sessions/refreshes` | Gives a session a new access token and a new refresh token. A refresh token is good once: one presented a second time ends its session. |
| `POST` | `/sessions/sign-outs` | Ends the session on the server and clears its cookies. |

## /set-aside-files

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/set-aside-files` | The files a phone uploaded for a photograph that weren’t the file it fingerprinted, a page at a time, oldest first: by project or by phone, waiting or resolved. Each beside its photograph (what it belongs to, the fingerprint its phone gave and whether that file has been kept since), with what arrived and how it was resolved. Never where either is kept. |
| `POST` | `/set-aside-files/{setAsideFileId}/closings` | A file set aside is closed: the photograph’s own file won’t come, and this one stays beside it, kept, never the photograph’s. Marked with who closed it, when and why. Resolved once. |
| `GET` | `/set-aside-files/{setAsideFileId}/file` | A file set aside, as it was kept: the bytes that were stored, under the type their own first bytes said, sent as a download and never as a page. |

## /site-shapes

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/site-shapes/{siteAreaVersionId}/file` | The file a site’s shape version was loaded from, as it was kept: the bytes that were stored, under the type they were kept as, sent as a download and never as a page. None for a shape that was drawn. |

## /sites

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/sites/{siteId}` | Changes a site: its name, short code, site type, site facts, permit number, GPS or dates; one left out stays as it is. The tickets with a field event at the site not locked as invoiced are queued. |
| `POST` | `/sites/{siteId}/esri-shapes` | Saves a site’s own shape from an Esri feature layer as its next version, the layer’s features as one shape. The API fetches it as GeoJSON in WGS84 and keeps the copy. An earlier shape stays as it was. The tickets with a field event at the site not locked as invoiced are queued. |
| `PUT` | `/sites/{siteId}/labels/{siteLabelId}` | Gives a site one of the firm’s own site labels, or gives back one removed. It changes nothing the system does. |
| `POST` | `/sites/{siteId}/labels/{siteLabelId}/removals` | Removes a label given to a site by mistake: audited, never deleted; give it again to put it back. |
| `POST` | `/sites/{siteId}/map-files/{mapFileId}/shapes` | Saves a site’s own shape from a map’s file sent before for the site’s project, as its next version: the layer named, or the file’s only one, as one shape. From then on the site is checked against the shape in place of the project’s circle, and an earlier shape stays as it was. The tickets with a field event at the site not locked as invoiced are queued. |
| `GET` | `/sites/{siteId}/quantity-lookup` | What one site’s phones ask for: its rows of the project’s quantity lookup, worked out from the setup as it stands; or, asked with a setup version, as its phones held them under that version, for the debris types phones are shown. No such version of its project: not found. |
| `GET` | `/sites/{siteId}/shapes` | A site’s own shape, every version, newest first: each with its shape, how it came (drawn, a file, an Esri layer), its file’s name or the layer’s address, and whether its file was kept. Never where. |
| `POST` | `/sites/{siteId}/shapes` | Saves a site’s own shape as its next version: from then on the site is checked against the shape in place of the project’s circle, and an earlier shape stays as it was. The tickets with a field event at the site not locked as invoiced are queued. |
| `POST` | `/sites/{siteId}/tipping-fees` | Links a site to a tipping fee pay item written from it: how a fee line is told from a hauling line. A site may keep several. A link removed is linked again. |
| `POST` | `/sites/{siteId}/tipping-fees/{payItemId}/removals` | Removes a site’s link to a tipping fee pay item made by mistake: audited, never deleted; link it again to put it back. The tickets with a field event at the site not locked as invoiced are queued. |

## /surveys

| Method | Path | What it does |
| --- | --- | --- |
| `PATCH` | `/surveys/{surveyId}` | A survey is corrected in place: its work type, its survey number, the ROE number it names and its notes; only what is stated changes. A corrected ROE number finds its ROE again. The answers to its form’s own fields are given whole, against the version they answer. The project’s surveys are checked again on the next run. Refused for answers that don’t fit their version. |

## /two-step

| Method | Path | What it does |
| --- | --- | --- |
| `POST` | `/two-step/removals` | The signed-in person removes their own authenticator, giving a code from it. Their other sessions end; it is a security event, and they are told by mail. Whoever must have one then sets a new one up before anything else. A code that isn’t taken is a wrong try, as at sign-in. |
| `POST` | `/two-step/setups` | Begins setting up the signed-in person’s authenticator: answers the secret for the app. Nothing is kept until a code proves it. |
| `POST` | `/two-step/setups/confirmations` | Finishes setting up the signed-in person’s authenticator with a code from it. Their other sessions end. |

## /viewer

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/viewer/photographs/{photoId}/files/{file}` | A photograph’s file, or its depth file, as kept, for a viewer the database shows the photograph’s record: the bytes that were stored, under the type their own first bytes said, sent as a download and never as a page. A photograph the viewer isn’t shown is not found. |
| `GET` | `/viewer/projects` | The projects the signed-in viewer is given, a page at a time, in the order they were entered: each by name, with its time zone and when it was closed. |
| `GET` | `/viewer/projects/{projectId}/certifications` | A project’s certifications as the signed-in viewer is shown them, a page at a time, by cert number: each with its boxes, who it names by name and any revocation; never the driver’s phone or licence number. A contractor user is shown those naming its contractor. |
| `GET` | `/viewer/projects/{projectId}/drop-off-logs` | A project’s citizen drop-off logs as the signed-in viewer is shown them, a page at a time, in the order they were made: the resident and where their debris came from only where the firm lets this viewer see private citizens by name. A contractor user is shown none. |
| `GET` | `/viewer/projects/{projectId}/incidents` | A project’s incidents as the signed-in viewer is shown them, a page at a time, in the order they were made. A contractor user is shown none. |
| `GET` | `/viewer/projects/{projectId}/quantity-lookup` | What each of the project’s sites’ phones ask for, as a client user or an auditor reads it. Not found for a contractor user. |
| `GET` | `/viewer/projects/{projectId}/roes` | A project’s ROEs as the signed-in viewer is shown them, a page at a time, in the order they were made: each by number, with whether and when it was signed, and its owner’s name only where the firm lets this viewer see private citizens by name. |
| `GET` | `/viewer/projects/{projectId}/setup` | A project’s setup as a client user or an auditor reads it: its forms and debris types, its pay items by code and description (never a rate), its sites and their facts, its contracts and their primes, its rules by name. Not found for a contractor user. |
| `GET` | `/viewer/projects/{projectId}/setup-check` | The setup check as a client user or an auditor reads it: everything the project’s setup leaves open, each in plain words. Not found for a contractor user. |
| `GET` | `/viewer/projects/{projectId}/surveys` | A project’s surveys as the signed-in viewer is shown them, a page at a time, in the order they were made. A contractor user is shown none. |
| `GET` | `/viewer/projects/{projectId}/tickets` | One workflow’s tickets on a project as the signed-in viewer is shown them, a page at a time, by number: each with the statuses its field events hold and how many field events it has. A contractor user is shown its own contractor’s tickets. |
| `GET` | `/viewer/projects/{projectId}/tickets/{workflow}/{ticketNumber}` | One ticket as the signed-in viewer is shown it: its field events, the completed record the system concluded from them, and its priced lines, with a line’s rate and amount only where this viewer is shown the money. A ticket the viewer isn’t shown is not found. |
| `GET` | `/viewer/projects/{projectId}/tickets/{workflow}/{ticketNumber}/history` | What the audit log holds of one ticket as the signed-in client user or auditor is shown it, newest first, a page at a time: each change to its records, the values before and after as this viewer reads them, who made it and from where; never why. A contractor user is shown none; a ticket the viewer isn’t shown is not found. |
