> ## 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

> Essential patterns for working with the SensorUp GraphQL API - pagination, filtering, error handling, and GeoJSON.

# 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

```graphql theme={null}
type Connection {
  edges: [Edge]
  pageInfo: PageInfo!
}

type Edge {
  node: NodeType
  cursor: String
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
```

### Forward Pagination

```graphql theme={null}
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:**

```json theme={null}
{
  "first": 20
}
```

**Next page:**

```json theme={null}
{
  "first": 20,
  "after": "cursor-from-endCursor"
}
```

### Backward Pagination

```graphql theme={null}
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

```graphql theme={null}
query GetRecentAssets($filter: AssetFilter!) {
  assetProfiles {
    byId(profile: "your-profile") {
      assets(first: 20, filter: $filter) {
        edges {
          node {
            id
            displayName
            modifiedAt
          }
        }
      }
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "filter": {
    "modifiedAfter": "2025-11-01T00:00:00Z"
  }
}
```

### Property-Based Filtering

Some queries allow filtering by specific properties:

```graphql theme={null}
query FilteredObservations(
  $startTime: DateTime,
  $endTime: DateTime,
  $properties: [String!]
) {
  assetProfiles {
    byId(profile: "your-profile") {
      upQueryObservations(
        startTime: $startTime,
        endTime: $endTime,
        properties: $properties
      )
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```graphql theme={null}
interface MutationError {
  message: String!
  type: String!
}
```

**Example mutation with errors:**

```graphql theme={null}
mutation CreateAssetFilter($input: CreateAssetFilterInput!) {
  createAssetFilter(input: $input) {
    assetFilter {
      id
      displayName
    }
    correlationId
    errors {
      message
      type
    }
  }
}
```

**Response with validation error:**

```json theme={null}
{
  "data": {
    "createAssetFilter": {
      "assetFilter": null,
      "correlationId": "abc-123",
      "errors": [
        {
          "message": "Display name is required",
          "type": "VALIDATION_ERROR"
        }
      ]
    }
  }
}
```

### Error Handling Pattern

```javascript theme={null}
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

```graphql theme={null}
enum GeoJSONType {
  Point
  MultiPoint
  LineString
  MultiLineString
  Polygon
  MultiPolygon
  GeometryCollection
  Feature
  FeatureCollection
}
```

### Querying Geometry

```graphql theme={null}
query GetAssetGeometry($profileId: ID!, $assetId: ID!) {
  assetProfiles {
    byId(profile: $profileId) {
      assetById(id: $assetId) {
        id
        displayName
        geometry {
          type
          coordinates
          bbox
        }
        geoJSONFeature
      }
    }
  }
}
```

**Response:**

```json theme={null}
{
  "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

```graphql theme={null}
input GeoJSONGeometryInput {
  type: GeoJSONType!
  coordinates: GeoJSONCoordinates
  bbox: [Float]
}
```

**Example usage in mutations:**

```json theme={null}
{
  "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

```graphql theme={null}
type Audit {
  createdTime: DateTime!
  updatedTime: DateTime!
  createdBy: UserReference
  updatedBy: UserReference
}
```

### Querying Audit Information

```graphql theme={null}
query GetIssueAudit($issueId: ID!) {
  issues {
    byId(id: $issueId) {
      id
      displayName
      audit {
        createdTime
        updatedTime
        createdBy {
          userId
        }
        updatedBy {
          userId
        }
      }
    }
  }
}
```

**Response:**

```json theme={null}
{
  "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

```graphql theme={null}
type UserReference @key(fields: "userId") {
  userId: ID!
}
```

**Query user references:**

```graphql theme={null}
query GetUserDetails {
  issues {
    byId(id: "issue-123") {
      audit {
        createdBy {
          userId
          # Additional user fields resolved by su-user subgraph
        }
      }
    }
  }
}
```

### Asset References

```graphql theme={null}
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

```graphql theme={null}
query GetRecentData($startTime: DateTime!, $endTime: DateTime!) {
  assetProfiles {
    byId(profile: "your-profile") {
      upQueryObservations(
        startTime: $startTime,
        endTime: $endTime
      )
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "startTime": "2025-11-01T00:00:00Z",
  "endTime": "2025-11-05T23:59:59Z"
}
```

### Relative Time Queries

Some fields support relative time strings:

```graphql theme={null}
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

```graphql theme={null}
query GetAssetProperties($profileId: ID!, $assetId: ID!) {
  assetProfiles {
    byId(profile: $profileId) {
      assetById(id: $assetId) {
        id
        properties
        observation
      }
    }
  }
}
```

**Response:**

```json theme={null}
{
  "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

```graphql theme={null}
query GetSelectedProperties(
  $profileId: ID!,
  $assetId: ID!,
  $properties: [String!]
) {
  assetProfiles {
    byId(profile: $profileId) {
      assetById(id: $assetId) {
        id
        properties(only: $properties)
      }
    }
  }
}
```

**Variables:**

```json theme={null}
{
  "profileId": "wells",
  "assetId": "well-123",
  "properties": ["name", "operator"]
}
```

## Correlation IDs

All mutations return a `correlationId` for tracking and debugging.

```graphql theme={null}
mutation CreateAssetFilter($input: CreateAssetFilterInput!, $correlationId: String) {
  createAssetFilter(input: $input, correlationId: $correlationId) {
    assetFilter {
      id
    }
    correlationId
    errors {
      message
    }
  }
}
```

**Providing your own correlation ID:**

```json theme={null}
{
  "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

* **[Domain Guides](./guides/cam-assets)** - Explore specific use cases
* **[Subgraph Reference](./subgraphs/assets)** - Detailed schema documentation
* **[Federation Concepts](./federation)** - Understanding entity composition
* **[Performance](./performance)** - Optimization and best practices
