Developer Reference · GraphQL · v1

GraphQL schema.

A flexible query language for documents, credentials, seals, professional directory lookups, and more — with file uploads and real-time subscriptions.

v1 (locked) Subscriptions File uploads Cursor pagination

Overview

The EngineeringID GraphQL API gives you a single endpoint for everything in the platform — documents, credentials, seals, the professional directory, and more. Query exactly the fields you need, mutate state with strongly typed inputs, subscribe to live updates over WebSockets.

Queries
39
Mutations
47
Subscriptions
8
Types
120+
v1 is locked. The schema is wire-compatible across all v1 releases — fields are added, never removed or retyped. New mutations and types are additive. Native iOS, macOS, and Android clients can rely on stable contracts.

Endpoint

POST /graphql
Base URL
https://api.engineeringid.com
Content-Type
application/json
Subscriptions
wss://api.engineeringid.com/socket

Smoke test

bash
curl https://api.engineeringid.com/graphql \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { me { id email } }"
  }'

Authentication

Every request must carry a bearer token in the Authorization header — either a session cookie token (browser) or an API key (server-to-server).

http
Authorization: Bearer cred_live_a1b2c3d4...
  • API keys are SHA-256 hashed before storage. The raw value is shown to you exactly once at creation time.
  • Per-key scopes control which operations a key can perform — independent of user identity.
  • Per-key rate limits isolate noisy integrations from each other.

Operations

The schema covers the full surface of the platform. Below are the most common operations developers reach for first. For an exhaustive list, query the introspection endpoint or open the schema browser.

Queries

Get the current user

graphql
query Me {
  me {
    id
    email
    name
    organizations {
      id
      name
      plan
    }
  }
}

List documents (paginated)

graphql
query Documents($first: Int!, $after: String) {
  documentsConnection(first: $first, after: $after) {
    edges {
      cursor
      node {
        id
        filename
        contentType
        size
        insertedAt
        sealedAt
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}

Variables

json
{
  "first": 25,
  "after": null
}

Verify a seal by code

Public — does not require authentication. Useful for embedding verification widgets in third-party sites.

graphql
query VerifySeal($code: String!) {
  verifySeal(code: $code) {
    valid
    verificationCode
    sealedAt
    document {
      filename
      version
      documentHash
    }
    credential {
      licenseNumber
      stateCode
      profession
      verificationMethod
    }
    sealedBy {
      name
    }
  }
}

Mutations

All mutations return a payload shaped { result, errors }. Validation errors are reported in errors[] with a field, message, and machine-readable code — top-level errors are reserved for transport / auth failures.

Seal a document

graphql
mutation CreateSeal($input: CreateSealInput!) {
  createSeal(input: $input) {
    seal {
      id
      verificationCode
      sealedAt
      document {
        id
        version
      }
    }
    errors {
      field
      message
      code
    }
  }
}

Variables

json
{
  "input": {
    "documentId": "01HXYZ-doc-uuid",
    "credentialId": "01HXYZ-cred-uuid"
  }
}

File uploads

File uploads use the GraphQL multipart request spec. Use any compliant client — apollo-upload-client, graphql-request, etc.

graphql
// Multipart request — see graphql-multipart-request-spec
// Uses a custom GraphQLUpload plug that extracts uploads into context.

mutation UploadDocument($file: Upload!, $folderId: ID) {
  createDocument(file: $file, folderId: $folderId) {
    document {
      id
      filename
      size
    }
    errors {
      field
      message
    }
  }
}
Behind the scenes. Uploads are extracted into Absinthe context (not variables) by a custom GraphQLUpload plug — this avoids Blueprint.Input.parse crashes that older approaches hit.

Subscriptions

Subscriptions stream over Phoenix Channels at wss://api.engineeringid.com/socket. Authenticate the same way as queries — pass the bearer token in the connect payload.

graphql
subscription BatchSealJobUpdates($jobId: ID!) {
  batchSealJobUpdated(jobId: $jobId) {
    id
    status
    total
    processed
    succeeded
    failed
  }
}

Pagination

All list queries follow the Relay connection spec: edges { cursor node } + pageInfo { hasNextPage endCursor }. Use first / after for forward paging, last/before for backward.

Default page size is 25 and the maximum is 100. Requests above the cap are clamped, not rejected — check pageInfo for the actual page size you got back.

Errors

GraphQL responses always return HTTP 200 when the request was understood. Application errors live in the response body, not the status code.

json
{
  "errors": [
    {
      "message": "Document not found",
      "extensions": {
        "code": "NOT_FOUND",
        "field": "documentId"
      },
      "path": ["createSeal"]
    }
  ],
  "data": {
    "createSeal": null
  }
}

Error codes

Code Meaning What to do
UNAUTHENTICATED Missing or invalid auth token Provide a valid API key or session token via Authorization header
FORBIDDEN Authenticated but not allowed to perform this action Check the user's permissions in the target organization
NOT_FOUND Resource doesn't exist or the user can't see it Verify IDs and the active organization context
VALIDATION_FAILED Input failed schema validation Inspect the per-field errors[] for details
RATE_LIMITED Too many requests within the rate window Back off exponentially and retry
INTERNAL_ERROR Unexpected server-side failure Retry; if it persists, contact developer support

Rate limits

Per API key
600 / min
Sustained throughput
Burst
100 / sec
Short-window cap
Query depth
10
Maximum nesting
When throttled, the response includes RATE_LIMITED in the error extensions. Back off exponentially and respect the retry-after header.

Need a deeper schema explorer?

The full schema (every type, field, and argument) is available via introspection or our hosted GraphiQL playground. Reach out and we'll point you to the right environment.

Contact us

Developer Support

Get in touch

Tell us what you're building. We answer integration, schema, auth, rate-limit, SDK, and pricing questions — usually within a business day.

No spam, unsubscribe anytime. We respect your privacy.