GraphQL API
CXTMS provides a comprehensive GraphQL API for interacting with the system. The API supports queries, mutations, and subscriptions for real-time updates.
Base URL
https://api.cargoxplorer.com/graphql
Authentication
All GraphQL requests require authentication via Bearer token in the Authorization header:
Authorization: Bearer <your-access-token>
Common Patterns
Entity Fields Lookup
The entityFields query accepts an optional entityName argument. Matching is case-insensitive and uses SQL ILIKE, so callers can pass an exact entity name or an ILIKE pattern.
query {
entityFields(organizationId: 1, entityName: "Order", take: 1000) {
items {
name
fieldDefinition
}
}
}
Use search to match field names by substring.
Pagination
List queries support offset-based pagination with the following parameters:
| Parameter | Type | Description |
|---|---|---|
skip | Int | Number of items to skip |
take | Int | Number of items to return (default: 20, max: 100) |
Filtering
Most list queries support filtering via the filter parameter using Lucene query syntax:
query {
getAttachments(organizationId: 1, filter: "attachmentType:Picture") {
items {
attachmentId
fileName
}
}
}
Sorting
Use the orderBy parameter to sort results:
query {
getAttachments(organizationId: 1, orderBy: "created desc") {
items {
attachmentId
fileName
}
}
}
Search
Full-text search is available via the search parameter. For orders and orderGroupBy, order quick search also includes linked commodity InventoryItem values (sku, productName, description, modelNumber, and customValues) so warehouse item identifiers can find the containing orders.
query {
getAttachments(organizationId: 1, search: "invoice") {
items {
attachmentId
fileName
}
}
}
Contact Import Mutation
importContacts
importContacts imports contacts from an uploaded CSV, JSON, or XLSX file. The importer now processes rows as dynamic dictionaries, so inbound keys are matched case-insensitively and can use either exact entity field names or columnMappings.
mutation ImportContacts($input: ImportContactsInput!) {
importContacts(input: $input) {
added
updated
errors
}
}
{
"input": {
"organizationId": 1,
"fileUploadUrl": "uploads/imports/contacts.xlsx",
"contactType": "Customer",
"columnMappings": {
"Name": "Company Name",
"EmailAddress": "Email",
"Division.DivisionName": "Division"
}
}
}
Import behavior
- Existing contacts are matched by
ContactIdfirst. Primary keys (ContactId,OrganizationId) are not applied as update fields. - Empty strings and nulls are skipped on updates so blank import cells do not wipe existing values.
contactTypeis an optional mutation-level default for new contacts. A row-levelContactTypevalue wins when present. If neither is supplied, new contacts default toContact.- Nested division data can resolve
DivisionIdbydivision.divisionName/Division.DivisionNamewithin the organization. - New rows without
Nameare skipped rather than creating invalid contacts.
Order Resolvers
getCommoditiesWithRelatedOrder
Returns the flat list of leaf commodities linked to shipments of a specified order type within an order's commodity hierarchy. Available as a field on the Order type.
query {
getOrders(organizationId: 1, take: 1) {
items {
orderId
getCommoditiesWithRelatedOrder(orderType: "ParcelShipment", filter: "weight>0") {
commodityId
description
pieces
weight
}
}
}
}
Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
orderType | String! | Yes | The related order type to filter by (e.g., ParcelShipment, PickupOrder). Uses the OrderTypes enum values (case-insensitive). |
filter | String | No | Lucene filter to apply on the returned commodities |
Behavior
- Traverses the order's commodity hierarchy via
OrderCommodityHierarchyView - Finds commodities linked to related orders of the specified type
- Returns only leaf commodities — excludes wrapper/consolidated commodities whose children are also linked
- Supports projection and filtering
getContactAddress (Order)
Returns a contactAddress from an order custom-value field that stores a contact-address id. Available as a field on the Order type.
query {
getOrders(organizationId: 1, take: 1) {
items {
orderId
getContactAddress(idPropertyName: "pickupContactAddressId") {
contactAddressId
name
addressLine
cityName
}
}
}
}
| Parameter | Type | Required | Description |
|---|---|---|---|
idPropertyName | String! | Yes | Custom-values key on the order containing the ContactAddressId. |
The resolver returns an empty result when the custom value is missing and enforces organization scope when loading the address.
Tracking Event Resolvers
getLastTrackingEvent (Order)
Returns the most recent tracking event for an order. Available as a field on the Order type.
The resolver uses a DataLoader to batch all lastTrackingEvent requests within a single GraphQL operation into one or two database round-trips, eliminating N+1 queries when fetching tracking events for multiple orders at once.
query {
getOrders(organizationId: 1, take: 10) {
items {
orderId
getLastTrackingEvent(eventDefinitionName: "Departed") {
trackingEventId
eventDate
description
}
}
}
}
Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
eventDefinitionName | String | No | Filter to events of a specific definition name. When omitted, the latest event of any type is returned. |
orderBy | String | No | Controls which event is selected as the "winner". Use the standard -field prefix convention: omit or pass a value starting with - for DESC (latest / most recent event); pass a value without a - prefix for ASC (earliest / "first seen" event). Defaults to DESC when omitted. |
Sort Order
The winning event is determined using the canonical two-key ordering:
orderBy direction | Key 1 | Key 2 (tie-breaker) |
|---|---|---|
DESC (default — omitted or starts with -) | COALESCE(EventDate, Created) DESC | TrackingEventId DESC |
| ASC (any non-prefixed value) | COALESCE(EventDate, Created) ASC | TrackingEventId ASC |
EventDate is used when set; falls back to Created so events that only have a creation timestamp are still included in the ordering.
DataLoader Batching
All getLastTrackingEvent calls sharing the same (OrganizationId, EventDefinitionName, OrderBy) are grouped into a single batch query. The DataLoader:
- Fetches only the minimum columns needed (
TrackingEventId, sort date) to determine the winner per order - Re-fetches the winning events through AutoMapper projection, materializing only the fields requested in the GraphQL selection set
Because OrderBy is part of the batch key, a query that requests both ASC and DESC lastTrackingEvent in the same operation will issue two separate batch queries — one per direction.
getCommodityLastTrackingEvent (Commodity)
Returns the most recent tracking event for a commodity. Available as a field on the Commodity type. Mirrors getLastTrackingEvent but walks the Commodity ↔ TrackingEvent relationship.
query {
getCommodities(organizationId: 1, take: 10) {
items {
commodityId
description
getCommodityLastTrackingEvent(eventDefinitionName: "Arrived") {
trackingEventId
eventDate
description
}
}
}
}
Arguments
| Parameter | Type | Required | Description |
|---|---|---|---|
eventDefinitionName | String | No | Filter to events of a specific definition name. |
orderBy | String | No | Controls which event is selected as the "winner". Omit or pass a --prefixed value for DESC (latest); any non-prefixed value for ASC (earliest). Defaults to DESC when omitted. |
Sort Order
Identical to the Order resolver — canonical two-key ordering:
orderBy direction | Key 1 | Key 2 (tie-breaker) |
|---|---|---|
| DESC (default) | COALESCE(EventDate, Created) DESC | TrackingEventId DESC |
| ASC | COALESCE(EventDate, Created) ASC | TrackingEventId ASC |
DataLoader Batching
All getCommodityLastTrackingEvent calls with the same (OrganizationId, EventDefinitionName, OrderBy) are batched together. The OrganizationId used for batching is taken from the parent Commodity.OrganizationId, which is always projected via AutoMapperProjectTo to ensure it is available.
getWeightTotal (Commodity)
Returns a commodity's total weight in the requested unit. If weightUnit is omitted, the resolver uses the commodity's own weightUnit.
query {
getCommodities(organizationId: 1, take: 10) {
items {
commodityId
description
weightUnit
getWeightTotal(weightUnit: "Lbs")
}
}
}
| Parameter | Type | Required | Description |
|---|---|---|---|
weightUnit | String | No | Target weight unit. Defaults to the parent commodity's weightUnit. |
Entity Field Metadata
entityFields results include priority, the module-level precedence used when multiple app modules define the same field name for the same entity. When querying a specific entityName, inactive fields are filtered out and duplicate field definitions resolve to the highest-priority active field.
query {
entityFields(organizationId: 1, entityName: "ParcelShipment") {
items {
name
isCustomField
isInactive
priority
fieldDefinition
}
}
}
Available APIs
- Attachments - File attachments and document management
- Order Group By - Multi-field grouping of orders with aggregate summaries
- Order Time Buckets - Time-series aggregation of orders for analytics and charts