Skip to main content

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.

Pagination

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
}

Forward Pagination

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:
{
  "first": 20
}
Next page:
{
  "first": 20,
  "after": "cursor-from-endCursor"
}

Backward Pagination

query GetAssets($last: Int!, $before: String) {
  assetProfiles {
    byId(profile: "your-profile") {
      assets(last: $last, before: $before) {
        edges {
          node {
            id
            displayName
          }
        }
        pageInfo {
          hasPreviousPage
          startCursor
        }
      }
    }
  }
}

Pagination Best Practices

  1. Use reasonable page sizes: 10-100 items per page depending on data size
  2. Store cursors, not offsets: Cursors remain valid even when data changes
  3. Check hasNextPage: Don’t assume there’s always a next page
  4. 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"
          }
        }
      }
    }
  }
}

GeoJSON Input

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
}

Querying Audit Information

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.

DateTime Format

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

  1. Request only needed fields: GraphQL allows precise field selection - use it to reduce payload size
  2. Use fragments for reusability: Define reusable fragments for common field sets
  3. Batch related queries: Use GraphQL’s ability to query multiple resources in one request
  4. Handle errors at both levels: Check for GraphQL errors AND mutation errors
  5. Store cursors for pagination: Cursors remain stable across data changes
  6. Use correlation IDs: Include them for better debugging and tracking
  7. Respect rate limits: Implement exponential backoff for retries
  8. Cache appropriately: Consider caching strategies based on data volatility

Next Steps