FieldRoutes API docs logoFieldRoutes API

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.

paging past the wall
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.

filters
# 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.

IN

Value is an array of accepted values.

BETWEEN

Value is a two-item array of bounds, inclusive.

LIKE

SQL-style pattern match.

STARTSWITH

Prefix match on a string column.

ENDSWITH

Suffix match on a string column.

CONTAINS

Substring 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.

  1. 1POST /customer/search with dateUpdated={"operator":">","value":"2026-03-01 00:00:00"}
  2. 2Chunk the returned customerIDs into groups of 1,000
  3. 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.

  1. 1POST /appointment/search and read the appointmentIDs array
  2. 2If length is exactly 50,000, take the last ID
  3. 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.

  1. 1POST /customer/search with includeData=1
  2. 2Read the resolved array plus customerIDsNoDataExported for the overflow
  3. 3Resolve the overflow IDs through /customer/get

Billing a completed visit

Complete the appointment, then create the invoice and apply the payment.

  1. 1POST /appointment/complete with the appointmentID
  2. 2POST /ticket/create for the charge
  3. 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.