Using the Reporting Events API

The Reporting Events API provides access to a totally-ordered, immutable stream of events coming from StudyTeam. You can use it to learn about things like a referred patient's progress through the recruiting phases of a trial or when a site has contacted a patient.

Events

The event stream is a sequence of events, ordered from oldest to newest. It contains different types of events, the details of which can be found in the Event Types Guide. The event stream will not change: existing events will never be removed or modified, and the events in the stream will never be reordered.

All events are wrapped in an identical envelope, which provides information about the event itself, with the following schema:

{
  "id": string
  "publishedTime": timestamp
  "eventType": string
  "message": object
}
  • id: a unique identifier for the event
  • publishedTime: when the event was published to the event stream. For more information on the different kinds of timestamps in the Reporting Events API, see Timestamps.
  • eventType: a string corresponding to one of the event types. Each event type consists of a version, a namespace, and a name, for example v1.patient.created is the first version of an event that indicates a patient was created as the result of a referral. For more information about versions and compatibility, see Compatibility.
  • message: the event itself

Timestamps

All timestamps are in GMT (Greenwich Mean Time).

Each event might contain multiple timestamps with names like publishedTime, referralReceivedTime, and contactAttemptDate. Of these, the publishedTime is special: it designates when an event was published to the event stream. This might be different from when the event occurred! Furthermore, events are not totally ordered by their publishedTime, the only way to obtain the order of the events in the stream is to use the order they were returned in from the API.

In short, publishedTime only tells you when an event was inserted into the stream, it should only be used for informational purposes.

To determine when the event occurred, you must parse the event itself. Different types of events have one or more timestamps that tell you domain-specific information about them. For example, v1.patient.created has a createdTime that tells you when the patient was created in StudyTeam. But v1.contactAttempt.created has two timestamps! createdTime that you when the contact attempt was entered into StudyTeam, and contactAttemptDate tells you the date on which the contact attempt occurred.

Compatibility

Backwards-incompatible changes to events will never occur. These types of changes include:

  • Removing fields from the event envelope or from event types
  • Changing the schema of fields in the event envelope or in event types
  • Changing the semantics of fields. For example, using a patient status option to refer to a different recruiting phase is backwards-incompatible.

However, certain types of backwards-compatible changes might occur in the future:

  • Fields may be added to the event envelope and to event types
  • New options for enumerations (such as patient status and contact attempt outcome) may be added that do not change the existing fields' semantics

For this reason, JSON objects in responses from the Reporting Events API must be parsed as an open set. Doing exclusive parsing, or throwing errors when unexpected fields are present, will cause your integration to fail at some point in the future.

New event types and new versions of existing event types may be added to the event stream in the future. These new events will have distinct names from existing events, and will not be included in API responses unless specifically requested (for details on how to specify which event types you want to receive, see Endpoints). Reify may coordinate with API consumers to deprecate existing events types on a case-by-case basis.

For example, a new event type v2.patient.created might be published in the future that is not compatible with v1.event.created. The first time you request events of this type from the API, you will receive events for all previously-created patients, including patients created prior to the new event's publication time. Such events will have a publishedTime that differs from their createdTime. You'll still be able to request v1.patient.created events though!

Endpoints

The event stream is exposed via a single endpoint: https://api.studyteamapp.com/rpi/events. With no arguments, it will return the entire event stream in its default order, however you can filter and paginate through it to find the events you're looking for (see Responses & Pagination).

When using the endpoint, you must provide a list of trial IDs and event types to return events about. For example, here's what a basic cURL might look like:

curl -X GET \
     -H 'Authorization: Bearer <BIFROST_ACCESS_TOKEN>' \
     -H 'Content-Type: application/json' \
     "https://api.studyteamapp.com/rpi/events?eventTypes=v1.patient.created&eventTypes=v1.patient.statusChanged&trialIds=example-trial"

For more information about the query parameters accepted by the events endpoint, see the API documentation.

Responses & Pagination

Responses from the Reporting Events API will have the following schema:

{
  "pagination": {
    "nextPage": URL
    "hasNextPage": bool
  }
  "events": [envelope]
}
  • events contains a list of event envelopes, as described in Events, in the order they occur on the event stream, from oldest to newest
  • nextPage: a URL pointing to the next page from the API containing newer events. These events might not exist yet!
  • hasNextPage: whether there are any newer events on the stream that match your query The Reporting Events API uses keyset pagination keyed on event IDs. This can be a little confusing, so let's go through some examples.

If your query returns more events than the specified limit, hasNextPage will be true. To retrieve the next page of results from the query, simply make an HTTP request to the URL returned in nextPage. This process can be repeated, calling nextPage on each subsequent response, until hasNextPage is false.

To periodically poll the API for new events, save the nextPage from the previous response and call it at some point in the future. It acts like a cursor that points to a particular location in the event stream. That's why it's always included in the response!

The nextPage URL is durable and can be stored indefinitely to be used at any point in the future.

Parsing the nextPage URL to manually construct cursors is not recommended, since the implementation of cursor URLs may change in the future. If you want to obtain events occurring after a particular point in the event stream, use the startingAfter query parameter (see the API documentation for details).

Sample Response

Request URL:

https://api.studyteam.com/rpi/events?eventTypes=v1.patient.created&eventTypes=v1.patient.referralAcknowledged&eventTypes=v1.patient.statusChanged&eventTypes=v1.contactAttempt.created&trialIds=ta-123&trialIds=ta-456&limit=4

Response Body:

{
  "pagination": {
    "nextPage": "https://api.studyteam.com/rpi/events?eventTypes=v1.patient.created&eventTypes=v1.patient.referralAcknowledged&eventTypes=v1.patient.statusChanged&eventTypes=v1.contactAttempt.created&trialIds=ta-123&trialIds=ta-456&limit=4",
    "hasNextPage": true
  },
  "events": [
    {
      "id": "abc122",
      "name": "v1.patient.created",
      "publishedTime": "2019-01-01T00:00:00.000Z",
      "message": {
        "referralReceivedTime": "2019-01-01T00:00:00.000Z",
        "siteId": "sa-123",
        "trialId": "ta-123",
        "referralSubjectId": "rs-123",
        "patientStatus": "potentialCandidate"
      }
    },
    {
      "id": "abc123",
      "name": "v1.patient.referralAcknowledged",
      "publishedTime": "2020-01-01T00:00:00.000Z",
      "message": {
        "referralAcknowledgedTime": "2020-01-01T00:00:00.000Z",
        "siteId": "sa-123",
        "trialId": "ta-123",
        "referralSubjectId": "rs-123"
      }
    },
    {
      "id": "abc124",
      "name": "v1.patient.statusChanged",
      "publishedTime": "2020-01-02T00:00:00.000Z",
      "message": {
        "statusChangedTime": "2020-01-02T00:00:00.000Z",
        "siteId": "sa-123",
        "trialId": "ta-123",
        "referralSubjectId": "rs-123",
        "patientStatus": "preScreening"
      }
    },
    {
      "id": "abc125",
      "name": "v1.patient.contactAttempt.created",
      "publishedTime": "2020-01-03T00:00:00.000Z",
      "message": {
        "createdTime": "2020-01-03T00:00:00.000Z",
        "siteId": "sa-123",
        "trialId": "ta-123",
        "referralSubjectId": "rs-123",
        "contactAttemptId": "ca-123",
        "contactAttemptDate": "2020-01-03",
        "contactAttemptOutcomes": ["noAnswer", "leftVoicemail"]
      }
    }
  ]
}