Documentation Index
Fetch the complete documentation index at: https://docs.sensorup.com/llms.txt
Use this file to discover all available pages before exploring further.
Common Patterns
This guide covers common patterns you’ll use throughout the SensorUp GraphQL API.
The API uses Relay-style cursor-based pagination for all list queries. This provides consistent forward and backward pagination with cursor stability.
Connection Structure
type Connection {
edges: [Edge]
pageInfo: PageInfo!
}
type Edge {
node: NodeType
cursor: String
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
query GetAssets($first: Int!, $after: String) {
assetProfiles {
byId(profile: "your-profile") {
assets(first: $first, after: $after) {
edges {
node {
id
displayName
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
First page:
Next page:
{
"first": 20,
"after": "cursor-from-endCursor"
}
query GetAssets($last: Int!, $before: String) {
assetProfiles {
byId(profile: "your-profile") {
assets(last: $last, before: $before) {
edges {
node {
id
displayName
}
}
pageInfo {
hasPreviousPage
startCursor
}
}
}
}
}
- Use reasonable page sizes: 10-100 items per page depending on data size
- Store cursors, not offsets: Cursors remain valid even when data changes
- Check hasNextPage: Don’t assume there’s always a next page
- Handle missing cursors: Some connections may not return cursors for every edge
Filtering
Most list queries support filtering through typed input objects.
Time-Based Filtering
query GetRecentAssets($filter: AssetFilter!) {
assetProfiles {
byId(profile: "your-profile") {
assets(first: 20, filter: $filter) {
edges {
node {
id
displayName
modifiedAt
}
}
}
}
}
}
Variables:
{
"filter": {
"modifiedAfter": "2025-11-01T00:00:00Z"
}
}
Property-Based Filtering
Some queries allow filtering by specific properties:
query FilteredObservations(
$startTime: DateTime,
$endTime: DateTime,
$properties: [String!]
) {
assetProfiles {
byId(profile: "your-profile") {
upQueryObservations(
startTime: $startTime,
endTime: $endTime,
properties: $properties
)
}
}
}
Variables:
{
"startTime": "2025-11-01T00:00:00Z",
"endTime": "2025-11-05T00:00:00Z",
"properties": ["temperature", "pressure"]
}
Error Handling
The API uses two error patterns: GraphQL-level errors and mutation-level errors.
GraphQL Errors
GraphQL errors appear in the errors array at the top level:
{
"errors": [
{
"message": "Cannot query field 'invalidField' on type 'Asset'",
"locations": [{ "line": 3, "column": 5 }],
"path": ["assetProfiles", "byId", "invalidField"]
}
],
"data": null
}
Common GraphQL error types:
- Validation errors: Invalid query syntax or schema violations
- Authentication errors: Missing or invalid credentials
- Authorization errors: Insufficient permissions
- Internal errors: Server-side issues
Mutation Errors
Mutations return errors in their result type using the MutationError interface:
interface MutationError {
message: String!
type: String!
}
Example mutation with errors:
mutation CreateAssetFilter($input: CreateAssetFilterInput!) {
createAssetFilter(input: $input) {
assetFilter {
id
displayName
}
correlationId
errors {
message
type
}
}
}
Response with validation error:
{
"data": {
"createAssetFilter": {
"assetFilter": null,
"correlationId": "abc-123",
"errors": [
{
"message": "Display name is required",
"type": "VALIDATION_ERROR"
}
]
}
}
}
Error Handling Pattern
const result = await executeQuery(mutation, variables)
// Check for GraphQL-level errors
if (result.errors) {
console.error('GraphQL errors:', result.errors)
return
}
// Check for mutation-level errors
const { assetFilter, errors } = result.data.createAssetFilter
if (errors && errors.length > 0) {
console.error('Mutation errors:', errors)
return
}
// Success
console.log('Created asset filter:', assetFilter)
GeoJSON Support
The API has comprehensive GeoJSON support for geospatial data.
GeoJSON Types
enum GeoJSONType {
Point
MultiPoint
LineString
MultiLineString
Polygon
MultiPolygon
GeometryCollection
Feature
FeatureCollection
}
Querying Geometry
query GetAssetGeometry($profileId: ID!, $assetId: ID!) {
assetProfiles {
byId(profile: $profileId) {
assetById(id: $assetId) {
id
displayName
geometry {
type
coordinates
bbox
}
geoJSONFeature
}
}
}
}
Response:
{
"data": {
"assetProfiles": {
"byId": {
"assetById": {
"id": "asset-123",
"displayName": "Well Site A",
"geometry": {
"type": "Point",
"coordinates": [-114.0719, 51.0447],
"bbox": null
},
"geoJSONFeature": {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-114.0719, 51.0447]
},
"properties": {
"name": "Well Site A"
},
"id": "asset-123"
}
}
}
}
}
}
input GeoJSONGeometryInput {
type: GeoJSONType!
coordinates: GeoJSONCoordinates
bbox: [Float]
}
Example usage in mutations:
{
"geometry": {
"type": "Point",
"coordinates": [-114.0719, 51.0447]
}
}
Coordinate Systems
- All coordinates use WGS84 (EPSG:4326)
- Coordinate order:
[longitude, latitude] (GeoJSON standard)
- Elevations can be included as third coordinate:
[lon, lat, elevation]
Audit Trails
Many types include audit information for tracking changes.
Audit Type
type Audit {
createdTime: DateTime!
updatedTime: DateTime!
createdBy: UserReference
updatedBy: UserReference
}
query GetIssueAudit($issueId: ID!) {
issues {
byId(id: $issueId) {
id
displayName
audit {
createdTime
updatedTime
createdBy {
userId
}
updatedBy {
userId
}
}
}
}
}
Response:
{
"data": {
"issues": {
"byId": {
"id": "issue-123",
"displayName": "Equipment Failure",
"audit": {
"createdTime": "2025-11-01T10:00:00Z",
"updatedTime": "2025-11-03T14:30:00Z",
"createdBy": {
"userId": "user-1"
},
"updatedBy": {
"userId": "user-2"
}
}
}
}
}
}
Entity References
Apollo Federation enables cross-subgraph entity resolution.
User References
type UserReference @key(fields: "userId") {
userId: ID!
}
Query user references:
query GetUserDetails {
issues {
byId(id: "issue-123") {
audit {
createdBy {
userId
# Additional user fields resolved by su-user subgraph
}
}
}
}
}
Asset References
type AssetReference @key(fields: "profile assetId") {
profile: ID
assetId: ID!
asset: Asset
}
DateTime Handling
All timestamps use ISO-8601 format in UTC.
2025-11-05T14:30:00Z
2025-11-05T14:30:00.123Z (with milliseconds)
Querying with DateTime
query GetRecentData($startTime: DateTime!, $endTime: DateTime!) {
assetProfiles {
byId(profile: "your-profile") {
upQueryObservations(
startTime: $startTime,
endTime: $endTime
)
}
}
}
Variables:
{
"startTime": "2025-11-01T00:00:00Z",
"endTime": "2025-11-05T23:59:59Z"
}
Relative Time Queries
Some fields support relative time strings:
query GetLiveData {
assetProfiles {
byId(profile: "your-profile") {
# Historical: "7 days ago" to "now"
upQuery(startTime: "7 days ago", endTime: "now")
# Live data: "now"
upQuery(startTime: "now")
}
}
}
JSONObject Type
The JSONObject scalar represents arbitrary JSON data.
Querying JSON Properties
query GetAssetProperties($profileId: ID!, $assetId: ID!) {
assetProfiles {
byId(profile: $profileId) {
assetById(id: $assetId) {
id
properties
observation
}
}
}
}
Response:
{
"data": {
"assetProfiles": {
"byId": {
"assetById": {
"id": "asset-123",
"properties": {
"name": "Well Site A",
"operator": "Company XYZ",
"capacity": 1000
},
"observation": {
"phenomenonTime": "2025-11-05T14:00:00Z",
"result": {
"pressure": 150.5,
"temperature": 22.3
}
}
}
}
}
}
}
Filtering JSON Properties
query GetSelectedProperties(
$profileId: ID!,
$assetId: ID!,
$properties: [String!]
) {
assetProfiles {
byId(profile: $profileId) {
assetById(id: $assetId) {
id
properties(only: $properties)
}
}
}
}
Variables:
{
"profileId": "wells",
"assetId": "well-123",
"properties": ["name", "operator"]
}
Correlation IDs
All mutations return a correlationId for tracking and debugging.
mutation CreateAssetFilter($input: CreateAssetFilterInput!, $correlationId: String) {
createAssetFilter(input: $input, correlationId: $correlationId) {
assetFilter {
id
}
correlationId
errors {
message
}
}
}
Providing your own correlation ID:
{
"input": { ... },
"correlationId": "my-unique-id-123"
}
Use correlation IDs for:
- Request tracking across systems
- Debugging with support teams
- Audit logging
- Idempotency (some mutations)
Best Practices
-
Request only needed fields: GraphQL allows precise field selection - use it to reduce payload size
-
Use fragments for reusability: Define reusable fragments for common field sets
-
Batch related queries: Use GraphQL’s ability to query multiple resources in one request
-
Handle errors at both levels: Check for GraphQL errors AND mutation errors
-
Store cursors for pagination: Cursors remain stable across data changes
-
Use correlation IDs: Include them for better debugging and tracking
-
Respect rate limits: Implement exponential backoff for retries
-
Cache appropriately: Consider caching strategies based on data volatility
Next Steps