COI tracking and insurance compliance, fully programmable
The Billy API lets you automate certificate of insurance (COI) tracking, vendor compliance, and requirement management from any system: your ERP, project management platform, or custom application. Conventional HTTP, JSON in and out, RESTful design.
Request API Access Become a PartnerAutomate payment compliance
Connect Billy to your accounting system or ERP so finance can verify vendor compliance before releasing payment, without ever leaving their tools.
Sync vendors & projects
Keep vendors, projects, and contracts in lockstep between Billy and Procore, Sage, Viewpoint, or your own internal systems using bulk endpoints.
Build compliance dashboards
Pull real-time COI status, policy limits, and requirement data into BI tools and custom reporting for a live view of insurance risk.
Why Billy API?
Traditionally, managing compliance documents is a manual, time-consuming process fraught with potential errors and omissions that lead to risk transfer. The Billy API transforms this process by automating the collection, processing, and tracking of all necessary compliance documents between multiple systems.
With Billy, general contractors can focus more on the job at hand and less on the paperwork, ensuring that every subcontractor and vendor easily meets the required standards.
Developer Personas
The two primary developer personas interacting with the Billy API are Billy Partners and Technology Partners. You likely fall into one of these categories, and your specific integration goals and requirements can be characterized by one of these two developer personas.
Billy Partners
Billy Clients are individuals and organizations that have one or more Billy accounts and utilize Billy for their construction operations. It is not uncommon for clients to rely on several other software tools and processes in addition to Billy to manage their projects. For example, a large general contracting firm may use Sage 300 ERP or Viewpoint Vista while using Billy to manage payment compliance.
In this scenario, the GC could hire a development team to build a custom integration between their ERP systems and Billy using this Billy API, thereby improving efficiency and upgrading the user experience for their employees and vendors.
Applications and integrations produced by Billy clients are intended for their internal use and are not normally published to other Billy Partners.
Technology Partners
Billy Technology Partners leverage the Billy API to provide Billy users with a best-in-class experience, irrespective of what problem they are seeking to solve. Technology Partners benefit from exclusive access to the following resources:
- Developer training and support
- Dedicated Slack channel
- Development sandbox
- Co-branding and marketing of your integration with Billy Partners
Paths to Success
Billy partners manage their integration development milestones and releases. Since their custom integrations are only used internally, they have complete control over quality, performance, and reliability requirements. Billy partners also manage the internal training needs for their users.
The Billy API has already begun transforming the compliance process for general contractors across the industry. By replacing complex processes with Billy’s simplified solution, managing construction insurance and compliance becomes faster, easier, and more efficient.
Authentication
All API requests (except authentication itself) require a valid Bearer token in the Authorization header. Request a token with your client credentials; tokens are valid for 120 minutes (7,200 seconds). When your token expires, simply request a new one.
Request Access Token
POST/api/v1/authentication/token
Exchange your client_id and client_secret for a Bearer token.
Request body
{
"grant_type": "basic",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET"
}
Response example
{
"access_token": "7NCvlBqctl+g3D+92EMhZELDkXGbDsNuZol0IudSQSKYL9veGgnZDIhSEsJF/1uA",
"expires_in": 7200,
"token_type": "Bearer"
}
Then include the token on every subsequent request:
curl --request GET \
--url https://{your_base_url}/api/v1/projects \
--header 'Authorization: Bearer YOUR_ACCESS_TOKEN'
Pagination, Filtering & Errors
All list endpoints are paginated. Use page to navigate and per_page to change the page size (default 100). You can also filter any list endpoint by updated_at using a since parameter with an ISO 8601 datetime, which is ideal for incremental syncs.
curl --request GET \
--url {environment_url}/api/v1/projects/$PROJECT_ID/contracts?page=1&per_page=50&since=2024-12-31T00:00:00 \
--header 'Authorization: Bearer $SESSION_TOKEN'
| Response header | Type | Description |
|---|---|---|
total-count | integer | Total count of records |
total-page | integer | Total number of pages that can be queried |
has-more | boolean | true when there are more records beyond the current page |
| Status | Meaning |
|---|---|
400 Bad Request | A required parameter was missing or invalid |
401 Unauthorized | The access token is missing or invalid |
404 Not Found | The specified resource does not exist |
422 Unprocessable Entity | The request body failed validation |
428 Precondition Required | A precondition was not met (e.g. rate limit) |
500 Internal Server Error | A server error occurred |
Team
Information about the authenticated token’s team, including its slug and an environment-based base_ui_url. Clients can build deep links to the Billy web app using the pattern <base_ui_url>/teams/<slug>.
Get Team
GET/api/v1/team
Response example
{
"team_id": 123,
"name": "Acme Construction",
"slug": "acme-construction",
"email": "contact@acme.example.com",
"website_url": "https://acme.example.com",
"base_ui_url": "{your_environment_url}"
}
Projects
Projects represent construction or development sites that require insurance coverage.
List All Projects
GET/api/v1/projectsReturns all projects for your team. Supports pagination.
Get Project by ID
GET/api/v1/projects/:project_idproject_id is the Billy system ID of the project.
Create Project
POST/api/v1/projectsRequest body example
{
"external_id": "some_external_id_3",
"name": "My Project Name",
"number": "123-Project",
"status": "draft",
"address": {
"city": "city",
"country": "US",
"state": "TN",
"street_1": "123 street1",
"street_2": "123 street2",
"zip": "12345"
}
}
Update Project
PATCH/api/v1/projects/:project_idRequest body example
{
"external_id": "some_external_id",
"name": "My Project Name Updated",
"number": "123-Project",
"address": {
"city": "city",
"country": "US",
"state": "TN",
"street_1": "123 street1",
"street_2": "123 street2",
"zip": "12345"
}
}
List Project Additional Insureds
GET/api/v1/projects/:project_id/additional_insuredsResponse example
[
{
"company_name": "my company name",
"default": true,
"id": null,
"inserted_at": "2025-11-18T18:59:31.093868Z",
"updated_at": "2025-11-18T18:59:31.093886Z"
},
{
"company_name": "my other additional insured company name",
"default": false,
"id": 12343,
"inserted_at": "2025-11-18T18:59:31.093868Z",
"updated_at": "2025-11-18T18:59:31.093886Z"
}
]
Create Project Additional Insured
POST/api/v1/projects/:project_id/additional_insuredsRequest body
{
"company_name": "my new additional insured company name"
}
Update Project Additional Insured
PATCH/api/v1/projects/:project_id/additional_insureds/:idRequest body
{
"company_name": "my updated additional insured name"
}
Delete Project Additional Insured
DELETE/api/v1/projects/:project_id/additional_insureds/:idVendors
Vendors are contractors or service providers that need to be insured for projects. Each vendor carries its compliance status, contacts, and requirement group.
List Vendors
GET/api/v1/vendorsSupports pagination query parameters.
Response example
[
{
"address": {
"city": "Nashville",
"country": "US",
"state": "TN",
"street_1": "1234 St",
"street_2": null,
"zip": "37411"
},
"company_name": "Test Vendor",
"compliance": [
{
"notes": null,
"policy": null,
"requirement_id": 4141,
"requirement_kind": "document",
"requirement_type_name": "W-9",
"reviewed_at": null,
"status": "inactive"
}
],
"contacts": [
{
"email": "test@testvendor.com",
"first_name": "Test",
"id": 62784,
"inserted_at": "2025-12-11T16:58:13",
"last_name": "Contact",
"updated_at": "2025-12-11T16:58:13",
"vendor_id": 72620
}
],
"external_id": null,
"id": 72620,
"inserted_at": "2025-12-11T16:58:13",
"is_compliant": false,
"phone": null,
"primary_contact_id": null,
"requirement_group_id": 850,
"updated_at": "2025-12-11T17:11:18"
}
]
Get Vendor by ID
GET/api/v1/vendors/:vendor_idReturns a single vendor object with the same shape as an entry in the List Vendors response.
Get Vendor by External ID
GET/api/v1/vendors/user_defined/:external_idexternal_id is the ID assigned to the vendor by your own system, which makes it perfect for ERP integrations that key off their own vendor numbers.
Create Vendor
POST/api/v1/vendorsRequest body example
{
"external_id": "some_vendor_external_id",
"company_name": "My Vendor Name",
"address": {
"city": "city",
"country": "US",
"state": "TN",
"street_1": "123 street1",
"street_2": "123 street2",
"zip": "12345"
},
"contact": {
"first_name": "First",
"last_name": "Last",
"email": "email@example.com"
}
}
Update Vendor
PATCH/api/v1/vendors/:vendor_idRequest body example
{
"primary_contact_id": 105
}
Archive Vendor
DELETE/api/v1/vendors/:vendor_idSoft-deletes the vendor by setting is_archived: true. Archived vendors are excluded from the list endpoint.
Response example
{
"id": 72620,
"company_name": "Test Vendor",
"is_archived": true
}
Send Vendor Document Request
POST/api/v1/vendors/:vendor_id/send_requestSends a document request email to the vendor’s primary contact. Rate-limited to once per 10 minutes per vendor. Returns 428 if a request was already sent within the last 10 minutes, or if the vendor has no primary contact.
Response example
{
"last_requested_datetime": "2025-11-18T18:52:28.260906Z"
}
Bulk Create Vendors
POST/api/v1/vendors/bulkCreates multiple vendors in a single request. Each item in the array is processed independently, so failures do not block the rest.
Request body
{
"vendors": [
{
"company_name": "Vendor One",
"contact": {
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@vendorone.com"
}
},
{
"company_name": "Vendor Two",
"contact": {
"first_name": "Bob",
"last_name": "Jones",
"email": "bob@vendortwo.com"
}
}
]
}
Response example
{
"results": [
{ "status": "ok", "data": { "id": 101, "company_name": "Vendor One" } },
{ "status": "error", "errors": { "email": ["has already been taken"] } }
]
}
Bulk Update Vendors
PATCH/api/v1/vendors/bulkUpdates multiple vendors in one request. Each item must include an id. Returns the same results array format as bulk create.
Request body
{
"vendors": [
{ "id": 101, "external_id": "EXT-001" },
{ "id": 102, "primary_contact_id": 55 }
]
}
Bulk Send Vendor Document Requests
POST/api/v1/vendors/bulk_send_requestSends document request emails to multiple vendors in a single call. Each vendor is processed independently.
Request body
{
"vendor_ids": [101, 102, 103]
}
Response example
{
"results": [
{ "status": "ok", "data": { "vendor_id": 101, "last_requested_datetime": "2025-11-18T18:52:28Z" } },
{ "status": "error", "errors": { "vendor_id": 102, "message": "documents already requested within last 10 minutes" } }
]
}
Vendor Contacts
Manage the contacts that belong to a specific vendor.
Create Vendor Contact
POST/api/v1/vendors/:vendor_id/contactsRequest body
{
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com"
}
Response example
{
"id": 500,
"vendor_id": 72620,
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"is_archived": false,
"inserted_at": "2025-12-11T17:00:00Z",
"updated_at": "2025-12-11T17:00:00Z"
}
Update Vendor Contact
PATCH/api/v1/vendors/:vendor_id/contacts/:contact_idRequest body
{
"first_name": "Janet",
"email": "janet@example.com"
}
Set Primary Vendor Contact
PUT/api/v1/vendors/:vendor_id/contacts/:contact_id/primarySets a contact as the primary contact for the vendor. The contact must not be archived.
Response example
{
"primary_contact_id": 500
}
Archive Vendor Contact
DELETE/api/v1/vendors/:vendor_id/contacts/:contact_idSoft-deletes the contact. If the contact is currently the primary contact, the vendor’s primary contact is cleared.
Contacts
Get Contact by ID
GET/api/v1/contacts/:contact_idcontact_id is the Billy system ID of the contact.
Create Contact
POST/api/v1/contactsRequest body example
{
"vendor_id": "102",
"first_name": "Second",
"last_name": "Contact",
"email": "email2@example.com"
}
Update Contact
PATCH/api/v1/contacts/:contact_idContracts
Contracts associate vendors with projects and define the insurance requirements that apply. The contract’s compliance array shows each requirement’s status, including matched policy details like carrier, limits, and expiration.
List Project Contracts
GET/api/v1/projects/:project_id/contractsSupports pagination query parameters.
Response example
[
{
"compliance": [
{
"notes": null,
"policy": {
"carrier": "AXA",
"effective_date": "2025-12-11",
"expiration_date": "2026-12-11",
"limits": [
{ "amount": 1000000, "name": "Each Occurrence" }
],
"naic_number": "12345",
"policy_number": "123"
},
"requirement_id": 4138,
"requirement_kind": "insurance",
"requirement_type_name": "Commercial General Liability Insurance",
"reviewed_at": "2025-12-11T17:03:37",
"status": "active"
}
],
"contact_id": 62784,
"external_id": "V1",
"id": 62032,
"inserted_at": "2025-12-11T16:58:13",
"is_active": true,
"is_compliant": true,
"last_requested_datetime": null,
"project_id": 5101,
"requirement_group_id": 849,
"updated_at": "2025-12-11T17:05:06",
"vendor_id": 72620
}
]
Get Contract by ID
GET/api/v1/contracts/:contract_idReturns a single contract object with the same shape as an entry in the List Project Contracts response.
Create Contract
POST/api/v1/contractsrequirement_group_id is optional; it defaults to the project’s default requirement group.
Request body example
{
"project_id": "4",
"vendor_id": "102",
"contact_id": "105",
"requirement_group_id": "1111"
}
Update Contract
PATCH/api/v1/contracts/:contract_idRequest body example
{
"contact_id": 102,
"external_id": "new-external-id"
}
Send Contract Document Request
POST/api/v1/contracts/:contract_id/send_requestSends a document request email to the contract’s contact. Rate-limited to once per 10 minutes per contract. Returns 428 if already requested within the last 10 minutes, or if the contract is not active or the project status does not allow requests.
Response example
{
"last_requested_datetime": "2025-11-18T18:52:28.260906Z"
}
Bulk Create Contracts
POST/api/v1/contracts/bulkCreates multiple contracts in a single request. Each item is processed independently.
Request body
{
"contracts": [
{
"vendor_id": 101,
"project_id": 4,
"contact_id": 105
},
{
"vendor_id": 102,
"project_id": 4,
"contact_id": 106,
"external_id": "EXT-002",
"requirement_group_id": 850
}
]
}
Response example
{
"results": [
{ "status": "ok", "data": { "id": 62033, "vendor_id": 101 } },
{ "status": "error", "errors": { "base": ["Invalid vendor_id"] } }
]
}
Bulk Update Contracts
PATCH/api/v1/contracts/bulkUpdates multiple contracts in one request. Each item must include an id. Returns the same results array format as bulk create.
Request body
{
"contracts": [
{ "id": 62032, "contact_id": 107, "external_id": "EXT-UPDATED" },
{ "id": 62033, "contact_id": 108 }
]
}
Contract Waives
Waiving a requirement exempts a contract from that specific compliance requirement.
Waive a Requirement
POST/api/v1/contracts/:contract_id/waived_requirementsRequest body
{
"requirement_id": 4138,
"notes": "Approved by project manager on 2025-01-15"
}
Response example
{
"id": 77,
"subcontract_id": 62032,
"requirement_id": 4138,
"notes": "Approved by project manager on 2025-01-15",
"label": "Commercial General Liability Insurance",
"inserted_at": "2025-11-18T18:59:31Z",
"updated_at": "2025-11-18T18:59:31Z"
}
Unwaive a Requirement
DELETE/api/v1/contracts/:contract_id/waived_requirements/:requirement_idRemoves a waiver if one exists. Returns 404 if no waiver exists for the given requirement on that contract. Success returns { "message": "unwaived" }.
Requirement Groups
Requirement groups define sets of insurance and document requirements. The API uses the terms project and company for the two kinds. Project groups apply at the contract level, company groups apply at the vendor level.
List Project Requirement Groups
GET/api/v1/requirement_groups/projectReturns all non-archived requirement groups of the project kind.
Response example
{
"requirement_groups": [
{ "id": 849, "name": "Standard Project Requirements", "is_default": true },
{ "id": 850, "name": "High-Risk Project Requirements", "is_default": false }
]
}
Get Project Requirement Group
GET/api/v1/requirement_groups/project/:idResponse example
{
"id": 849,
"name": "Standard Project Requirements",
"kind": "project",
"is_default": true,
"is_archived": false,
"requirements": [
{
"requirement_id": 4138,
"requirement_type": {
"label": "Commercial General Liability Insurance",
"kind": "insurance"
},
"insurance_requirement_details": {
"policy_options": [],
"additional_insured": "named",
"subrogation_waived": "yes",
"required_limits": [
{ "name": "Each Occurrence", "amount": 1000000 }
],
"inserted_at": "2025-12-11T16:00:00Z",
"updated_at": "2025-12-11T16:00:00Z"
},
"inserted_at": "2025-12-11T16:00:00Z",
"updated_at": "2025-12-11T16:00:00Z"
}
],
"inserted_at": "2025-12-11T16:00:00Z",
"updated_at": "2025-12-11T16:00:00Z"
}
List Company Requirement Groups
GET/api/v1/requirement_groups/companyReturns all non-archived requirement groups of the company kind (used for vendor-level compliance). Same format as the project list.
Get Company Requirement Group
GET/api/v1/requirement_groups/company/:idSame format as the project show response.
Create Requirement Group
POST/api/v1/requirement_groupskind must be "project" or "company". Returns 201 with the created group.
Request body
{
"name": "New Group Name",
"kind": "project"
}
Copy Requirement Group
POST/api/v1/requirement_groups/:id/copyCreates a new requirement group by copying an existing one, including all its requirements. Returns 201 with the new group.
Request body
{
"name": "Copy of My Group"
}
Update Requirement Group
PATCH/api/v1/requirement_groups/:idRequest body
{
"name": "Renamed Group"
}
Archive Requirement Group
DELETE/api/v1/requirement_groups/:idSoft-deletes the requirement group. Returns 200 with the archived group ("is_archived": true).
Add Insurance Requirement
POST/api/v1/requirement_groups/:requirement_group_id/requirements/insuranceReturns 201 with the created requirement.
Request body
{
"type": "Commercial General Liability Insurance",
"additional_insured": "named",
"subrogation_waived": "yes",
"requirement_limits": [
{ "name": "Each Occurrence", "amount": "1000000" },
{ "name": "General Aggregate", "amount": "2000000" }
]
}
Add Document Requirement
POST/api/v1/requirement_groups/:requirement_group_id/requirements/documentReturns 201 with the created requirement.
Request body
{
"label": "W-9"
}
Add Custom Form Requirement
POST/api/v1/requirement_groups/:requirement_group_id/requirements/custom_formLinks an existing custom form (survey) as a requirement within a requirement group. Returns 201 with the created requirement.
Request body
{
"survey_id": 12
}
Archive Requirement
DELETE/api/v1/requirement_groups/:requirement_group_id/requirements/:requirement_idRemoves a requirement from a group by archiving it. Success returns { "message": "archived" }.
Certificates
Certificates of Insurance (COIs) are uploaded documents that satisfy insurance requirements.
Review Certificate
PATCH/api/v1/certificates/:id/reviewMarks a COI certificate as reviewed. The review status (approved/rejected) is computed based on whether the certificate’s policy details meet the requirement limits. This endpoint records that the certificate has been examined.
Response example
{
"id": 9901,
"reviewed_on": "2025-11-18T18:59:31Z",
"inserted_at": "2025-11-18T18:00:00Z",
"updated_at": "2025-11-18T18:59:31Z"
}
Custom Forms & Responses
Custom forms (internally called surveys) are questionnaires vendors complete as part of their compliance requirements. Responses are vendor submissions against those forms.
List Custom Forms
GET/api/v1/custom_formsReturns all published, non-archived custom forms for the team.
Response example
{
"custom_forms": [
{
"id": 12,
"title": "Diversity and Inclusion Survey",
"description": "Please complete all sections.",
"inserted_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-06-01T00:00:00Z"
}
]
}
Archive Custom Form
DELETE/api/v1/custom_forms/:idSoft-deletes a custom form. Archived forms are excluded from the list endpoint. Returns 200 with the archived form.
List Custom Form Responses
GET/api/v1/custom_form_responsesReturns submitted custom form responses. At least one of contract_id or vendor_id must be provided as a query parameter. It returns 422 if both are omitted.
| Parameter | Type | Required | Description |
|---|---|---|---|
contract_id | integer | conditionally | Filter by contract (project-level) |
vendor_id | integer | conditionally | Filter by vendor (company-level) |
Response example
{
"custom_form_responses": [
{
"id": 55,
"survey_id": 12,
"subcontract_id": 62032,
"vendor_id": 72620,
"submitted_at": "2025-11-18T18:59:31Z",
"inserted_at": "2025-11-18T18:59:31Z",
"updated_at": "2025-11-18T18:59:31Z"
}
]
}
Review Custom Form Response
PATCH/api/v1/custom_form_responses/:id/reviewRecords a compliance review decision for a custom form response against a specific requirement.
| Field | Type | Required | Values |
|---|---|---|---|
requirement_id | integer | yes | ID of the requirement being evaluated |
status | string | no | "approved", "rejected" |
notes | string | no | Free-text reviewer notes |
Request body
{
"requirement_id": 4150,
"status": "approved",
"notes": "All sections completed satisfactorily."
}
Response example
{
"id": 88,
"survey_response_id": 55,
"requirement_id": 4150,
"status": "approved",
"notes": "All sections completed satisfactorily.",
"inserted_at": "2025-11-18T18:59:31Z",
"updated_at": "2025-11-18T18:59:31Z"
}
Bulk Operation Format
All bulk endpoints return a results array with one entry per input item. A failure in one item does not prevent the others from being processed.
{
"results": [
{ "status": "ok", "data": { ... } },
{ "status": "error", "errors": { "field": ["message"] } }
]
}
400 Bad Request before any items are processed, with the error "bulk request exceeds maximum of 200 items".Frequently Asked Questions
Common questions about the Billy API and COI tracking automation.
What is a COI tracking API?
A COI tracking API lets software systems programmatically manage certificates of insurance: creating vendors and projects, defining insurance requirements, requesting documents from subcontractors, and checking compliance status in real time. The Billy API exposes all of this over standard REST endpoints, so you can embed insurance compliance directly into your ERP, project management, or accounting workflows.
Can Billy integrate with our ERP or accounting system?
Yes. The Billy API is designed for exactly this: you can sync vendors using your own external IDs, look vendors up by those IDs, and pull compliance status into your ERP so your finance team can verify a vendor is compliant before releasing payment. Billy also offers pre-built integrations with Procore, Autodesk, CMiC, Sage 300, Sage Intacct, JD Edwards, Viewpoint Vista, and Acumatica.
Is there a sandbox environment for testing?
Yes. Billy provides a dedicated sandbox environment for development and testing, separate from production. Your sandbox and production base URLs are shared along with your client credentials when you are granted API access, and a Postman collection is available so you can explore every endpoint before writing code.
How does API authentication work?
The Billy API uses Bearer token authentication. You exchange your client ID and secret for an access token, which is valid for 120 minutes. Include the token in the Authorization header on every request, and request a new token when it expires.
Are there rate limits or batch limits?
Document request emails are rate-limited to once per 10 minutes per vendor or contract. Bulk endpoints accept up to 200 items per request, and each item is processed independently, so one failure never blocks the rest of the batch.
How do I get API credentials?
API access is available to Billy customers and technology partners. Request a demo to get set up with client credentials, a development sandbox and, for technology partners, developer training, a dedicated Slack channel, and co-marketing opportunities.
Let’s build together
Embrace the change with Billy API and focus on delivering successful projects without the headache of compliance.
Request API Access Explore Partnerships