Skip to main content

Using the PaintForce API

What the API can do, how it differs from webhooks, how to create an API key, and step-by-step examples like sending website leads into PaintForce.

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.

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.

  1. Log in to PaintForce in a web browser as an Owner or Admin.

  2. Go to Settings → Webhooks. API keys are managed at the bottom of this screen.

  3. Click Edit, then + Add API key.

  4. Give it a label you'll recognize later, like "Website lead form".

  5. Click Save. The new key appears in the table.

  6. 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.app

  • Authentication: send the key in a header on every request, Authorization: Bearer YOUR_API_KEY

  • Format: JSON. Send Content-Type: application/json when 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:

  • salesRepUserId lists your team members, each with a userId.

  • leadSourceId lists your lead sources, each with an id.

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-DD in your company's time zone.

  • A range can be at most 366 days.

  • /v1/reports/jobs and /v1/reports/requests return one row per job or request, 100 at a time. Keep calling with the nextCursor value until it comes back null.

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 archived field 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 _seconds is 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

401 "Authorization header required"

The Authorization header is missing.

401 "Invalid authorization header format"

The header must be exactly Bearer YOUR_API_KEY, with a capital B and one space.

401 "Unauthorized - Invalid API Key"

The key is wrong, incomplete, or was deleted. Create a new one if needed.

400 "Validation Error"

The errors list says which field is wrong, e.g. /body/details "must have required property", or /body/phone "must be number".

403 on a reports endpoint

The person who created the key isn't an Owner, Admin or Sales user. Have an Owner or Admin create the key.

404 "not found"

The ID doesn't exist in your account.

500 mentioning "created by id"

The createdById isn't a valid PaintForce user ID. Look it up again (step 1 of the lead example).

Still stuck? Contact us through the chat bubble. Include the endpoint, the status code and the error message, but never your API key.

Did this answer your question?