The PaintForce API lets your other software work with your PaintForce account directly. It can read data, like your contacts or report numbers, and it can add or change data, like creating a new lead when someone fills out the form on your website.
This article explains what the API can do, how to get an API key, and how to make your first requests. Setting it up takes a developer, or someone comfortable with the "custom request" steps in Zapier or Make. The first sections are written for anyone deciding whether the API is the right fit.
Webhooks or API: which one do I need?
PaintForce offers two ways to connect with other software. They work in opposite directions:
Webhooks: PaintForce tells your other app when something happens. PaintForce sends the data out automatically, the moment it changes. Nobody has to ask.
API: your other app asks PaintForce for data, or sends data into it. It happens whenever that app decides to make a request.
| Webhooks | API |
Direction | Out of PaintForce | Into PaintForce, or reading from it |
When it runs | Automatically, right after a change | Whenever your other app makes a request |
Good for | "When a job is completed, add it to our website map." "When an invoice is created, copy it to QuickBooks." | "When someone fills out our website form, create a Request in PaintForce." "Pull this month's numbers into our dashboard." |
What you set up | Paste a URL in Settings → Webhooks. No code needed with Zapier or Make. | Create an API key, then a developer (or a Zapier/Make step) makes the requests. |
Data covered | Jobs, estimates, invoices, payments, expenses, contacts, requests | Contacts, requests, notes, calendar events, reports |
Many integrations use both. For example, a webhook tells your system that a job changed, and the API adds a note back to that job.
Webhooks are covered in Webhooks: Automatically Send PaintForce Data to Other Apps.
What you can do with the API
Area | What's available |
Contacts | List, look up, create, update and delete contacts. Import contacts from a CSV file. |
Requests (leads) | List, look up, create, update (including assigning and archiving) and delete requests. |
Notes | Read and add notes on jobs and on requests. |
Calendar | List, create, update and delete calendar events and calendar tags. |
Reports | Read job and request reports, summaries and saved report views: the same numbers you see on the Reports screen. |
Not available through the API: creating or editing jobs, estimates, invoices, payments or expenses. You can read job information through the Reports endpoints. To get job, estimate and invoice changes sent to you as they happen, use webhooks.
Step 1: Create an API key
An API key is a long secret code that proves a request comes from your company. Anyone who has it can read and change your contacts and requests, so treat it like a password.
Log in to PaintForce in a web browser as an Owner or Admin.
Go to Settings → Webhooks. API keys are managed at the bottom of this screen.
Click Edit, then + Add API key.
Give it a label you'll recognize later, like "Website lead form".
Click Save. The new key appears in the table.
Copy it right away and store it somewhere safe, such as your password manager or your developer's secret settings. It's shown only once. After you leave the page, it's hidden forever.
Lost a key, or think someone else has it? Click Edit, remove that key's row with the red minus icon, and Save. It stops working immediately. Then create a new one.
Good to know:
A key works for your whole company account. It isn't limited to certain data.
A key acts as the person who created it. Reports only work if that person is an Owner, Admin or Sales user.
Create a separate key for each integration (website, Zapier, and so on). That way you can turn one off without breaking the others.
Step 2: Make your first request
Base URL:
https://api.paintforce.appAuthentication: send the key in a header on every request,
Authorization: Bearer YOUR_API_KEYFormat: JSON. Send
Content-Type: application/jsonwhen you include a body.
The examples below are written as plain HTTP requests: the method and URL first, then the headers, then the JSON body. Those are the same pieces you fill into Zapier's Custom Request, Make's HTTP module, or your code.
Try it by listing your contacts:
GET https://api.paintforce.app/v1/contacts Authorization: Bearer YOUR_API_KEY
You'll get back a list of your contacts. If you see {"message":"Unauthorized - Invalid API Key"} instead, check that you copied the whole key, including any = at the end.
Interactive API docs
Every endpoint, with its fields, is listed in the interactive docs at api.paintforce.app/api-docs/public. To try requests right in your browser, click Authorize and paste just your key; the docs add the word "Bearer" for you.
Where to run requests from
Call the API from a server (your website's backend, a serverless function) or from an integration tool like Zapier ("Webhooks by Zapier → Custom Request") or Make (HTTP module).
Don't call it from your website's front-end code. Browsers block those requests, and putting your key in page code would let anyone copy it.
Example: send website form leads into PaintForce
This is the most common use of the API. When someone submits the quote form on your website, your site's backend creates a contact and a request in PaintForce. It then shows up under Work → Requests just like one you entered yourself.
1. Look up your IDs (one time)
Creating records requires createdById, the PaintForce user ID of the person the record should be "created by". You can also pass IDs to assign the lead or set its lead source.
Get all of these IDs in one call:
GET https://api.paintforce.app/v1/reports/meta Authorization: Bearer YOUR_API_KEY
In the response, look under filterValues:
salesRepUserIdlists your team members, each with auserId.leadSourceIdlists your lead sources, each with anid.
Save the IDs you need in your integration's settings.
2. Create the contact
Requests don't create contacts automatically, so create the customer first.
POST https://api.paintforce.app/v1/contacts
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json{
"createdById": "YOUR_USER_ID",
"firstName": "Pat",
"lastName": "Smith",
"email": "[email protected]",
"phone": 3035550199,
"addresses": [{
"street": "1600 Pennsylvania St",
"additionalStreet": "",
"city": "Denver",
"state": "CO",
"postal": "80203",
"country": "United States"
}]
}The response is the new contact. Keep its id for the next step.
Phone numbers must be sent as a number with digits only, e.g. 3035550199, not "303-555-0199". A formatted phone string is rejected.
3. Create the request
POST https://api.paintforce.app/v1/requests
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json{
"title": "Website lead - exterior repaint",
"details": "From website form: 2-story house, wants a quote in October.",
"createdById": "YOUR_USER_ID",
"customerContactId": "CONTACT_ID_FROM_STEP_2",
"leadSourceId": "LEAD_SOURCE_ID",
"assignedToId": "USER_ID_TO_ASSIGN"
}Only title, details and createdById are required. You can also send salesRepId, leadSetterId, tags (tag IDs) and assessmentDateTime (an ISO date and time such as 2026-10-05T15:00:00Z, which also marks the request as scheduled).
A real response looks like this:
{
"id": "2a4859f2-fc2f-416b-af4c-748e8e4faa4c",
"orgId": "9f1c2d3e-1111-4a5b-8c9d-0e1f2a3b4c5d",
"title": "Website lead - exterior repaint",
"details": "From website form: 2-story house, wants a quote in October.",
"assessmentDateTime": null,
"createdBy": {
"displayName": "Alex Rivera",
"userId": "Xr7kP2mQ9sT4vW1yZ3aB5cD8eF0g",
"email": "[email protected]"
},
"customerContact": {
"id": "2c1620c0-3a5d-4cc8-98e7-4eed1bc42067",
"firstName": "Pat",
"lastName": "Smith",
"email": "[email protected]",
"phone": 3035550199,
"company": "",
"selectedAddressId": "58f2a1ad-b202-43dd-8d80-d87a1fd13917",
"salesRep": null,
"address": {
"addressId": "58f2a1ad-b202-43dd-8d80-d87a1fd13917",
"street": "1600 Pennsylvania St",
"additionalStreet": "",
"postal": "80203",
"state": "CO",
"city": "Denver",
"country": "United States"
}
},
"leadSource": {
"id": "45154038-ef88-440d-92a5-645eb804fc23",
"name": "Google Search"
},
"leadSetter": null,
"salesRep": null,
"assignedTo": {
"userId": "Xr7kP2mQ9sT4vW1yZ3aB5cD8eF0g",
"firstName": "Alex",
"lastName": "Rivera",
"role": "owner",
"email": "[email protected]"
},
"creationDate": "2026-09-18T22:19:54.999Z",
"lastUpdatedDate": "2026-09-18T22:19:54.999Z",
"scheduled": false,
"archived": false,
"tags": []
}What happens next:
The request appears in PaintForce right away.
The assigned person, the sales rep, and anyone set to receive new-request notifications are notified.
If you use Request Created or Contact Created webhooks, they fire too.
If you leave out
assignedToId, the request shows up as Unassigned.
Example: add a note to a request or job
POST https://api.paintforce.app/v1/requests/REQUEST_ID/notes
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json{ "createdById": "YOUR_USER_ID", "message": "Customer prefers a call after 5pm." }For a job, use /v1/job/JOB_ID/notes. Job IDs come from the Reports endpoints, or from the id field in a job webhook.
Example: pull report numbers
GET https://api.paintforce.app/v1/reports/jobs/summary?startDate=2026-01-01&endDate=2026-06-30&groupBy=month Authorization: Bearer YOUR_API_KEY
This returns the same totals as the Reports screen, such as revenue, profit and jobs won, grouped by month.
Dates are
YYYY-MM-DDin your company's time zone.A range can be at most 366 days.
/v1/reports/jobsand/v1/reports/requestsreturn one row per job or request, 100 at a time. Keep calling with thenextCursorvalue until it comes backnull.
Things to know
Changes behave like changes in the app. Records created or changed through the API send the usual notifications and fire your webhooks.
Contact and request lists return everything at once, including archived records. There's no paging or filtering, so check the
archivedfield yourself.Dates in responses come in two forms: an ISO string like
"2026-09-18T22:19:54.999Z", or an object like{"_seconds": 1789769981, "_nanoseconds": 429000000}, where_secondsis a Unix timestamp. Handle both.Unknown IDs are ignored, not rejected. A misspelled tag or lead source ID is silently dropped, and so is an assignee who isn't on your team. Double-check the IDs you save.
Retries can create duplicates. If a create request times out and you send it again, you may get two records. Check before retrying.
Rate limits: report endpoints allow about 60 requests a minute (summaries 10 a minute). CSV contact imports are limited to 1 MB / 500 rows, 5 imports per 15 minutes. If you go over a limit you'll get HTTP
429. Wait a minute and try again.
Troubleshooting
Response | What it means |
| The |
| The header must be exactly |
| The key is wrong, incomplete, or was deleted. Create a new one if needed. |
| The |
| The person who created the key isn't an Owner, Admin or Sales user. Have an Owner or Admin create the key. |
| The ID doesn't exist in your account. |
| The |
Still stuck? Contact us through the chat bubble. Include the endpoint, the status code and the error message, but never your API key.