Skip to main content
HoundDog API Documentation

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:

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/:id route, and their ids sent as party_id anywhere else (a body or a query filter) are 422 validation_failed naming party_id.
  • There is no vendors alias. An authenticated request under /api/v2/vendors answers 404 not_found with 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:

  • name can be null for an incomplete party (status.incomplete true).
  • status holds every lifecycle and compliance flag. manually_compliant is meaningful only while compliant is true (the party was marked compliant by a person or an override). no_requirements is true when the party has no requirement to meet; such a party is neither compliant nor non-compliant.
  • requirements.compliance_profile is the party's compliance profile, {"id", "name"}.
  • noncompliant lists each failing coverage (a coverage prefix such as gl, auto, wc) with the reasons the compliance engine recorded.
  • In noncompliant, coverage is null for the project compliance summary entry "Not Compliant with <project>".
  • expirations come from the party's current certificates (one entry per required coverage that has an expiration date).
  • contacts are the party's contact people (see the contacts sub-resource below). A contact's email can be null: the primary contact may have no email (a phone-only party).
  • A contact's factor_opt_outs is set only on the primary contact: the factors (insurance, w9) the main contact no longer receives because another contact holds that role.
  • insurance_contacts are 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.
  • tags are the party's tags.
  • custom_fields is 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 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.
  • projects lists the projects the party is on.
  • Under project compliance, each projects[] entry also carries that project's own status: compliant, compliant_through, and noncompliant reasons. The party-level status.compliant is then the all-projects aggregate.
  • On accounts with projects but without project compliance, each projects[] entry carries pass_fail (compliant, marked_compliant, not_compliant or no_requirement).
  • certificates is a summary. An invalidated certificate stays in it with canceled true: 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 members document_type, effective_on and expires_on are null until the certificate's status is Processed, as on the certificate itself.
  • note_to_party is the note shown to the party in its request emails and on its upload page.
  • compliance_score and factor_scores ({"insurance": {"score", "satisfied"}, ...}) are the party's multi-factor compliance scores.
  • portal_url is the party's compliance portal, the one page where the party sees and completes every outstanding item. It is a bearer link (see Links), and null on accounts without multi-factor compliance.
  • outreach lists 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 (kind primary). A primary contact with no email address can still have an entry, with email_address null: 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 (kind holder), 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 (kind escalated) whose count starts 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 (kind agent, contact_id null, link the agent's own upload page). The agents of one request share one count and one last_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:

  • insurance while the party's insurance request is open and not with its agent.
  • w9 while 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

  • count is 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_at is 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's count is 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.
  • limit is the account's contact limit: how many reminders one recipient receives on one cadence. It is the same for every entry.
  • exhausted is true once count reaches limit: 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).

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 (use POST /signing_requests/:id/resend to 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 own insurance_requirement_id (or, under multi-factor compliance, its compliance_profile_id): project compliance with project-specific requirements (enable_project_compliance and enable_project_compliance_specific_requirements), as on the dashboard's form; there an omitted or null value 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 own insurance_requirement_id or compliance_profile_id is not offered, a request never changes it: an omitted key, or the link's stored value sent back unchanged (null included, and null for a new link), is accepted as a no-op, so the projects of a GET can be sent back as read. Any other value is 403 feature_disabled, details naming projects[].insurance_requirement_id or projects[].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, as requirements.insurance_requirement_ids serves 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_ids is the whole instruction: it replaces the set, the first id becomes requirements.insurance_requirement_id (the primary every older field reports), and no default is applied. insurance_requirement_id sent beside it is 422 validation_failed naming insurance_requirement_id. Each id must be an insurance requirement of your account and not an individual's, or the request is 422 naming insurance_requirement_ids; an id the party already holds is always accepted. An empty list means "No Requirements", under the same rule as an explicit null below.
  • requirements.insurance_requirement_ids is the one requirement insurance_requirement_id already reports, as a list. insurance_requirement_ids may only be sent with exactly that value (a no-op, so a GET body can be sent back as read); any other value is 403 feature_disabled.
  • insurance_requirement_id omitted means the account's default. Omitting it is never a choice of "No Requirements".
  • An explicit null means "No Requirements", accepted only when the account allows that option; otherwise 422.
  • An account with no default requirement and no insurance_requirement_id in the request is 422 validation_failed naming insurance_requirement_id.
  • Under multi-factor compliance the same rules apply to compliance_profile_id instead, and insurance_requirement_id is 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_date takes 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.
  • tags takes tag type ids or the tag objects a GET serves ({"id": 5, "name": "Electrical"}), or a mix. From an object only id is read; a name that differs from the tag's name is ignored and never renames it. An object without an id is 400 validation_failed naming tags.
  • A custom_fields entry may be sent as served, name and all (name is ignored).
  • A projects[] entry may be sent as served: its name, compliant, compliant_through, noncompliant and pass_fail are ignored.

  • tags and projects are sets: a key that is present replaces the whole set, a key that is omitted leaves the set untouched, and [] clears it.

  • custom_fields is 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 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. Only your account's active fields available on parties are accepted: any other id is 422 validation_failed naming custom_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; null clears 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 a GET serves carries both members, and the one the field does not take is ignored when it is what was served: on a field that takes value, a user_id of null; on a User-type field (alongside user_id), a value that is null, the display name served for the field, or the display name of the user you send. Any other shape (a user_id on a field that takes value, a User-type field without user_id or with some other value) is 422 naming custom_fields.

  • insurance_requirement_id: null moves the party to No Requirements, only where the account allows it (otherwise 422). A null sent for a party that already has no requirement is accepted and changes nothing, so the requirements.insurance_requirement_id a GET serves can be sent back as read.

  • Under project compliance (without multi-factor compliance) a party-level insurance_requirement_id: null is always 422, even for a party that has none: omit the key to leave it alone.

  • Under multi-factor compliance, compliance_profile_id: null does the same, and a null sent 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_id is 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_id is missing under project compliance, or names a project not in your account. It is a body field, so a miss is 422 naming project_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.