Concepts
How the API expects to be used
The API is RPC-shaped rather than REST-shaped. Everything is a POST to a resource plus an operation, form-encoded in, JSON out.
Request shape
- URL
- https://{subdomain}.pestroutes.com/api/{resource}/{operation}
- Method
- POST
- Body
- application/x-www-form-urlencoded
- Auth
- authenticationKey + authenticationToken
Send the key and token as the last two parameters. If a very large request gets truncated, trailing credentials mean it fails instead of half-executing.
Search, then get
A search returns an array of primary keys and nothing else, up to 50,000 IDs per search response. Pass those keys to the matching get endpoint to resolve entities, up to 1,000 entities per get call. Adding includeData=1 to a search resolves the first 1,000 inline and reports the overflow in {entity}IDsNoDataExported.
let cursor = 0;
const all = [];
while (true) {
const ids = await search("appointment", {
appointmentID: { operator: ">", value: cursor },
});
all.push(...ids);
if (ids.length < 50000) break;
cursor = ids[ids.length - 1];
}Filter operators
Every search parameter accepts either a bare value or an operator object.
# simple equality
--data-urlencode 'active=1'
# operator object
--data-urlencode 'dateAdded={"operator":">","value":"2026-01-01"}'
# range
--data-urlencode 'dateAdded={"operator":"BETWEEN","value":["2026-05-12","2026-05-13"]}'
# set membership
--data-urlencode 'regionID={"operator":"IN","value":[4,7,9]}'=Exact match. The default when you pass a bare value.
>Greater than. The standard way to page past a 50,000 ID wall.
<Less than.
>=Greater than or equal.
<=Less than or equal.
!=Not equal.
INValue is an array of accepted values.
BETWEENValue is a two-item array of bounds, inclusive.
LIKESQL-style pattern match.
STARTSWITHPrefix match on a string column.
ENDSWITHSuffix match on a string column.
CONTAINSSubstring match on a string column.
Workflows
Incremental customer sync
Poll for records changed since your last check-in, then resolve them in batches of 1,000.
- 1POST /customer/search with dateUpdated={"operator":">","value":"2026-03-01 00:00:00"}
- 2Chunk the returned customerIDs into groups of 1,000
- 3POST /customer/get with customerIDs[]=... for each chunk
Paging past the 50,000 ID wall
A search result of exactly 50,000 IDs means there is more. Continue from the last primary key.
- 1POST /appointment/search and read the appointmentIDs array
- 2If length is exactly 50,000, take the last ID
- 3Repeat with appointmentID={"operator":">","value":<lastID>} until fewer than 50,000 return
One-shot search with data
Pass includeData=1 to have search resolve the first 1,000 entities inline instead of a second call.
- 1POST /customer/search with includeData=1
- 2Read the resolved array plus customerIDsNoDataExported for the overflow
- 3Resolve the overflow IDs through /customer/get
Billing a completed visit
Complete the appointment, then create the invoice and apply the payment.
- 1POST /appointment/complete with the appointmentID
- 2POST /ticket/create for the charge
- 3POST /payment/create against the ticketID
Limits and errors
Default throughput is 3,000 reads and 3,000 writes per office, capped at 60 per minute. Reads and writes have separate budgets, and multiple keys can be grouped into a shared pool on request.
Failures come back as a non-2xx status with a JSON body. There is no published error code taxonomy, so treat the status plus the body message as the contract, and ask apisupport@fieldroutes.com for request logs when a call behaves unexpectedly.
Vocabulary
Ticket
An invoice. Line items live on the ticket, payments settle it.
Subscription
A recurring service plan that generates appointments over time.
Appointment
One scheduled visit, tied to a route and a spot.
Spot
A bookable slot on a route for a given day.
Knock / Door
Door-to-door sales tracking records.
Office scope
Single-office keys carry an implied officeIDs filter. Global keys may pass any officeIDs.