Parties
A party is a company or person you track for compliance. The dashboard may call them parties; the API calls them parties whatever the label, and the label is available as party_name on the account resource so you can match it in your UI.
| Operation | Scope |
|---|---|
GET /parties |
read |
GET /parties/:id |
read |
POST /parties |
read_write |
PATCH /parties/:id |
read_write |
POST /parties/:id/archive |
read_write |
POST /parties/:id/unarchive |
read_write |
POST /parties/:id/pause |
read_write |
POST /parties/:id/unpause |
read_write |
POST /parties/:id/invalidate_certificates |
read_write |
POST /parties/:id/send_reminder |
read_write |
GET /parties/:id/events |
read |
GET /parties/:id/contacts |
read |
POST /parties/:id/contacts |
read_write |
PATCH /parties/:id/contacts/:contact_id |
read_write |
DELETE /parties/:id/contacts/:contact_id |
read_write |
Related routes that hang off a party are documented with their own resource:
GET /parties/:id/approval_overrides(Approval overrides)GET /parties/:id/compliance_requests(Compliance requests)GET /parties/:id/documents(Documents)POST /parties/:id/w9(W-9s)GET /parties/:id/requirement_statuses(Requirement statuses)
Rules that apply to every party route
- Individuals are not served as parties in v2. The individuals your account tracks (employee compliance) never appear in
GET /parties, their ids answer 404 on every/parties/:idroute, and their ids sent asparty_idanywhere else (a body or a query filter) are 422validation_failednamingparty_id. - There is no
vendorsalias. An authenticated request under/api/v2/vendorsanswers 404not_foundwith the message "In API v2 this resource is /api/v2/parties; V1 keeps /api/v1/vendors. See /api/docs/v2/migrating-from-v1." The key and account checks still run first, as for every other route: a request with no key or an invalid key gets 401, a deactivated or API-disabled account 403 (see Authentication). See Migrating from v1. - An id from another account is 404 in a path and 422 naming the field in a body, never 403.
- Writes go through the same code the dashboard's party form uses, so they send the same emails, record the same events (with your key as the actor, the dashboard's event types such as
Party Added) and recompute compliance at once.
The party entity
GET /parties returns the list entity (the summary, status and custom_fields). GET /parties/:id returns the detail entity. GET /parties?include=detail returns the detail entity in a list, with per_page capped at 50 (see Pagination).
{
"id": 191537,
"name": "Acme Electric",
"slug": "acme-electric-8f2c",
"email_address": "[email protected]",
"contact_first_name": "Dana",
"contact_last_name": "Ruiz",
"phone_number": "555-0100",
"address": "12 Main St",
"address2": null,
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"external_id": "PO-1234",
"external_id_2": null,
"external_id_3": null,
"created_at": "2026-10-01T15:02:11Z",
"updated_at": "2026-10-07T09:12:40Z",
"status": {
"compliant": false,
"manually_compliant": false,
"unresponsive": false,
"unresponsive_at": null,
"no_requirements": false,
"in_review": false,
"archived": false,
"paused": false,
"incomplete": false,
"jobtread_archived": false
},
"custom_fields": {
"12": { "name": "Region", "value": "Central", "user_id": null },
"13": { "name": "Account Manager", "value": "Sam Ortiz", "user_id": 301 }
}
}
The detail entity adds:
{
"requirements": {
"insurance_requirement_id": 42,
"insurance_requirement_ids": [42],
"compliance_profile": null
},
"noncompliant": [
{ "coverage": "gl", "reasons": ["General Liability each occurrence limit is below the required $1,000,000"] }
],
"expirations": [
{ "coverage": "gl", "expires_on": "2027-03-01" }
],
"contacts": [
{ "id": 881, "first_name": "Dana", "last_name": "Ruiz", "email": "[email protected]", "phone": "555-0100",
"is_primary": true, "active": true, "opted_out": false, "roles": [], "factor_opt_outs": [] }
],
"insurance_contacts": [
{ "id": 77, "name": "Pat Lee", "email": "[email protected]", "phone_number": "555-0199", "agency_name": "Lee Insurance", "active": true }
],
"insurance_contact_name": null,
"insurance_contact_email": null,
"insurance_contact_phone": null,
"insurance_agency_name": null,
"tags": [ { "id": 5, "name": "Electrical" } ],
"projects": [
{ "project_id": 9, "name": "Tower B", "insurance_requirement_id": 42, "compliance_profile_id": null }
],
"certificates": [
{ "id": 5521, "status": "Processed", "canceled": false, "effective_on": "2026-03-01", "expires_on": "2027-03-01", "document_type": "CERTIFICATE_OF_LIABILITY_INSURANCE" },
{ "id": 5530, "status": "Pending", "canceled": false, "effective_on": null, "expires_on": null, "document_type": null }
],
"notes": "Internal note",
"note_to_party": "Please list Tower B as the certificate holder.",
"archive_date": null,
"started_in_compliance": false,
"manual_expiration_date": null,
"collect_initial_w9": false,
"compliance_score": null,
"factor_scores": {},
"portal_url": null,
"outreach": [
{
"factors": ["insurance"],
"recipient": { "contact_id": 881, "email_address": "[email protected]", "kind": "primary" },
"cadence": "insurance",
"position": { "count": 2, "last_sent_at": "2026-10-04T05:00:00Z", "limit": 4, "exhausted": false },
"record": { "type": "compliance_request", "id": 88131 },
"link": "https://app.hounddog.com/upload-certificate/4c7e..."
}
],
"links": { "self": "/api/v2/parties/191537", "events": "/api/v2/parties/191537/events" }
}
Field notes:
namecan benullfor an incomplete party (status.incompletetrue).statusholds every lifecycle and compliance flag.manually_compliantis meaningful only whilecompliantis true (the party was marked compliant by a person or an override).no_requirementsis true when the party has no requirement to meet; such a party is neither compliant nor non-compliant.requirements.compliance_profileis the party's compliance profile,{"id", "name"}.noncompliantlists each failing coverage (a coverage prefix such asgl,auto,wc) with the reasons the compliance engine recorded.- In
noncompliant,coverageisnullfor the project compliance summary entry "Not Compliant with <project>". expirationscome from the party's current certificates (one entry per required coverage that has an expiration date).contactsare the party's contact people (see the contacts sub-resource below). A contact'semailcan benull: the primary contact may have no email (a phone-only party).- A contact's
factor_opt_outsis set only on the primary contact: the factors (insurance,w9) the main contact no longer receives because another contact holds that role. insurance_contactsare the insurance agents entered for the party. They are read-only in v2; the single-agent fields (insurance_contact_name,insurance_contact_email,insurance_contact_phone,insurance_agency_name) are writable.tagsare the party's tags.custom_fieldsis keyed by custom field id and served on the list and the detail alike. It holds only the fields that have a value: a field with no value (never set, cleared, or blank) is absent, never served asnull. A User-type field also carriesuser_id; itsvalueis the user's display name. What each type serves and accepts is in Custom field types.projectslists the projects the party is on.- Under project compliance, each
projects[]entry also carries that project's own status:compliant,compliant_through, andnoncompliantreasons. The party-levelstatus.compliantis then the all-projects aggregate. - On accounts with projects but without project compliance, each
projects[]entry carriespass_fail(compliant,marked_compliant,not_compliantorno_requirement). certificatesis a summary. An invalidated certificate stays in it withcanceledtrue: it is on file but no longer counts toward compliance. Documents moved out of the certificate flow and tax forms uploaded as certificates are excluded, exactly as on Certificates. Its parsed membersdocument_type,effective_onandexpires_onarenulluntil the certificate'sstatusisProcessed, as on the certificate itself.note_to_partyis the note shown to the party in its request emails and on its upload page.compliance_scoreandfactor_scores({"insurance": {"score", "satisfied"}, ...}) are the party's multi-factor compliance scores.portal_urlis the party's compliance portal, the one page where the party sees and completes every outstanding item. It is a bearer link (see Links), andnullon accounts without multi-factor compliance.outreachlists the reachouts HoundDog is running for the party; see Outreach.- Fields of features your account does not use are present with an empty value (
null,[]or{}).
Custom field types
Every custom field has a type, chosen when the field is created and fixed once the field has a value. The type decides what a write takes in the field's custom_fields entry, what a read serves, and which values are accepted. The API name is the type's name in this API. A value the type refuses is 422 validation_failed naming custom_fields (the message names the field id), and nothing is written.
| Type | API name | What you send | What is served | Validation |
|---|---|---|---|---|
| String | string |
{"value": "Central"}: text, or a JSON number (stored as its decimal text). null, or a blank or whitespace-only string, clears it. |
value: the text. user_id is null. |
Any text. |
| Integer | integer |
{"value": "-12"} or {"value": -12}. null, or a blank or whitespace-only string, clears it. |
value: the whole number as text, such as "-12". user_id is null. |
A whole number: digits with an optional leading minus sign. Spaces around it are removed. A number with a decimal point (12.5, 12.0) is 422 naming custom_fields. |
| User | user |
{"user_id": 301}: one of the account's assignable users. null, or a blank or whitespace-only string, clears it. |
user_id: the assigned user's id. value: their display name. |
One of the account's assignable users. |
The dashboard's forms and spreadsheet imports apply the same validation, so a value stored by any of them can be read and sent back unchanged. Your account's fields, with their ids, types and where they appear, are listed on the Custom fields page.
Outreach
outreach lists every reachout HoundDog is running for the party: one entry per person being asked on each open request, with how far that person's reminders have gone and the page they were sent to. Use it to see where each collection stands, or to build your own reminders. An item that is satisfied, or a request that is completed, has no entry, and outreach is [] when nothing is being asked. A request that has gone unresponsive has no entries either: nothing more is sent on it, even to a recipient below the limit.
Nor does a request for a project whose communications are turned off.
An archived party has none (archiving stops all contact). A paused party keeps its entries, but nothing is sent while it is paused. While your account does not send email to its parties, outreach is [].
| Field | Meaning |
|---|---|
factors |
What the reachout asks for; each kind of entry below gives its values |
recipient |
contact_id (the contact's id in contacts; null for an insurance agent), email_address, and kind: primary (the primary contact), holder (another of the party's contact people who holds a role), escalated (a contact person added because the party did not respond) or agent (an insurance agent) |
cadence |
The reminder schedule the entry follows; each kind of entry below gives its value |
position |
count, last_sent_at, limit and exhausted; see Position |
record |
The record behind the reachout, {"type", "id"}; each kind of entry below gives its type |
link |
The page the recipient was sent to, or null; see Links |
Insurance entries
Each open compliance request has entries with factors ["insurance"], cadence insurance and record {"type": "compliance_request", "id": ...}:
- The party's side. One entry for the primary contact (
kindprimary). A primary contact with no email address can still have an entry, withemail_addressnull: its reminders can only go out by text message, and only on an account that sends text messages. - With contact routing, each contact holding the insurance role has its own entry (
kindholder), and the primary contact keeps its entry unless it is opted out of insurance. - When the party does not respond, a request can escalate to up to two more of the party's contact people (not on multi-factor compliance accounts). Each escalated contact has its own entry (
kindescalated) whosecountstarts at 0, while everyone already asked keeps their position. - An escalated contact person stays on the request's cadence (and keeps its entry) even if the account later turns escalation off.
- The insurance agent. While the request is with the party's insurance agent (the party handed it over, or has no email address of its own and an agent is on file), the party's side is not contacted and has no entries: there is one entry per agent address the request emails (
kindagent,contact_idnull,linkthe agent's own upload page). The agents of one request share onecountand onelast_sent_at. - When the account asks the insurance agent alongside the party from the start, the request's agent entries appear next to the party's entries and follow a schedule of their own.
- On a multi-factor compliance party with two or more required factors, the party's side of the insurance request is part of the combined entry instead (below). Agent entries are unchanged.
- On such a party, when insurance is routed to other contact people, those holders have their own insurance entries and the primary contact has none for insurance: the combined email keeps it informed.
- Under project compliance a party can have one open request per project; each has its own entries.
The combined entry
On a multi-factor compliance party with two or more required factors, the main contact receives one combined email that lists every outstanding item and links to the party's compliance portal. While that email is being sent (not once the party is unresponsive, and not while compliance requests are turned off for your account), it has one entry: cadence combined, recipient the primary contact, record null (the combined email's position is kept on the party, not on one record), link the portal (the same link as portal_url), and factors every item it asks for:
insurancewhile the party's insurance request is open and not with its agent.w9while a W-9 is pending.signing:<template id>for each document waiting for a party signature, by the id of its signing template: one per template, however many of that template's requests are open (the signing entries are per request).
When every outstanding item is assigned to the party's other contact people, the combined email still goes to the main contact as a status update and still counts toward unresponsive, so the entry stays with factors empty. The combined entry's position counts combined emails, not items: it does not say which item is done. Read each factor's progress from factor_scores and from the factor's own records. An item routed to another of the party's contact people leaves the combined email and has its own entries. A party with a single required factor receives no combined email: that factor's own entries apply.
W-9 entries
An entry exists only for a record the follow-up cadence acts on: one created since the cadences went live and, on a multi-factor compliance party, one the profile requires. Older or unrequired records stay pending without an entry, and so does a pending W-9 that already has a file attached (nothing more is sent for it).
Each pending W-9 has entries with factors ["w9"], cadence w9, record {"type": "w9", "id": ...} and link the W-9's form_url: one entry for the primary contact, positions kept per person as on a compliance request.
With contact routing, each contact holding the W-9 role has its own entry (kind holder), and the primary contact keeps its entry unless it is opted out of W-9s.
On a multi-factor compliance party with two or more required factors, a pending W-9 is part of the combined entry unless it is routed to other contact people, who then have their own entries (the primary contact has none for the W-9).
A W-9 routed to other contact people has entries whether or not the profile still requires it: its reminders continue until it is completed or deleted.
Signing entries
An entry exists only for a record the follow-up cadence acts on: one created since the cadences went live and, on a multi-factor compliance party, one the profile requires. Older or unrequired records stay pending without an entry, and so does a request whose purpose the party already meets (the cadence marks it no longer needed instead of reminding).
Each open signing request has one entry per party seat that has not signed and is being asked (in a sequential document, only the next signer): factors ["signing:<template id>"], cadence signing, record {"type": "signing_request", "id": ...} and link null. kind is holder when the seat's address belongs to another of the party's active contact people (contact_id is that contact), otherwise primary (contact_id is the primary contact when the address is its own, and null when the seat's address matches no current contact). Each seat keeps its own position (the recipient's contact_count and last_contacted on the signing request). Fixed seats, filled by people outside the party such as your own countersigner, have no entry.
On a multi-factor compliance party with two or more required factors, a seat addressed to the primary contact is part of the combined entry; a seat routed to another contact person has its own entry.
A seat routed to another contact person has its entry whether or not the profile requires the template: its reminders continue until it is signed or the request is closed.
Position
countis how many reminders this recipient has received on this cadence, the first one included. For the primary contact, a reminder text counts too, on an account that sends text messages; a text sent because of an event (a certificate on file, a rejected certificate) does not.- A reminder to an address suppressed by Email Health still counts: the cadence moves on although that email was not sent.
last_sent_atis the clock the cadence consults before its next send: when the record last sent, or for the combined entry when the combined email last went out. It can be set while this recipient'scountis 0 (a contact person added after the last send). A compliance request keeps one clock for all the people it asks, so on its entries it is when the request last sent to any of them; the agents of a request share their own clock.- A W-9 likewise keeps one clock for all the people it asks.
- A signing seat keeps its own clock.
- The combined entry reads the combined email's own count and clock.
limitis the account's contact limit: how many reminders one recipient receives on one cadence. It is the same for every entry.exhaustedis true oncecountreacheslimit: this cadence sends that recipient nothing more. A recipient assigned later starts at 0, so one exhausted entry does not mean the request has stopped. When every recipient of a request is exhausted the party can become unresponsive (status.unresponsive).
Links
link is the HoundDog page the recipient was sent to:
- For the party's side of an insurance request: the request's upload page, the same link as the compliance request's
upload_url. - For an insurance agent: the agent's own upload page for the request (a different page from the party's).
- For the combined entry: the party's compliance portal, the same link as
portal_url. On a multi-factor compliance party the upload page opens the portal at its insurance item. - For a W-9 entry: the W-9 request page, where the party signs the form online or uploads a file, the same link as the W-9's
form_url. - For a signing seat:
null. The API never serves a signing link. The signer reaches the document through the email they were sent (usePOST /signing_requests/:id/resendto send it again). - On a multi-factor compliance party the compliance portal also reaches every document waiting for its signature.
Every link the API serves is a bearer link: anyone holding it can act as the party on that page (upload documents, answer the request). Treat it like a password: share it only with the party, and never log it.
GET /parties
Lists parties, newest id last (ordered by id).
| Parameter | Meaning |
|---|---|
lifecycle[] |
Any of active, paused, incomplete, archived, jobtread_archived. When lifecycle[] is omitted, active applies even when status[] is given (the dashboard drops that default whenever a status chip is selected; the API does not) |
status[] |
Any of compliant, marked_compliant, in_progress, unresponsive, no_requirements |
updated_since |
ISO-8601 time; parties edited since then |
external_id, external_id_2, external_id_3, email, slug |
Exact-match lookups |
insurance_requirement_id |
Parties on that requirement |
tag_id |
Parties carrying that tag itself |
project_id |
Parties on that project |
compliance_profile_id |
Parties on that compliance profile |
q |
Free-text search on the name |
include |
detail to return the detail entity (page cap 50) |
page, per_page |
See Pagination |
Values inside one of lifecycle[] or status[] combine with OR; the two lists, and every other filter, combine with AND. So lifecycle[]=active&lifecycle[]=paused&status[]=in_progress&status[]=unresponsive means "active or paused, and non-compliant", and status[]=unresponsive alone means "active and unresponsive". Send every lifecycle you want (for example lifecycle[]=active&lifecycle[]=paused) to widen it.
tag_id matches the tag itself, not its child tags: a party carrying only a child of that tag is not listed.
updated_since returns parties edited since that time and MAY also return parties whose compliance was recomputed (some recomputes touch updated_at, others do not). It is not a status feed: to follow status, read GET /events?since_id= (see Events) or subscribe to Vendor Became Compliant and Vendor Became Noncompliant webhooks.
A change to a party's custom fields counts as an edit: setting, changing or clearing a value (through the API, the dashboard, an import or an integration) moves the party's updated_at, so updated_since returns it. 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).
GET /api/v2/parties?status[]=unresponsive&per_page=200
{ "data": [ { "id": 191537, "name": "Acme Electric", "...": "...", "status": { "...": "..." } } ], "total": 12, "has_more": false, "next_page": null }
Errors: 400 validation_failed (unknown filter value, per_page above the cap), 401, 403, 422 validation_failed (a tag_id, project_id, insurance_requirement_id or compliance_profile_id that is not in your account, naming the field), 429.
GET /parties/:id
Returns the detail entity. Errors: 404 not_found (no such party, or another account's).
POST /parties
Creates a party. JSON body:
{
"name": "Acme Electric",
"email_address": "[email protected]",
"contact_first_name": "Dana",
"contact_last_name": "Ruiz",
"phone_number": "555-0100",
"address": "12 Main St",
"city": "Austin",
"state": "TX",
"postal_code": "78701",
"external_id": "PO-1234",
"insurance_requirement_id": 42,
"tags": [5],
"custom_fields": { "12": { "value": "Central" }, "13": { "user_id": 301 } },
"projects": [ { "project_id": 9, "insurance_requirement_id": 42 } ],
"note_to_party": "Please list Tower B as the certificate holder.",
"archive_date": "2027-01-31"
}
Writable fields: name (required), email_address, contact_first_name, contact_last_name, phone_number, external_id, notes, started_in_compliance, manual_expiration_date, insurance_contact_name, insurance_contact_email, insurance_contact_phone, insurance_agency_name, insurance_requirement_id, and the fields of the features below. Anything else is ignored. Files are never accepted here: upload certificates with POST /certificates.
Fields that need an account feature (a gated field on an account without the feature is 403 feature_disabled: the message names the feature and details names the field, see Errors; sent with the value the API serves for the party (null and an empty string counting as equal), or empty on a create (null, "", [], {}, or false for an on/off field), it is accepted and changes nothing, so a GET body can be sent back as read):
address,address2,city,state,postal_code: editing of party addresses (enable_editing_of_vendor_addresses).tags: tags (enable_tags).custom_fields: custom fields (enable_custom_fields).projects: projects (enable_projects).- A
projects[]entry's owninsurance_requirement_id(or, under multi-factor compliance, itscompliance_profile_id): project compliance with project-specific requirements (enable_project_complianceandenable_project_compliance_specific_requirements), as on the dashboard's form; there an omitted ornullvalue clears it, and an archived compliance profile is not accepted on a new link. Elsewhere it is never changed (see the next item). - Where a
projects[]entry's owninsurance_requirement_idorcompliance_profile_idis not offered, a request never changes it: an omitted key, or the link's stored value sent back unchanged (nullincluded, andnullfor a new link), is accepted as a no-op, so theprojectsof aGETcan be sent back as read. Any other value is 403feature_disabled,detailsnamingprojects[].insurance_requirement_idorprojects[].compliance_profile_id. note_to_party: party notes (enable_party_notes).archive_date: scheduled auto-archive (enable_auto_archive).compliance_profile_id: multi-factor compliance (enable_multi_factor_compliance).collect_initial_w9: W-9 collection (enable_w9_collection).insurance_requirement_ids: multiple insurance requirements (enable_multiple_insurance_requirements), the whole set enforced against the party, asrequirements.insurance_requirement_idsserves it.
The primary contact is the party's own email_address, contact_first_name and contact_last_name; add more people with the contacts sub-resource after the party exists.
Requirement defaults:
insurance_requirement_idsis the whole instruction: it replaces the set, the first id becomesrequirements.insurance_requirement_id(the primary every older field reports), and no default is applied.insurance_requirement_idsent beside it is 422validation_failednaminginsurance_requirement_id. Each id must be an insurance requirement of your account and not an individual's, or the request is 422 naminginsurance_requirement_ids; an id the party already holds is always accepted. An empty list means "No Requirements", under the same rule as an explicitnullbelow.requirements.insurance_requirement_idsis the one requirementinsurance_requirement_idalready reports, as a list.insurance_requirement_idsmay only be sent with exactly that value (a no-op, so aGETbody can be sent back as read); any other value is 403feature_disabled.insurance_requirement_idomitted means the account's default. Omitting it is never a choice of "No Requirements".- An explicit
nullmeans "No Requirements", accepted only when the account allows that option; otherwise 422. - An account with no default requirement and no
insurance_requirement_idin the request is 422validation_failednaminginsurance_requirement_id. - Under multi-factor compliance the same rules apply to
compliance_profile_idinstead, andinsurance_requirement_idis rejected (422): the compliance profile decides the requirement.
manual_expiration_date is stored as a time: send a date (2027-01-31) and read back the start of that day, US Central time (2027-01-31T06:00:00Z), as for an approval override's end_date.
archive_date is stored the same way. Where the field is not enabled on your account, archive_date round-trips as the timestamp you read: send that value back as served. A calendar date written for a different time zone may name another day and is then a change.
Side effects: the party's compliance is computed at once. When the party needs documents, a compliance request is created and the initial request email (or SMS) goes out to the party, as when a person adds a party on the dashboard, unless the account has vendor email turned off.
On a multi-factor compliance account with two or more required factors the party receives one combined email instead.
A W-9 request is created when the account collects W-9s from new parties.
A Party Added event is recorded with your key as the actor, which fires any Party Added webhooks.
Response: 201 with the detail entity. Errors: 400, 403 feature_disabled, 422 validation_failed (a referenced id not in your account, a duplicate external id where the account enforces unique external ids, the rules above).
PATCH /parties/:id
Updates the fields you send and leaves the rest alone. Same fields and feature gates as the create, except that collect_initial_w9, started_in_compliance and manual_expiration_date apply on create only and are ignored on PATCH.
The entity a GET serves can be sent back as read. A PATCH of the whole party entity, unchanged, answers 200 and changes nothing (no event, no compliance request, no email). Members that are not body fields are ignored whatever they hold: id, slug, external_id_2, external_id_3, created_at, updated_at, status, requirements, noncompliant, expirations, contacts, insurance_contacts, certificates, compliance_score, factor_scores, portal_url, outreach and links (change contacts with the contacts sub-resource below). The members you can write accept the shape they are served in:
archive_datetakes a date or a timestamp. Sent as served (the same instant in any offset, or the stored time's US Central date) it is not written, so the stored time never moves. A different date, or a timestamp of a different instant, is stored as the start of its US Central day.tagstakes tag type ids or the tag objects aGETserves ({"id": 5, "name": "Electrical"}), or a mix. From an object onlyidis read; anamethat differs from the tag's name is ignored and never renames it. An object without anidis 400validation_failednamingtags.- A
custom_fieldsentry may be sent as served,nameand all (nameis ignored). A
projects[]entry may be sent as served: itsname,compliant,compliant_through,noncompliantandpass_failare ignored.tagsandprojectsare sets: a key that is present replaces the whole set, a key that is omitted leaves the set untouched, and[]clears it.custom_fieldsis a patch, on the create and the update alike: each field id you send is written, every field you leave out keeps its value. To clear a field, send it asnull,{"value": null}or{"user_id": null}(a blank or whitespace-onlyvalueoruser_idclears 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": nullchange nothing; there is no "clear all", so clear each field by id. Only your account's active fields available on parties are accepted: any other id is 422validation_failednamingcustom_fields(an archived field's id included, with the message<id> is archived), and so is the same field id sent twice. An archived field is not served; its values are kept and are served again once the field is restored.A custom field takes
{"value": "..."}(a JSON number is stored as its decimal text;nullclears it), or{"user_id": n}for a User-type field. The field's type can restrict the value (an Integer field takes whole numbers only); see Custom field types. The user must be one of the account's assignable users. The entry aGETserves carries both members, and the one the field does not take is ignored when it is what was served: on a field that takesvalue, auser_idofnull; on a User-type field (alongsideuser_id), avaluethat isnull, the display name served for the field, or the display name of the user you send. Any other shape (auser_idon a field that takesvalue, a User-type field withoutuser_idor with some othervalue) is 422 namingcustom_fields.insurance_requirement_id: nullmoves the party to No Requirements, only where the account allows it (otherwise 422). Anullsent for a party that already has no requirement is accepted and changes nothing, so therequirements.insurance_requirement_idaGETserves can be sent back as read.Under project compliance (without multi-factor compliance) a party-level
insurance_requirement_id: nullis always 422, even for a party that has none: omit the key to leave it alone.Under multi-factor compliance,
compliance_profile_id: nulldoes the same, and anullsent for a party that already has no compliance profile is likewise accepted and changes nothing.Compliance is recomputed after the update unless the requirement changed (a requirement change runs its own recompute).
Under multi-factor compliance a change of
compliance_profile_idis recomputed in the request too, so the response already shows the new profile's compliance.
Response: 200 with the detail entity. Errors: 404, 403 feature_disabled, 422 validation_failed.
Also 409 conflict when another change stored the first value of the same custom field of this party at the same moment: nothing was written and the error carries no conflict member, so send the request again.
Party actions
Each action answers 200 with the party detail entity. Each records the same event the dashboard records, with your key as the actor. Repeating a lifecycle action is 422 validation_failed: archiving an archived party, unarchiving one that is not archived, pausing a paused party, unpausing one that is not paused.
POST /parties/:id/archive
Archives the party: open compliance requests are completed, any triage entry is resolved, and all contact stops. Where the account is connected to JobTread the vendor is set to "Not Tracked" there. Event: Party Archived.
POST /parties/:id/unarchive
Restores an archived party and recomputes its compliance, which can start a new request and email the party. Event: Party Unarchived.
POST /parties/:id/pause
Pauses all outreach to the party. Needs pausing enabled on the account (enable_pausing); 403 feature_disabled otherwise. Event: Party Paused.
POST /parties/:id/unpause
Resumes outreach to a paused party and recomputes compliance. Always allowed for a paused party, whether or not pausing is enabled on the account: the dashboard offers Unpause for any paused party, and a duplicate merged into another party is left paused (unarchiving it does not unpause it). Event: Party Unpaused.
POST /parties/:id/invalidate_certificates
Invalidates every active certificate of the party (they stay on file but no longer count) and recomputes compliance, which can make the party non-compliant and start a new request. The certificates' canceled_by reads API key "<key name>".
POST /parties/:id/send_reminder
Sends a manual reminder for the party's open insurance request, exactly as the dashboard's reminder button.
| Body field | Meaning |
|---|---|
project_id |
Required under project compliance: the project whose request to remind |
recipient_type |
party_only (default), insurance_only (the insurance agents), or both |
On a multi-factor compliance party with two or more required factors the combined compliance reminder is sent instead (one email listing every outstanding item) and recipient_type does not apply.
The reminder is vendor outreach: it respects the account's vendor email setting, and it counts in the request's contact counters. It also respects email suppression: a suppressed address is not emailed.
Errors (each 422 validation_failed):
- The party is already compliant, or has no open request.
project_idis missing under project compliance, or names a project not in your account. It is a body field, so a miss is 422 namingproject_id, never 404.- For the combined reminder, the refusals the dashboard applies (the dashboard's messages name the party; the API's are the same, reworded without the name):
- nothing is outstanding for the party (no reminder sent)
- the main contact's address is suppressed by Email Health (no reminder sent)
- a reminder was already sent to the party within the last hour
- the party has no email address on file, or every outstanding item is assigned to the party's other contact people (who follow their own reminder cadence) or waits on a renewal request that has not been created.
GET /parties/:id/events
The party's events, oldest first (by id), in the collection envelope. Same entity and filters as GET /events without party_id.
Contacts sub-resource
A party's contact people. The primary contact is the party's own email_address and name and is always present.
Adding a contact, deleting one, and editing a contact other than the primary need contact people on your account, as the dashboard's Contact People card does: contact routing (enable_contact_routing), or contact escalation (enable_multiple_contacts) on an account without multi-factor compliance. Otherwise they are 403 feature_disabled. Reading the list and editing the primary contact's name, email and phone are always allowed.
On your account, adding a contact, deleting one, and editing a contact other than the primary are 403 feature_disabled (contact people are not enabled). Reading the list and editing the primary contact's name, email and phone work.
Without contact people, a PATCH of a contact other than the primary that sends every field with the value served (null and an empty string counting as equal) is accepted: 200 with the contact, nothing written, no event. Any change is 403 feature_disabled.
| Field | Meaning |
|---|---|
id |
Contact id |
first_name, last_name, email, phone |
The person |
roles |
Role keys from the account's vocabulary: insurance, w9, signing, and signing_recipient:<template recipient id> for one seat of one signing template |
active |
An inactive contact receives nothing |
opted_out |
The person asked not to be contacted |
is_primary |
Read-only. Change the primary through the party's own email_address |
factor_opt_outs |
Read-only, primary only (see the entity notes above) |
On an account without contact routing, roles and factor_opt_outs read empty and sending roles is 403 feature_disabled; sending the empty roles you read ([], on a create too) is accepted and changes nothing.
GET /parties/:id/contacts
The party's contacts in the collection envelope.
POST /parties/:id/contacts
Adds a contact. Body: first_name, last_name, email (required), phone, roles, active, opted_out. Response 201 with the contact. Errors: 403 feature_disabled (roles other than [] without contact routing), 422 validation_failed.
PATCH /parties/:id/contacts/:contact_id
Updates the fields you send. roles, when present, replaces the contact's roles.
A contact a GET serves can be sent back as read: id, is_primary and factor_opt_outs are read-only and ignored when sent, and the other fields sent with their served values change nothing.
On the primary contact, first_name, last_name, email and phone update the party's own contact_first_name, contact_last_name, email_address and phone_number, exactly as a PATCH /parties/:id of those fields would. The primary's active and opted_out cannot be changed: sent with the value served (true and false) they are ignored, and any other value is 422 validation_failed naming the field.
active and opted_out cannot be null (400 validation_failed), here and on POST.
DELETE /parties/:id/contacts/:contact_id
Removes a contact. Response 204. Deleting the primary contact is 422 validation_failed.
Assigning a role adds a recipient for that factor; the primary keeps receiving it unless it is opted out of that factor. See the dashboard's Contact People card for the same rules.