GraphQL API
Read and change Revolution St data through one GraphQL endpoint.
Getting started
- Create an API key on Configuration → API Access, or use any other credential the REST API accepts.
- Send it as
Authorization: Bearer …on aPOSTtohttps://v3.revolutionstreetwxs.io/api/graphql. The sections below cover the request format, filters and error codes. - To browse the full schema, open
https://v3.revolutionstreetwxs.io/api/graphqlin a browser. It shows GraphiQL, with autocomplete and docs for every query and mutation.
The endpoint
Send a POST to https://v3.revolutionstreetwxs.io/api/graphql with a JSON body { "query": "...", "variables": { ... } }. Opening the same URL in a browser shows GraphiQL, a query editor with autocomplete, inline docs and history.
The schema has one query per entity (orders, waves, products, walls, locations, …), an aggregate query per entity (ordersAggregate), queries for the activity logs (logs), and create, update and delete mutations for every entity that can be edited in the app. Introspection is enabled, so any GraphQL client can download the schema.
Authentication
The GraphQL API accepts the same credentials as the REST API: an API key, an OAuth access token or a federated wxs_ft_… token, sent as a bearer token:
curl -X POST https://v3.revolutionstreetwxs.io/api/graphql \
-H "Authorization: Bearer wxs_pk_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ orders(limit: 5) { total items { orderNumber status } } }"}'
Results are limited to the credential’s environment. Each query and mutation lists the permission it requires in its description (the same permissions as roles and keys, such as orders:ListOrders or walls:CreateWall). The key’s rate limit applies, counting one request per operation, and responses include the RateLimit-* headers.
If you’re signed in to Revolution St in the same browser, GraphiQL sends requests as you (its Headers pane contains {"x-wxs-try-it": "1"}). Otherwise, put {"Authorization": "Bearer wxs_pk_…"} in the Headers pane.
Queries
Each entity query takes where, orderBy, limit (default 100, maximum 1000), offset, and since / until (on createdAt). It returns items and a total. The total is only counted when you select it, and only the fields you select are read from the database.
query OpenOrders($where: JSON) {
orders(
where: $where
orderBy: [{ column: createdAt, direction: DESC }]
limit: 20
) {
total
items { id orderNumber status priority customerName createdAt }
}
}
with variables { "where": { "status": { "inArray": ["pending", "picking"] } } }. Enum fields are returned as GraphQL enums (PENDING, ON_DEMAND). The field description lists the values to use in filters.
Filtering with where
where is a JSON argument that uses the same Drizzle-style syntax as the export API: {"status": "shipped"} for equality, {"priority": {"gte": 2}, "name": {"ilike": "%wall%"}} with operators eq ne gt gte lt lte inArray notInArray like notLike ilike notIlike isNull isNotNull between notBetween, combined with {"and": [...]}, {"or": [...]} and {"not": {...}}. Column names are the type’s field names. Enum columns take the stored value ("on-demand", not ON_DEMAND). Timestamps are ISO 8601 strings. An unknown column or operator returns a BAD_USER_INPUT error.
Pagination and aggregates
Page through results with limit and offset. Without orderBy, rows are sorted by createdAt and then id, so pages stay consistent. <entity>Aggregate groups by a column and returns one { group, value } per value, largest first. The value is a count by default, or SUM, AVG, MIN or MAX of an aggregateField:
{
ordersAggregate(groupBy: status) { group value }
wavesAggregate(groupBy: status, aggregate: SUM, aggregateField: totalItems) { group value }
}
Activity logs
Activity such as scans, picks and diverts is stored in separate log tables. logDatasets lists the ones your credential can read, with their columns. logs(dataset: SCAN_LOG, …) returns rows as JSON objects and takes the same where, orderBy, since / until and paging arguments. logsAggregate groups them.
{
logs(dataset: PICK_PUT_LOGS, since: "2026-09-01T00:00:00Z", limit: 50,
orderBy: [{ column: "created_at", direction: DESC }]) {
total
rows
}
}
Mutations
Entities that can be edited in the app have create<Type>, update<Type> and delete<Type> mutations. Their input types use the same validation as the app and the REST API, and GraphiQL autocompletes their fields. A change made through GraphQL is validated, recorded in the audit log and in the History timeline, the same as a change made in the app.
mutation {
createWall(input: { name: "Pack Wall 3", rows: 4, cols: 6, dualSided: false, sideAName: "A", sideBName: "B" }) {
id
name
createdAt
}
}
A few nested fields, such as a wall’s cubby grid or a report’s sections, have the type JSON. The field description explains their structure.
Errors
Errors are returned in errors[], each with an extensions.code:
UNAUTHENTICATED(HTTP 401): the credential is missing or invalid.FORBIDDEN: the credential doesn’t have the permission the field requires. The message names it.BAD_USER_INPUT: an invalidwhere, a page size out of range, or invalid mutation input.extensions.issueslists the fields.NOT_FOUND: no such record in your environment.ALREADY_EXISTS: a value that must be unique is already in use.extensions.fieldnames the field.RATE_LIMITED(HTTP 429): wait untilRateLimit-Resetbefore retrying.
These match the REST API’s status codes.