Skip to main content
GET
/
api
/
public
/
v1
/
updates
curl -sS "https://app.opentrain.ai/api/public/v1/updates?cursor=$LAST_CURSOR&limit=50" \
  -H "Authorization: Bearer $OT_API_TOKEN"
{
  "events": [
    {
      "id": "1042",
      "type": "proposal.received",
      "apiVersion": "v1",
      "createdAt": "2026-06-12T10:00:00.000Z",
      "resourceId": "<PROPOSAL_ID>",
      "jobId": "<JOB_ID>",
      "data": {
        "proposalId": "<PROPOSAL_ID>",
        "jobId": "<JOB_ID>"
      }
    },
    {
      "id": "1043",
      "type": "approval.confirmed",
      "apiVersion": "v1",
      "createdAt": "2026-06-12T10:05:00.000Z",
      "resourceId": "<APPROVAL_ID>",
      "jobId": "<JOB_ID>",
      "data": {
        "approvalId": "<APPROVAL_ID>",
        "status": "confirmed"
      }
    }
  ],
  "nextCursor": "1043",
  "hasMore": false
}
Fetches everything that happened on your account since your last poll in one cheap call, instead of re-reading every resource. Events are returned oldest first; pass the returned nextCursor on the next poll to receive only newer events. Payloads carry IDs only — fetch details with the matching read endpoint. Webhooks push these same events; the recommended architecture uses the webhook as the trigger and this feed as the source of truth — see stay in sync. For tokenless public marketplace changes, use GET /jobs/changes instead. Requirements: at least one of proposals:read, messages:read, or payments:read — each event type is visible only with its scope (below). Works pre-claim.

Event types

typeRequired scopeKey data fields
proposal.receivedproposals:readproposalId, jobId
proposal.status_changedproposals:readproposalId, status
message.receivedmessages:readconversationId, messageId
contract.createdpayments:readcontractId, jobId, proposalId
milestone.status_changedpayments:readmilestoneId, contractId, status
payment.pendingpayments:readinvoiceId, milestoneId, contractId
approval.confirmedpayments:readapprovalId, status
contract.budget_state_changedpayments:readcontractId, previousState, state, paymentType, fundedVolume, consumedVolume, remainingVolume, consumedFraction
Event types your token cannot read are silently filtered out of the feed; they are not an error. A token with none of the three scopes gets 403.

Request

cursor
string
The nextCursor from your previous poll (a numeric event ID). Omit on the first poll to start from the beginning of your account’s event history.
limit
integer
default:"50"
Max events to return, 1–200.

Response

events
object[]
nextCursor
string | null
Persist this and pass it as cursor on the next poll. When the page is empty it echoes your input cursor (null on a first poll with no events).
hasMore
boolean
true when more events already exist beyond this page — poll again immediately with nextCursor.

Errors

StatuscodeMeaning
400BAD_REQUESTcursor is not a numeric event ID (details.field: "cursor"), or limit outside 1–200 (details.field: "limit")
401UNAUTHORIZEDMissing or invalid token
403FORBIDDENToken has none of proposals:read, messages:read, payments:read (details.requiredScopes, details.grantedScopes)
curl -sS "https://app.opentrain.ai/api/public/v1/updates?cursor=$LAST_CURSOR&limit=50" \
  -H "Authorization: Bearer $OT_API_TOKEN"
{
  "events": [
    {
      "id": "1042",
      "type": "proposal.received",
      "apiVersion": "v1",
      "createdAt": "2026-06-12T10:00:00.000Z",
      "resourceId": "<PROPOSAL_ID>",
      "jobId": "<JOB_ID>",
      "data": {
        "proposalId": "<PROPOSAL_ID>",
        "jobId": "<JOB_ID>"
      }
    },
    {
      "id": "1043",
      "type": "approval.confirmed",
      "apiVersion": "v1",
      "createdAt": "2026-06-12T10:05:00.000Z",
      "resourceId": "<APPROVAL_ID>",
      "jobId": "<JOB_ID>",
      "data": {
        "approvalId": "<APPROVAL_ID>",
        "status": "confirmed"
      }
    }
  ],
  "nextCursor": "1043",
  "hasMore": false
}