List events
- Permissions
- Depends on the request
Returns changes to customers, customer locations, jobs and members, in order, starting after a cursor you saved from an earlier response.
Use it to keep a copy of FieldTask records up to date by polling, instead of or as well as webhooks. Each event tells you which record changed and the names of the fields that changed. It doesn't include field values: read the current record with the matching list or get endpoint.
The feed covers only these four record families. Quotes, invoices, products and member invitations have no events.
This page is generated from contract 2026-11-03-review.2, a review export that hasn't been released. FieldTask enables the event feed separately from the rest of the API.
Before you start
You need:
- API access for your organization. An owner or admin requests it in Settings → API connections.
- A REST credential: an API token created in Settings → API connections, or an access token from an OAuth client-credentials connection. Tokens from an MCP sign-in don't work with the REST API.
- Change sync enabled for your organization. FieldTask enables the feed, then enrolls your organization for each record family separately. Until then the feed returns
404 resource_not_found(not enabled in this environment),503 sync_unavailable(organization not enrolled) or403 insufficient_scope(a family you asked for isn't enrolled). Ask FieldTask which families are enabled for your organization. - Read permission for each family you want. See the table below.
For a step-by-step polling design, see the Event feed guide.
Permissions
This operation has no fixed permission requirement; see the conditions below.
Each record family in resources needs its own read permission:
| When | Additional permission | Including its dependencies |
|---|---|---|
resources includes customer_locations | customer_locations:read, customers:read | customer_locations:read, customers:read |
resources includes customers | customers:read | customers:read |
resources includes jobs | jobs:read | jobs:read |
resources includes members | members:read | members:read |
If you leave out resources, you get every family that FieldTask has enabled, your organization is enrolled for and the connection can read. If there are none, the request fails with 403 insufficient_scope.
Changing a connection's permissions, even adding an unrelated one, invalidates every cursor issued to it. See Recovery.
Request
Responses
- 200
- 400
- 401
- 403
- 404
- 405
- 409
- 410
- 429
- 503
OK
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Bad Request
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Unauthorized
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Forbidden
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Not Found
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Method Not Allowed
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Conflict
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Gone
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Too Many Requests
Response Headers
Responses must not be cached.
Seconds before retry; present on 429 and an in-progress operation.
Server-generated request correlation ID.
Service Unavailable
Response Headers
Responses must not be cached.
Server-generated request correlation ID.
Parameters
| Parameter | Required | What to send |
|---|---|---|
after | Yes | now for your first request, then the page.next_cursor from your previous response. Up to 4,096 characters. URL-encode it. |
resources | No | A comma-separated list of distinct families: customers, customer_locations, jobs, members. Order doesn't matter. Omit it to get every family available to the connection. |
limit | No | The maximum number of events per page, from 1 to 100. Defaults to 25. |
Send the same resources with every cursor. A cursor only works with the selection it was issued for; any other selection returns 400 invalid_cursor. You can change limit between requests.
We recommend always sending resources. A cursor issued without it is tied to the families that were available at that time, so if FieldTask later enrolls another family for your organization, the cursor stops working and you have to start again.
How to poll
- Bootstrap. Request
after=now. The response has no events, and itsnext_cursormarks the current end of the feed. Save it. - Backfill. Read every record you need with the list endpoints, such as
GET /public/v1/customersandGET /public/v1/jobs, and store each record'srevision. - Poll. Request
after=your saved cursor. Apply the events, then save the newnext_cursor. Waitpage.retry_after_secondsand repeat.
Take the cursor before you backfill. Changes made while you backfill then also appear in the feed, so nothing is missed. Some of those events describe changes your backfill already contains; revision lets you skip them.
The Event feed guide covers this loop, storage and recovery in more detail.
The page object
| Field | Meaning |
|---|---|
next_cursor | The cursor to send as after next time. Every successful response has one, including after=now and pages with no events. Store the latest. |
has_more | true if more events are already waiting after this cursor. |
retry_after_seconds | How long to wait before your next request. It's short while has_more is true and longer once you've caught up. Use the value returned; don't hard-code it. |
A page can contain fewer events than limit, or none at all, while has_more is true. Events for families you didn't select, and updates to records that are no longer visible, are skipped, but the cursor still moves past them. Keep reading until has_more is false.
Every request counts against your organization's daily event-feed allowance, including after=now and empty pages. The allowance is separate from ordinary reads. Polling faster than retry_after_seconds uses it up without returning more data.
Cursors aren't consumed. If your process stops before it saves a new cursor, request the previous one again: you get the same events, so make sure applying an event twice is harmless.
Events
Each event describes one change to one record:
| Field | Meaning |
|---|---|
event_id | Unique ID of this event. Use it to ignore an event you've already applied. |
schema_version | Always 1 in this version. |
event_type | The family and kind of change, such as job.updated. See Event types. |
resource.type | customers, customer_locations, jobs or members. |
resource.id | ID of the record that changed. |
resource.revision | The record's revision after this change. |
changed_fields | Names of the fields that changed, limited to fields this connection can read. Values aren't included. |
tombstone | true for *.deleted events. |
observed_at | When FieldTask recorded the change, in UTC. |
published_at | When the event was added to the feed, in UTC. |
Events are returned in the order FieldTask added them to the feed. That is close to, but not always exactly, the order the changes happened, so use revision rather than position to decide which state is newer.
Revisions
A record's revision starts at 1 and increases with every recorded change. It's the same number as the revision field in that record's REST responses. Gaps between the revisions you see are normal.
- Ignore an event whose
revisionis less than or equal to the revision you've stored for that record. - After reading the current record, store the
revisionfrom the read response, not from the event.
Created, updated and deleted
*.created: the record became visible through the API, either because it was created or because a deleted record was restored.changed_fieldslists all of its fields.*.updated: one or more fields changed. Updates to a record that isn't visible when you read the page are skipped; you'll receive its*.deletedevent instead.*.deleted: the record was deleted or is no longer visible through the API.tombstoneistrueandchanged_fieldsis empty. Deleted events are always returned, so you can remove the record from your copy. A record can later be created again with a higher revision.
Recovery
Two problems mean the cursor can never be used again. Both include "resync_required": true:
| Problem | Cause |
|---|---|
410 history_expired | The cursor is more than 30 days old, or the changes after it are no longer kept. The feed keeps changes for 30 days. |
409 access_context_changed | Since the cursor was issued, the connection's permissions changed, its access was otherwise changed, or FieldTask changed what the feed includes for your organization. |
Don't retry the same cursor. Instead:
- Request
after=nowand keep the new cursor. - Backfill again from the list endpoints. Replace your local copy, including removing records that no longer appear.
- Continue polling with the new cursor.
Other problems are recoverable without a resync:
400 invalid_cursor: you sent a cursor that was modified, belongs to another connection or was issued for differentresources. Send the right cursor with its own selection. If you've lost it, start again as above.429: wait forRetry-Afterseconds, then retry with the same cursor.503 sync_unavailable: retry with backoff using the same cursor. If it continues, ask FieldTask to check that sync is enabled for your organization.
Errors
| Status | code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_query | The query has an unknown or repeated parameter, after is longer than 4,096 characters, limit isn't a whole number from 1 to 100, or resources names an unknown family or repeats one. | Send only after, resources and limit, each once. |
| 400 | invalid_cursor | after is missing or empty, or the cursor was changed, belongs to another connection, or was issued for a different resources selection. | Send the cursor exactly as returned, with the same resources it was issued for. If you've lost it, start again with after=now and backfill. |
| 401 | invalid_token | The bearer token is missing, malformed, expired, revoked or issued for a different resource (for example an MCP sign-in). | Send a current REST API token or REST OAuth access token as Authorization: Bearer …. |
| 403 | insufficient_scope | A family in resources isn't available to this connection: FieldTask hasn't enabled it, your organization isn't enrolled for it, or the connection lacks its read permissions. Without resources, no family is available. error.code is missing_permission. | Check the permissions in the table above, or remove the family from resources. Ask FieldTask whether the family is enabled for your organization. |
| 404 | resource_not_found | FieldTask hasn't enabled the event feed in this environment, or the URL is wrong. | Check the API origin and the path /public/v1/events (no trailing slash). |
| 405 | method_not_allowed | This path doesn't accept the HTTP method, or the create isn't enabled in this environment. The Allow header lists the methods that are. | Use a method listed in Allow. |
| 409 | access_context_changed | The connection's permissions, enrollment or resource selection changed since this cursor was issued. resync_required is true. | Get a new cursor with after=now, then rebuild the affected local data from the list endpoints. |
| 410 | history_expired | The cursor points to changes older than the feed keeps. resync_required is true. | Get a new cursor with after=now, then rebuild your local data from the list endpoints. |
| 429 | rate_limit_exceeded | Too many requests in the current minute, or too many concurrent requests, for the organization or connection. | Wait for Retry-After seconds, then retry. |
| 429 | daily_quota_exceeded | The organization or connection has used its daily event-feed allowance. Every feed request counts, including after=now and empty pages. The allowance is separate from ordinary reads and resets at 00:00 UTC. | Wait for Retry-After seconds. Poll no faster than page.retry_after_seconds. |
| 503 | sync_unavailable | The feed can't be read right now, or change sync isn't set up for your organization or for a family you selected. | Retry with backoff using the same cursor. If it continues, ask FieldTask to confirm that sync is enabled for your organization. |