Projects
A project groups parties for a job and can carry its own insurance requirement.
Under project compliance, each party is checked per project.
The API calls them projects whatever your dashboard's label, which is available as project_name on the account resource. Every route on this page needs projects enabled on the account (enable_projects); otherwise it answers 403 feature_disabled.
| Operation | Scope |
|---|---|
GET /projects |
read |
GET /projects/:id |
read |
POST /projects |
read_write |
PATCH /projects/:id |
read_write |
DELETE /projects/:id |
read_write |
POST /projects/:id/parties |
read_write |
DELETE /projects/:id/parties/:party_id |
read_write |
POST /projects/:id/complete |
read_write |
POST /projects/:id/uncomplete |
read_write |
POST /projects/:id/archive |
read_write |
POST /projects/:id/unarchive |
read_write |
To list a project's parties, use GET /parties?project_id=<id> (Parties).
The project entity
{
"id": 9,
"name": "Tower B",
"description": "12-story office tower",
"external_id": "JOB-2209",
"insurance_requirement_id": 42,
"insurance_requirement_ids": [42],
"compliance_profile_id": null,
"note_to_party": "List Tower B LLC as additional insured.",
"certificate_holder": null,
"start_date": "2026-04-01",
"end_date": "2027-06-30",
"completed": false,
"archived": false,
"custom_fields": {
"21": { "name": "Phase", "value": "Framing", "user_id": null },
"22": { "name": "Contract Manager", "value": "Sam Ortiz", "user_id": 301 }
},
"created_at": "2026-03-15T10:00:00Z",
"updated_at": "2026-09-01T10:00:00Z"
}
note_to_party is shown to parties asked for documents under this project, and certificate_holder overrides the account's certificate holder for it.
compliance_profile_id is the project's compliance profile.
On an account without project compliance, note_to_party and certificate_holder are stored but not used.
custom_fields is keyed by custom field id and served on the list and the detail alike, in the same shape as on parties. It holds only your account's active fields available on projects that have a value: a field with no value (never set, cleared, or blank) is absent, never served as null. A User-type field also carries user_id; its value is the user's display name. What each type serves and accepts is in Custom field types.
A change to a project's custom fields counts as an edit: setting, changing or clearing a value (through the API or the dashboard) moves the project's updated_at. Sending a value back unchanged does not, and neither does renaming a user (a User-type field's served value) or renaming, archiving, restoring or deleting a custom field definition (an archived field's values leave the served custom_fields and come back on restore without moving updated_at). Follow definition changes through the Custom Field Definition events (see Events).
Fields of features your account does not use are present with an empty value (custom_fields is {}).
Without multi-factor compliance, sending compliance_profile_id is 403 feature_disabled naming the field, unless it carries the value the project is served with (null on a create): that is accepted and changes nothing. Sending null for a project that already holds a profile is a change, so it is 403 too; the stored profile is kept.
GET /projects, GET /projects/:id
The account's projects in the collection envelope, and one project.
POST /projects
Body: name (required), description, insurance_requirement_id, note_to_party, start_date, end_date, certificate_holder.
Also compliance_profile_id.
A requirement or profile id outside your account is 422 naming the field.
Also custom_fields, written as on a PATCH (below): each field id you send is stored, and a cleared or blank field stores nothing.
Your account requires unique project names: a name another project already has (compared ignoring capitalization and spaces at either end, completed and archived projects included) is 422 validation_failed naming name, and nothing is created.
Also insurance_requirement_ids, the whole set of requirements the project confers, as it is served. It is the whole instruction: it replaces the set, the first id becomes insurance_requirement_id, and insurance_requirement_id sent beside it is 422 naming that field. An id outside your account, or an individual's requirement, is 422 naming insurance_requirement_ids; an id the project already holds is always accepted. A PATCH that changes only the set is a change: it is saved, recorded and recomputed like any other edit.
insurance_requirement_ids is served as the project's own requirement alone and may only be sent back with exactly that value (a no-op, so a GET body can be sent back as read); any other value is 403 feature_disabled.
Under project compliance a project always carries an insurance requirement, as on the dashboard: a create without insurance_requirement_id takes the account's default requirement (422 naming the field when there is none), and null is 422 on a create or an update.
Under project compliance with multi-factor compliance, the project's compliance profile governs and no insurance requirement is imposed.
Response 201.
PATCH /projects/:id
Updates the fields you send. Changing the requirement rechecks the project's parties.
Your account requires unique project names: renaming to a name another project already has (compared as on a create) is 422 validation_failed naming name, and nothing changes. Sending the project's own name back is never refused.
custom_fields is a patch, on the create and the update alike, exactly as on parties: each field id you send is written, every field you leave out keeps its value. To clear a field, send it as null, {"value": null} or {"user_id": null} (a blank or whitespace-only value or user_id clears it too: blank means no value); its value is removed and it is no longer served. Clearing a field that has no value changes nothing. {} and "custom_fields": null change nothing; there is no "clear all", so clear each field by id.
- A field takes
{"value": "..."}, or{"user_id": n}for a User-type field, by its type's rule (Custom field types); the user must be one of the account's assignable users, and assigning one on a project counts for "Assigned to Me", the user's page and notifications exactly as assigning them on the dashboard does. The entry aGETserves can be sent back as read. - Only your account's active fields available on projects are accepted: any other id (a field available only on parties, or an archived field, included) is 422
validation_failednamingcustom_fields, and so is the same field id sent twice or a value the field's type refuses. Nothing is written. An archived field is not served; its values are kept and are served again once the field is restored. - A
PATCHthat changes only custom fields is an edit like any other: it is saved and recorded asProject Edited, whose message names the custom fields that changed. APATCHthat changes nothing records nothing. An edit made on the dashboard follows the same rule and records the same message. Also 409conflict(on a create or an update) when another change stored the first value of the same custom field of this project at the same moment: nothing was written and the error carries noconflictmember, so send the request again. Without custom fields,custom_fieldsis served as{}; sending that back (ornull, or a map that only clears fields) is accepted and changes nothing, and any other value is 403feature_disablednamingcustom_fields.
start_date and end_date take a date or a timestamp. Sent with the date a GET serves (or a timestamp of the stored instant) they are not written: the stored time stays exactly as it was and no end-date rule runs again. A different date, or a timestamp of a different instant, is stored as the start of its US Central day.
Setting an end date in the past takes effect at once: the project is completed, and archived as well when the date is older than the account's retention period. Completion is one-way: moving the date back into the future does not reopen the project; use uncomplete / unarchive.
With project dates, a completed project's requirement stops applying, so a party whose only requirement came from that project moves to "No Requirements".
Response 200.
DELETE /projects/:id
Deletes a project.
On your account a project carries project-scoped certificates, requests and history, so it cannot be deleted: the request is 422 validation_failed and nothing changes. Close it with POST /projects/:id/complete or POST /projects/:id/archive (below) instead, as on the dashboard.
422 validation_failed when document signing requests belong to it.
Event: Project Removed. Response 204.
POST /projects/:id/parties
Attaches an existing party. Body: party_id (required) and, optionally, the per-project requirement, insurance_requirement_id.
Under multi-factor compliance, the per-project compliance_profile_id too.
A per-project requirement or profile is accepted only where the dashboard offers one: project compliance with project-specific requirements (the profile also needs multi-factor compliance). Otherwise a non-null value is 403 feature_disabled naming the field; null, what a new link is served with, is accepted and changes nothing.
Attaching recomputes the party's compliance, which can start a request for the project and email the party. A party already on the project, a foreign party_id, or a foreign requirement or profile id is 422 validation_failed. Response 201 with the project.
DELETE /projects/:id/parties/:party_id
Detaches the party and recomputes its compliance. The event is Parties Removed from Project, the counterpart of the attach's. Response 204. 404 when the party is not on the project.
Project lifecycle
These four actions need project lifecycles on the account (project compliance, or project dates); otherwise 403 feature_disabled. Each recomputes the compliance of the project's parties and records the same event as the dashboard (Project Marked Complete, Project Marked Incomplete, Project Archived, Project Unarchived). Each answers 200 with the project.
POST /projects/:id/complete
Marks the project complete.
POST /projects/:id/archive
Archives the project. An archived project's requirement no longer applies to its parties.
POST /projects/:id/uncomplete and POST /projects/:id/unarchive
Reopen the project. Because an end date in the past completes (or archives) the project again, both take one of:
| Body field | Meaning |
|---|---|
end_date |
A new end date, which must be in the future (today or a past date is 422 validation_failed and nothing changes) |
clear_end_date |
true to remove the end date |
Sending neither leaves the end date as it is, so a project whose end date has passed is completed again by the nightly lifecycle run.