PeerToSite Employer API
A real, working JSON API over the same data your PeerToSite dashboard uses — postings, assignments, and invoices. Every example on this page is live; there's nothing here that isn't actually implemented.
Getting Started
Welcome to the PeerToSite Employer API v1. It's a plain JSON REST API over HTTPS — the same data model your PeerToSite dashboard reads and writes to, exposed for your own scripts and integrations.
Version 1 covers dispatch requests end to end — so a ticket in your service desk can become a PeerToSite dispatch, stay in step with it, and close itself — plus reading postings, assignments and invoices. Bidding, check-ins and payment are dashboard-only.
Authentication
Every request is authenticated with an API key, scoped to a single company. A Company Manager generates one under Settings → Integrations → API Keys — it's shown once, so save it somewhere safe. A key is read only or read and write; a read-only key gets 403 on anything that changes data. Each key is limited to 120 requests a minute (429 with Retry-After beyond that).
Note for anyone comparing this to other platforms: this is a single long-lived bearer key, not an OAuth2 client-credentials exchange. Simpler, at the cost of not supporting token expiry or scoped grants yet.
Example
curl https://YOUR-PeerToSite-HOST/api/v1/postings \
-H "Authorization: Bearer peertosite_live_xxxxxxxxxxxxxxxxxxxxxxxx"
A missing or invalid key returns 401 Unauthorized.
Response Format
Every response — success or error — is wrapped in the same envelope:
{
"meta": {
"status_code": 200,
"version": 1,
"timestamp": 1755781200
},
"response": { ... }
}
JSON only, no XML output option — one less format to keep in sync while the API is this new.
Errors
Errors use standard HTTP status codes. The error message is in response.error:
{
"meta": { "status_code": 404, "version": 1, "timestamp": 1755781200 },
"response": { "error": "No such posting" }
}
| Code | Meaning |
|---|---|
401 | Missing, malformed, or revoked API key |
404 | The resource doesn't exist, or doesn't belong to your company |
422 | The request body is missing a required field or has the wrong type |
/api/v1/dispatch-itemsThe service items you can request (each has a service_item_id) and the urgency levels (critical, high, medium, low). Map your ticket categories and priorities to these once. Urgency sets the price level — and a Critical or High request sent while its location is closed (outside the working hours set in Sites) is billed at the Critical level; the request's after_hours says so — so make that mapping deliberately.
/api/v1/dispatch-requestsCreates a dispatch request, exactly as the form does (your company needs the Dispatch service and a plan). The location is one of your sites, by your own store number.
curl -X POST https://YOUR-PeerToSite-HOST/api/v1/dispatch-requests \
-H "Authorization: Bearer $PEERTOSITE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: INC0012345" \
-d '{"site_code": "104", "service_item_id": "…", "urgency": "medium",
"title": "PIN pad offline at lane 3", "description": "No power since 9am.",
"reference": "INC0012345", "asset_tag": "PP-104-1"}'
Send an Idempotency-Key — the ticket's own id is ideal. If the same key arrives again, PeerToSite returns the first request (200) instead of creating a second, so a retry never books two visits. asset_tag is optional. A problem with the request is a 422 saying what's wrong.
/api/v1/dispatch-requests/{id}One request: status (matching, offered, accepted, escalated, unfilled, canceled), job_status (open, assigned, in_progress, pending_approval, disputed, completed, canceled), your reference, and the times — requested_at, accepted_at, expected_arrival, arrive_by, checked_in_at, signed_off_at. GET /api/v1/dispatch-requests?reference=INC0012345 finds requests by your ticket number; without it, the newest 100.
Responses never include a technician's name or contact details — you reach them through the job chat (below).
/api/v1/dispatch-requests/{id}The ticket was edited: send any of title, description, reference. The title and description can change until the technician checks in (409 after); the reference at any time. The technician is told when the details change.
/api/v1/dispatch-requests/{id}/messagesA ticket comment: {"body": "The back door code is 4471"}. It appears in the job chat, where the technician sees it. The technician's replies come to you as dispatch.message webhooks. Like any chat message, one containing a phone number or email address is flagged to PeerToSite.
/api/v1/dispatch-requests/{id}/cancelThe ticket was canceled: the visit is canceled and the firm is told. This follows the same rule as the Cancel button — only until the technician checks in. After that PeerToSite answers 409 and nothing changes, so a ticket closed by mistake can't erase a visit in progress. Once the technician has told PeerToSite they're on the way, cancelling is charged the request's travel fee plus tax — the same as the Cancel button — and the invoice appears under Payments. An optional {"reason": "…"} is kept in your audit log.
Webhooks
Instead of asking PeerToSite over and over, give PeerToSite an address and it calls you when a dispatch job changes. A Company Manager adds one under Settings → Integrations → Webhooks and picks the events:
dispatch.requested, dispatch.accepted, dispatch.arrival_confirmed, dispatch.checked_in, dispatch.signed_off, dispatch.completed, dispatch.canceled, dispatch.unfilled, dispatch.late, dispatch.message, dispatch.updated.
POST https://yourcompany.example/peertosite-webhook
X-PeerToSite-Event: dispatch.checked_in
X-PeerToSite-Delivery: 5f0c… (unique per message — use it to ignore a repeat)
X-PeerToSite-Timestamp: 1791230000
X-PeerToSite-Signature: sha256=3b1f…
{"event": "dispatch.checked_in", "sent_at": "2026-10-06T14:02:11+00:00",
"dispatch_request": {"id": "…", "reference": "INC0012345", "status": "accepted",
"job_status": "in_progress", "checked_in_at": "2026-10-06T14:02:09+00:00", …}}
Check the signature before trusting a message: compute HMAC-SHA256 of <X-PeerToSite-Timestamp>.<the raw body> with your webhook's signing secret, and compare it with the value after sha256=. Reject a timestamp more than a few minutes old.
Answer with any 2xx within 5 seconds. Anything else is tried again — six times over about fifteen hours — and every attempt is in your Delivery Log. Your address must be https on the public internet; PeerToSite doesn't follow redirects. Twenty failures in a row switch the webhook off until you turn it back on.
A message carries the request's id, your reference, its stage and times — and, for dispatch.message, the message text. Never a technician's name or contact details. It is sent to the address you give, wherever that system is hosted.
/api/v1/postingsLists your company's postings, most recent first (up to 100).
Request
curl https://YOUR-PeerToSite-HOST/api/v1/postings \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"
Response
{
"meta": { "status_code": 200, "version": 1, "timestamp": 1755781200 },
"response": [
{
"id": "b6e1...c02f",
"title": "Loading dock cleanup",
"status": "open",
"visibility": "open",
"currency_code": "USD",
"budget_min": 400.00,
"budget_max": 500.00,
"work_category_id": "8a21...9d40",
"created_at": "2026-08-19T14:02:11+00:00"
}
]
}
/api/v1/postingsCreates a new posting in draft status — the same state a posting starts in when created from the dashboard.
Request
curl -X POST https://YOUR-PeerToSite-HOST/api/v1/postings \
-H "Authorization: Bearer $PEERTOSITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Loading dock cleanup",
"scope_of_work": "Sweep and pressure-wash the rear loading dock.",
"work_category_id": "8a21...9d40",
"currency_code": "USD",
"budget_min": 400,
"budget_max": 500
}'
Body parameters
| Field | Type | Required |
|---|---|---|
title | string | Yes |
scope_of_work | string | Yes |
work_category_id | uuid | Yes |
currency_code | string | Yes |
budget_min / budget_max | number | No |
location_address | string | No |
required_skills | array of strings | No |
Response — 201 Created
{
"meta": { "status_code": 201, "version": 1, "timestamp": 1755781200 },
"response": {
"id": "b6e1...c02f",
"title": "Loading dock cleanup",
"status": "draft",
"currency_code": "USD",
"budget_min": 400.00,
"budget_max": 500.00,
"created_at": "2026-08-19T14:02:11+00:00"
}
}
/api/v1/postings/{id}Returns a single posting. 404s if it doesn't exist or belongs to a different company.
curl https://YOUR-PeerToSite-HOST/api/v1/postings/b6e1...c02f \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"
/api/v1/assignmentsLists your company's assignments — postings that have been awarded and are underway or complete.
curl https://YOUR-PeerToSite-HOST/api/v1/assignments \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"
Response
{
"meta": { "status_code": 200, "version": 1, "timestamp": 1755781200 },
"response": [
{
"id": "4f2a...7e11",
"title": "Loading dock cleanup",
"status": "in_progress",
"worker_id": "9c31...aa02",
"bid_request_id": "b6e1...c02f",
"scheduled_start": "2026-08-24T13:00:00+00:00",
"created_at": "2026-08-20T09:15:00+00:00"
}
]
}
/api/v1/assignments/{id}Returns a single assignment, including its schedule and SLA due date if one is set.
curl https://YOUR-PeerToSite-HOST/api/v1/assignments/4f2a...7e11 \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"
/api/v1/invoicesLists invoices submitted to your company, most recently submitted first.
curl https://YOUR-PeerToSite-HOST/api/v1/invoices \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"
Response
{
"meta": { "status_code": 200, "version": 1, "timestamp": 1755781200 },
"response": [
{
"id": "1a9e...5b30",
"invoice_number": "INV-1042",
"status": "paid",
"worker_id": "9c31...aa02",
"subtotal_amount": 450.00,
"currency_code": "USD",
"submitted_at": "2026-08-24T16:20:00+00:00",
"approved_at": "2026-08-24T16:40:00+00:00"
}
]
}
/api/v1/invoices/{id}Returns a single invoice.
curl https://YOUR-PeerToSite-HOST/api/v1/invoices/1a9e...5b30 \
-H "Authorization: Bearer $PEERTOSITE_API_KEY"