Migrating from v1
v1 keeps working unchanged alongside v2, with the same API keys; no sunset date is set.
Every response from a v1 endpoint carries two headers that mark it as deprecated: Deprecation: @1798761600 (2027-01-01, the date v1 is deprecated from, in the date form of RFC 9745: @ and Unix seconds; v1 keeps working after it) and a Link header with rel="deprecation" pointing at this page. Their bodies and status codes are unchanged, and no Sunset header is sent. Clients that log or alert on these headers can use them to find any v1 calls left in their code.
Every v1 endpoint and its v2 page
| v1 | v2 | Page |
|---|---|---|
GET /api/v1/vendors |
GET /api/v2/parties (enveloped, see Pagination) |
Parties |
GET /api/v1/vendors/:id |
GET /api/v2/parties/:id |
Parties |
GET /api/v1/vendors/external_id/:external_id |
GET /api/v2/parties?external_id=<value> (a collection; empty when nothing matches) |
Parties |
POST /api/v1/vendors/create (form-encoded) |
POST /api/v2/parties (JSON body) |
Parties |
GET /api/v1/certificates |
GET /api/v2/certificates |
Certificates |
GET /api/v1/certificates/:id |
GET /api/v2/certificates/:id, which also returns the parsed coverage data; the file is at GET /api/v2/certificates/:id/download |
Certificates |
GET /api/v1/documents |
GET /api/v2/documents |
Documents |
GET /api/v1/documents/vendor/:id |
GET /api/v2/parties/:id/documents |
Documents |
GET /api/v1/documents/:id |
GET /api/v2/documents/:id |
Documents |
POST /api/v1/documents/create |
POST /api/v2/documents |
Documents |
GET /api/v1/events |
GET /api/v2/events (with filters and the since_id cursor) |
Events |
GET /api/v1/events/:id |
GET /api/v2/events/:id |
Events |
GET /api/v1/insurance_requirements |
GET /api/v2/insurance_requirements |
Insurance requirements |
GET /api/v1/insurance_requirements/:id |
GET /api/v2/insurance_requirements/:id |
Insurance requirements |
GET /api/v1/projects |
GET /api/v2/projects |
Projects |
GET /api/v1/projects/:id |
GET /api/v2/projects/:id |
Projects |
Every /create path in v1 becomes a POST to the collection in v2. Authenticated requests to /api/v2/vendors answer 404 with a pointer here.
Names and shapes that change
List pagination changes too: v2 lists page with page and per_page only, with a larger default and cap, and have no offset parameter (see Pagination).
| v1 | v2 |
|---|---|
vendors (path) |
parties |
vendor_id in payloads |
party_id |
compliant and in_review on a vendor |
status.compliant and status.in_review, alongside the other state flags (manually_compliant, unresponsive, no_requirements, archived, paused, ...) |
insurance_requirement_id and compliance_profile on a vendor |
requirements.insurance_requirement_id and requirements.compliance_profile |
a vendor's projects: [{ "id", "name" }] |
projects: [{ "project_id", "name", ... }] with the per-project requirement and status |
an event's nested vendor and user objects |
party_id and user_id; each event also carries its id and created_at |
a project's nested insurance_requirement object |
insurance_requirement_id |
document_file in a document |
file_url, a short-lived link |
email on vendor create |
email_address |
document_file (document create) |
file |
GET /events?user_id= |
GET /events?user_id= (kept) |
collect_compliance_on |
started_in_compliance: true with manual_expiration_date; in v1 the field had no effect on its own |
project_ids as a comma-separated string |
projects: [{ "project_id": 1 }, ...] with optional per-project requirement and profile |
insurance_requirement_id omitted |
same default behavior: the account's default requirement (or default compliance profile) |
Vendor Added event for API-created parties |
Party Added, the same event the dashboard records |
offset on list requests (answered with X-Offset and X-Prev-Page headers, neither of which v2 sends) |
no offset: page with page and per_page, see Pagination |
per_page default 20, maximum 50 |
per_page default 50, maximum 200 (50 when include=detail is requested); above the cap is a 400 |
| Bare JSON arrays | { "data": [...], "total", "has_more", "next_page" } |
{"error": "..."} |
{"error": {"code", "message", "request_id", "details"}}, see Errors |
| 400 "Malformed Request" for almost everything | precise 400 / 401 / 403 / 404 / 409 / 422 / 429 codes |
| Certificate payload with the file reference only | the parsed coverage data once the certificate is processed, in one coverages map keyed by coverage prefix; the file through a download link |
Requirement coverage fields as flat {prefix}_{field} keys (gl_per_occurance_minimum) |
one coverages map nested by prefix: {prefix}_{field} becomes coverages.<prefix>.<field>, the prefix removed and the rest of the name kept as is. So gl_per_occurance_minimum is coverages.gl.per_occurance_minimum and wc_employers_liability_each_accident_minimum is coverages.wc.employers_liability_each_accident_minimum; see Insurance requirements |
Webhook payload {event, payload: {vendor_id, ...}} |
v2 subscriptions receive the signed payload on the Webhooks page; existing subscriptions are unchanged |
New in v2
Everything v1 could not do: update parties, the party actions (archive, pause, reminders, certificate invalidation), contacts, certificate upload, approval overrides, compliance requests, requirement management, webhook management, account settings and users. Project management, including attaching parties. Compliance profiles. W-9s. Document signing. Requirement statuses and idempotency keys.
What does not change
- Your API key works for both versions; no re-keying. A key's scope applies to both: a key created with the
readscope cannot make v1 writes either (403). - Event type strings are the same in both versions and in webhooks.
- Ids are the same integers; a v1 vendor id is a v2 party id. The lists are not the same set: v1
GET /vendorsreturned every party (archived, paused and incomplete ones too); v2GET /partiesdefaults tolifecycle[]=active(for everything send every lifecycle value:lifecycle[]=active&lifecycle[]=paused&lifecycle[]=incomplete&lifecycle[]=archived&lifecycle[]=jobtread_archived). - v1
GET /certificatesreturned every certificate row; v2GET /certificatesleaves out documents moved to a party's insurance documents and tax forms uploaded as certificates (see Certificates). - v1
GET /vendorsalso returned your individuals; v2 never serves them, and their ids answer 404.
Requests to /api/v2/vendors
v2 has no vendors alias. An authenticated request to /api/v2/vendors answers 404 with a message pointing here; a request with no key or an invalid key gets 401, and a deactivated or API-disabled account 403, as for every other route.