What the API does
Pavr's API does two things. It lets your own tools read your jobs, invoices and customer companies, and it lets your own site, phone system or form send in a request, the way work comes in on a call or a web form. Everything else, like changing a job, pricing a proposal or sending an invoice, happens in Pavr, where the approvals and the history live.
The base URL is https://pavr.io/api/v1. Every response is JSON in one envelope, and the sections below cover everything the API has. If an endpoint isn't on this page, it doesn't exist.
Authentication
There are two credentials, and each works on its own endpoints.
- An API token reads jobs, invoices and companies. Make one under Settings → Connections → API access. An account has one active token at a time, Pavr shows it once, and making a new one there revokes the old one on the spot.
-
An API key sends in requests. Make one under Settings → Connections → API keys and give it the
requests:writepermission. You can make a key for each tool that posts to you and revoke them one at a time.
Send either one as a bearer token. A token or key belongs to one account and only ever sees that account's work.
curl https://pavr.io/api/v1/jobs \
-H "Authorization: Bearer $PAVR_API_TOKEN"
A missing, malformed or revoked credential gets a 401. A key without requests:write gets a 403.
Endpoints
| Method | Path | Returns | Credential |
|---|---|---|---|
GET |
/api/v1/jobs |
Your jobs, newest first | Token |
GET |
/api/v1/jobs/:id |
One job | Token |
GET |
/api/v1/invoices |
Your invoices, newest first | Token |
GET |
/api/v1/invoices/:id |
One invoice | Token |
GET |
/api/v1/companies |
Your customer companies, newest first | Token |
GET |
/api/v1/companies/:id |
One company | Token |
POST |
/api/v1/requests |
Sends in a request | Key with requests:write
|
There's nothing to update or delete over the API, and no endpoints for proposals, change orders or work orders. Sample jobs and the invoices on them stay inside Pavr, so they never show up here. An id from another account, or one that doesn't exist, gets the same 404.
The response envelope
Every body has the same three keys.
{
"data": { "id": "…", "name": "…" },
"meta": { "request_id": "…", "api_version": "v1" },
"errors": null
}
data is one object, or a list on an index endpoint. meta always carries a request_id to quote if you write to us, and api_version. When something goes wrong, data is null and errors is a list of { "code", "message" }. The codes you can see are unauthorized, insufficient_scope, not_found, bad_request, validation_failed and rate_limited.
Jobs
Each job carries the fields below. Dates and times are ISO 8601, and money is in cents.
| Field | What it is |
|---|---|
id, job_number
|
The job's id, and the number your team says on the phone |
name |
The job's name |
billing_type |
fixed_bid, progress or time_and_materials
|
display_status, display_status_label
|
The one word every Pavr screen shows for the job, and its label. For an open job the word is open and the label is the name of the sales stage; after that it's won, in_operations, ready_to_invoice, invoiced, paid, lost or closed
|
sales_status |
open, won, lost or closed
|
sales_stage |
The name of the stage on your Jobs board |
operations_status |
not_started, in_operations or ready_to_invoice
|
lifecycle_state |
active. Archived jobs aren't returned |
signed_at, won_at, lost_at, closed_at
|
When each happened, or null
|
expected_completion_date |
The date on the job, or null
|
geofence_posture, geofence_radius_m_override
|
soft or strict: the job's clock-in location setting. The radius override is in meters, or null
|
company_id, location_id, request_id, sales_stage_id, recipient_company_id, recipient_contact_id
|
The ids to look up what the job belongs to. request_id is the request it came from |
assigned_pm_user_id, assigned_foreman_user_id
|
Who the job is assigned to |
account_id, created_at, updated_at
|
Your account, and when the job was made and last changed |
Invoices
| Field | What it is |
|---|---|
id, invoice_number, job_id
|
The invoice's id and number, and the job it bills |
invoice_type |
fixed_bid, progress_milestone, progress_period, retainage_release, tm_weekly, deposit or late_fee
|
status |
draft, finalized, sent, partial_paid, paid, past_due or voided
|
subtotal_cents, tax_cents, total_cents, balance_due_cents
|
The money on the invoice, and what's still owed |
finalized_at, due_at, paid_at
|
When each happened, or null
|
created_at, updated_at
|
When the invoice was made and last changed |
Companies
| Field | What it is |
|---|---|
id, name
|
The company's id and name |
billing_method_preference, tax_exempt
|
How it likes to be billed, and whether it's tax exempt |
website, notes
|
What you've saved on the company |
lifecycle_state, on_hold
|
active (archived companies aren't returned), and whether the company has a hold on it |
created_at, updated_at
|
When the company was made and last changed |
Creating a request
curl -X POST https://pavr.io/api/v1/requests \
-H "Authorization: Bearer $PAVR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 3f7a9e2c-1b4d-4e6a-9c3f-8a2d5e7f1b90" \
-d '{"request": {"name": "Dana Ruiz", "email": "dana@example.com", "phone": "555-0143", "address": "18000 Parking Lot Way"}}'
Put the fields inside a request object. Pavr takes name, email, phone, company_name, address, proposal_due_at, walk_at and a free-form payload object. Send at least an email or a phone, so your team can get back to them. Without one you get a 422 that says so.
The request lands in your Requests list with the status new, and the response returns the fields you sent with its id, status and created_at. Someone on your team looks at it and makes it a job or declines it.
Idempotency-Key is optional, and it's how you retry safely. Send the same key again and you get the same request back with a 200, not a second one. The first call returns a 201.
Paging
Index endpoints return 25 items a page, newest first. Ask for another page with page.
GET /api/v1/jobs?page=2
The page size is fixed, and a per_page or limit parameter does nothing. meta tells you where you are:
"meta": { "request_id": "…", "api_version": "v1", "page": 2, "per_page": 25, "next_page": 3 }
next_page is null on the last page. There's no total count, so keep asking until next_page is null.
Rate limits
60 requests per minute, per account, counted in a fixed window that resets at the top of each minute. Reads and request intake share the same count.
Go over and you get a 429 in the envelope with a Retry-After header, in seconds, until the window resets. It's the only rate-limit header the API sends, so don't build against X-RateLimit-*.
HTTP/1.1 429 Too Many Requests
Retry-After: 37
Sending in requests has two more limits, checked before the API sees the request: 60 a minute for each API key and 100 a minute for each IP address. Go over either and you get a 429 with the plain text Retry later and no Retry-After header, so wait a minute before you try again.
Webhooks
Pavr can post to your address when a job is opened, a job is won, a payment lands on an invoice, or a proposal is signed. The Webhook reference has the events, the payloads, how to check the signature and what happens when your server is down.