API Access
Overview
X-Assist allows integration with 3rd party software through its GraphQL API. It provides access to all features and data available in X-Assist.
The GraphQL API is served from a single endpoint:
Getting started
For first time users, we recommend using the interactive query tool available at https://x-assist.exop-group.com/devtools. The tool offers a query editor with autocompletion and a documentation browser which allows you to explore the GraphQL schema of X-Assist.
For example, to retrieve the three most recent events, you would perform the following query:
The API then responds with a JSON object that looks like this:
Authentication
The X-Assist API uses OAuth 2.0 Bearer Tokens to authenticate requests.
These tokens are issued by the EXOP IAM module, which implements a standards-compliant OAuth 2.0 authorization server and OpenID Connect provider. You can access its configuration data at https://accounts.exop-group.com/.well-known/openid-configuration.
Server-side applications should use the Client Credentials Grant to request an access token from the IAM's token endpoint.
Request an OAuth access token from the IAM
POST
https://accounts.exop-group.com/oauth/token
Request Body
Name | Type | Description |
---|---|---|
client_id | string | The OAuth client ID of your application. |
client_secret | string | The OAuth client secret of your application. |
grant_type | string | The OAuth grant type to use. Must be set to "client_credentials". |
Note that access tokens have a finite lifetime and expire at created_at + expires_in
seconds after the Unix epoch. After this time, you must request a new token from the IAM.
If you do not want to implement the token management yourself, you can use one of the existing client libraries for your programming language/environment instead.
Accessing the GraphQL endpoint
To query the GraphQL endpoint, the request must include an access token in the HTTP Authorization
header (preferred) or the access_token
query parameter.
GraphQL query
POST
https://x-assist.exop-group.com/graphql
Headers
Name | Type | Description |
---|---|---|
Authorization | string | The OAuth access token used to authenticate the request. Example value: "Bearer -sc-na0LtIghflDJ8rH8wBDdmRBSyd5iPF0znsVegeY" |
Request Body
Name | Type | Description |
---|---|---|
query | string | The GraphQL operation to execute. Can be a query, mutation or a subscription. See https://graphql.org/learn/queries/. |
operationName | string | Only required if multiple operations are present in the query. See https://graphql.org/learn/queries/#operation-name. |
variables | object | A dictionary of dynamic query arguments. See https://graphql.org/learn/queries/#variables. |
GraphQL does not use HTTP status codes, but instead uses an errors
array in the response.
The reason for this is that complex queries (e.g. ones that use multiplexing or are only partially executed) cannot be reasonably mapped to a single status code.
Therefore, API clients must always check the errors
array in the response. In addition to that, our GraphQL mutation payloads have a separate array of UserError
objects.
Entries in the top-level
errors
array are developer-facing errors (e.g. for syntax errors in the GraphQL query).Mutation errors are used to communicate user-facing errors, e.g. when an input field exceeds its maximum length.
Here is a full example showing how to retrieve the three most recent events from X-Assist:
Date and time
Timestamps and other time-related objects are crucial concepts in the X-Assist API, and it is critical that API clients handle them in the right way.
Timestamps
Fields with the scalar type DateTime
are the most basic ones. A DateTime
value is an ISO 8601 formatted string that represents an instant on the global timeline. DateTime
values in API responses are usually in UTC (indicated by a trailing Z
), but API clients must be able to handle values with other offsets as well. Unqualified DateTime
inputs (with an unspecified offset) are assumed to be in UTC.
A DateTime
field on an object is frequently accompanied by a timeZone
field that contains an IANA Time Zone ID that API clients can use to convert the UTC value back into local time. The time zone field can be on the same object as the timestamp itself (as is the case with HotelSegment
objects, for example), or within a nested object (e.g. FlightLeg
objects do not have timeZone
fields, but the associated airports do).
Time intervals
DateRange
objects combine two DateTime
fields to represent the time interval between the two points in time. The lower bound of the date range is always inclusive, while the upper bound is always exclusive.
Durations
Durations that do not refer to a fixed point in time are represented either as integers or as values of the type Duration
. For integers, the value is given in seconds. Duration
fields use ISO 8601 formatting.
ISO 8601 formatted durations can also be used in the DateTimeOrDuration
scalar, which is commonly used in the DateFilter
type. Here, durations are interpreted relative to the current time and negative values are permitted. For example, a date filter that covers the last 24 hours looks like this:
Geographical data
The X-Assist GraphQL schema uses the following types to represent geographical data:
GeoPoint
/GeoPointInput
GeoJSON
These types always use WGS 84 / SRID 4326 as spatial reference system.
Internationalization (I18n)
The content of X-Assist is available in the following languages:
Chinese
English
French
German
Italian
Japanese
Portuguese
Spanish
The HTTP Accept-Language
header determines the language in which the content of an API response is returned. Additionally, some fields in the GraphQL schema support a language
argument that allows API clients to explicitly request content in a specific language.
For example, clients may wish to retrieve the original English headline of an event in addition to machine translated headline:
Pagination
Collections of objects are exposed in the X-Assist API in two different ways. Collections with only a few items are exposed as plain lists (i.e. arrays) for simplicity:
Large collections use Relay Connections, a cursor-based pagination mechanism and the de-facto standard used by many GraphQL APIs.
Let's re-visit the earlier example query that retrieves the three most recent events:
The events
field in this query is a connection of type EventConnectionWithTotalCount
. Every connection has the fields pageInfo
, nodes
and edges
, and some connections (including the event connection) also provide additional fields such as totalCount
.
The number of items that can be returned in a single query is limited. Clients must be prepared to receive fewer items than requested.
Connections can expose additional data about the relationship between the objects on their edges.
For example, the edges of the affectedPeople
connection on the IncidentAlert
type have a distanceFromIncident
field that contains the distance in meters between the person and the event at the time of the alert.
If you do not need this information, you can select the nodes
field instead of edges
to remove one level of nesting from the API response.
To support pagination, the query must be changed and some fields from the PageInfo
object must be added to the selections:
The new query result now contains a cursor that can be used to fetch the next page:
If there are more items in the connection (i.e. hasNextPage
is true
), the next three events can be fetched by passing the cursor value in the after
argument:
This process is repeated until hasNextPage
is false
and the entire collection has been traversed.
Do not change filter predicates or sorting halfway through the pagination process, as this will lead to undefined results.
Object identification
Some types in X-Assist's GraphQL schema implement the Node
interface which allows API clients to retrieve one or more objects by ID. Refer to the Global Object Identification chapter in the GraphQL documentation for further information.
All applications must treat object IDs as opaque strings and make no assumptions about their content or length.
The encoding scheme of these IDs may also change over time. These changes are backwards compatible (X-Assist will continue to accept IDs in previous formats), but applications that store references to X-Assist objects are encouraged to include the id
field in each query and update the IDs in their own data stores when a change is detected.
Optimistic Concurrency Control
Many mutations use optimistic locking to prevent lost updates when multiple users make changes to existing data concurrently. The input objects for these mutations have a mandatory version
field.
When performing a mutation, pass the expected version of the object as input and add the version
field to your selections to get the new version of the object after the update. As an example, the mutation to update a site looks as follows:
The response contains the new version of the site:
If a version mismatch occurs, you will receive an error instead:
API clients can handle such errors by re-fetching the object (see Object identification) to get the current version, and then retrying the mutation.
Do not make assumptions about the content and the behavior of the version field.
In particular, do not assume that the version of new objects is equal to 1 or that updates only increment the version by one. Be aware that background processes or other events can also change the version of an object without any requests being sent to X-Assist from an API client.
For maximum robustness, treat the version as an opaque string.
Data consistency
The X-Assist API provides different data consistency guarantees for different kinds of operations.
Mutations
GraphQL mutation payloads provide strong consistency. When you perform a mutation, all fields in the response are guaranteed to return the most recent data and reflect your changes.
However, this does not apply to changes caused indirectly by a mutation. For instance, when you register a new trip in X-Assist, the trip itself will be visible immediately, but the person's itinerary stops (expected locations) will only be updated after a delay because they are re-computed by an asynchronous background job.
Subscriptions
GraphQL subscriptions offer the same guarantees as mutations.
Queries
Unlike mutations, regular GraphQL queries only offer eventual consistency. This means that changes may not immediately visible to all clients, and that a query can return stale data.
Rate limiting
Regular users and OAuth applications can make up to 1000 requests per hour, with a maximum number of 25 + 1 burst requests in rapid succession. This limit can be raised once your solution/integration has been validated by EXOP.
The HTTP response headers will show your current rate limit status:
We loosely follow the IETF draft for rate limiting headers, but we do not use fixed-length time windows.
When a request is blocked due to rate limiting, X-Assist will respond with HTTP status 429 ("Too Many Requests"):
Response Header | Description |
---|---|
X-RateLimit-Limit | The maximum number of available burst requests when no previous requests have been made, or the previous requests occurred long enough ago. |
X-RateLimit-Remaining | The number of requests that can be made immediately (i.e. the remaining burst request count). |
X-RateLimit-Reset | The time in seconds until the full number of burst requests will be available again. |
Retry-After | Only present when the request was blocked to due rate limiting. For allowed requests that can be retried later (see note below), this header will contain the duration to wait in seconds before the request will be allowed. When the request is forbidden due to complexity constraints, this header will have no value (i.e. an empty string). |
Not all requests are equal. For example, a request with a very complex GraphQL operation may consume multiple "logical requests" at once.
It is therefore possible to create requests that will never succeed because they would consume more logical requests than the maximum number of available burst requests.
Last updated