GraphQL API

The IOTA SDK GraphQL API provides a type-safe, flexible interface for querying and mutating data.

Endpoint

POST /api/graphql
GET /api/graphql (for introspection queries)
WebSocket /api/graphql (for subscriptions)

Authentication

Session-Based (Default)

Automatic with HTTP cookies set after login:

# 1. Login to obtain session cookie
curl -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"secret"}'

# 2. Use session cookie for GraphQL queries
curl -X POST http://localhost:8080/api/graphql \
  -H "Content-Type: application/json" \
  -b "session=abc123" \
  -d '{"query":"{users{data{id firstName}}}"}'

Bearer Token

For API clients and integrations:

curl -X POST http://localhost:8080/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-token-here" \
  -d '{"query":"{users{data{id firstName}}}"}'

Request Format

Standard Query

{
  "query": "query { users(limit: 10) { data { id firstName email } } }",
  "variables": {},
  "operationName": null
}

With Variables

{
  "query": "query GetUser($id: ID!) { user(id: $id) { id firstName email } }",
  "variables": {
    "id": "550e8400-e29b-41d4-a716-446655440000"
  }
}

Response Format

Success Response

{
  "data": {
    "users": {
      "data": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "firstName": "John",
          "lastName": "Doe",
          "email": "john@example.com"
        }
      ],
      "total": 1
    }
  }
}

Error Response

{
  "errors": [
    {
      "message": "User not found",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "extensions": {
        "code": "NOT_FOUND"
      }
    }
  ]
}

Core Types

User

type User {
  id: ID!
  firstName: String!
  lastName: String!
  email: String!
  uiLanguage: String!
  updatedAt: Time!
  createdAt: Time!
}

Session

type Session {
  token: String!
  userId: ID!
  ip: String!
  userAgent: String!
  expiresAt: Time!
  createdAt: Time!
}

PaginatedResponse

type PaginatedUsers {
  data: [User!]!
  total: Int64!
}

Query Examples

Get Current User

query {
  me {
    id
    firstName
    lastName
    email
    uiLanguage
  }
}

Response:

{
  "data": {
    "me": {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john@example.com",
      "uiLanguage": "en"
    }
  }
}

List Users

query {
  users(offset: 0, limit: 10, ascending: true) {
    data {
      id
      firstName
      lastName
      email
      createdAt
    }
    total
  }
}

Response:

{
  "data": {
    "users": {
      "data": [
        {
          "id": "550e8400-e29b-41d4-a716-446655440000",
          "firstName": "John",
          "lastName": "Doe",
          "email": "john@example.com",
          "createdAt": "2024-01-15T10:30:00Z"
        }
      ],
      "total": 1
    }
  }
}

Get User by ID

query GetUser($id: ID!) {
  user(id: $id) {
    id
    firstName
    lastName
    email
    createdAt
    updatedAt
  }
}

Variables:

{
  "id": "550e8400-e29b-41d4-a716-446655440000"
}

Mutation Examples

Authenticate

mutation {
  authenticate(email: "user@example.com", password: "secret") {
    token
    userId
    expiresAt
  }
}

Response:

{
  "data": {
    "authenticate": {
      "token": "abc123token",
      "userId": "550e8400-e29b-41d4-a716-446655440000",
      "expiresAt": "2024-01-16T10:30:00Z"
    }
  }
}

Google Authentication

mutation {
  googleAuthenticate
}

This initiates OAuth flow. Redirect to the returned URL, then use session cookies.

Delete Session

mutation {
  deleteSession(token: "abc123token")
}

Filtering

Pagination Parameters

users(
  offset: 0,        # Starting position
  limit: 10,        # Items per page
  sortBy: ["firstName"],  # Sort fields
  ascending: true   # Sort direction
) {
  data { id firstName }
  total
}

Filter Operators

# Not yet fully implemented, but planned for:
users(filters: [
  {field: "status", operator: "eq", value: "active"},
  {field: "createdAt", operator: "gte", value: "2024-01-01"}
]) {
  data { id firstName }
  total
}

Subscriptions

Subscribe to real-time events:

subscription {
  sessionDeleted
}

WebSocket URL:

wss://localhost:8080/api/graphql

Connection:

{
  "type": "connection_init",
  "payload": {
    "Authorization": "Bearer token"
  }
}

Common Patterns

Paginate Through All Results

query GetAllUsers {
  page1: users(offset: 0, limit: 100) {
    data { id firstName }
    total
  }
}

Then fetch subsequent pages with offset += limit.

Search Users

# Once full-text search is implemented
query {
  users(filters: [
    {field: "email", operator: "like", value: "example"}
  ]) {
    data { id firstName email }
    total
  }
}

Sort Results

query {
  users(
    limit: 20,
    sortBy: ["firstName", "lastName"],
    ascending: true
  ) {
    data { id firstName lastName }
    total
  }
}

Error Handling

Authentication Error

{
  "errors": [
    {
      "message": "Unauthorized",
      "extensions": {
        "code": "UNAUTHENTICATED"
      }
    }
  ]
}

Validation Error

{
  "errors": [
    {
      "message": "validation error: email is required",
      "extensions": {
        "code": "VALIDATION_ERROR"
      }
    }
  ]
}

Permission Error

{
  "errors": [
    {
      "message": "Forbidden",
      "extensions": {
        "code": "FORBIDDEN"
      }
    }
  ]
}

Rate Limiting

GraphQL requests are subject to rate limiting:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 998
X-RateLimit-Reset: 1640000000

HTTP Status Codes

Status Meaning
200 Request processed (check errors in response)
400 Invalid request format
401 Authentication required
403 Forbidden
429 Rate limited
500 Server error
503 Service unavailable

Introspection

Query the schema:

{
  __schema {
    types {
      name
      kind
      fields {
        name
        type { name kind }
      }
    }
  }
}

Caching

Cache Headers

Responses include cache headers:

Cache-Control: max-age=300, public
ETag: "abc123"

Client-Side Caching

Implement in client libraries using response __typename:

// Apollo Client example
const cache = new InMemoryCache({
  typePolicies: {
    User: {
      keyFields: ["id"]
    }
  }
});

Batch Queries

Send multiple queries in one request:

[
  {
    "query": "query { users(limit: 10) { data { id } } }"
  },
  {
    "query": "query { me { id firstName } }"
  }
]

Response:

[
  {
    "data": {
      "users": { "data": [...], "total": ... }
    }
  },
  {
    "data": {
      "me": { "id": "...", "firstName": "..." }
    }
  }
]

Testing with cURL

Simple Query

curl -X POST http://localhost:8080/api/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "{ users(limit: 10) { data { id firstName } total } }"
  }'

Query with Variables

curl -X POST http://localhost:8080/api/graphql \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query GetUser($id: ID!) { user(id: $id) { id firstName } }",
    "variables": {
      "id": "550e8400-e29b-41d4-a716-446655440000"
    }
  }'

With Authentication

curl -X POST http://localhost:8080/api/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{"query":"{ me { id firstName } }"}'

GraphQL Playground

Access interactive GraphQL IDE in development:

http://localhost:8080/graphql

Features:

  • Query editor with syntax highlighting
  • Schema documentation
  • Query history
  • Variables panel
  • Response viewer

Best Practices

  1. Always paginate: Use limit/offset for large datasets
    users(offset: 0, limit: 100) { data { id } total }
    
  2. Request only needed fields: Minimize data transfer
    # Good
    users { data { id firstName } }
    
    # Bad
    users { data { ... all fields ... } }
    
  3. Use fragments for reusable selections:
    fragment UserFields on User {
      id
      firstName
      lastName
      email
    }
    
    query {
      users { data { ...UserFields } }
    }
    
  4. Handle errors gracefully:
    if (response.errors) {
      console.error("GraphQL error:", response.errors[0].message);
    }
    
  5. Implement exponential backoff for retries:
    let delay = 100;
    while (retries < maxRetries) {
      try {
        return await query();
      } catch (e) {
        await sleep(delay);
        delay *= 2;
      }
    }
    

Limitations

  • Query timeout: 30 seconds
  • Maximum query depth: 10 levels
  • Maximum query complexity: 1000 points
  • File upload max size: 100MB

Modules Schema

Each module registers its own GraphQL types and queries/mutations. See module documentation for specific schemas.

Available Modules

  • Core: Users, Roles, Groups, Sessions
  • Finance: Payments, Expenses, Transactions
  • CRM: Clients, Chats, Messages
  • Warehouse: Products, Inventory, Orders
  • Projects: Projects, Stages
  • HRM: Employees

For more information, see the API Reference or IOTA SDK Documentation.


Back to top

IOTA SDK - Multi-tenant Business Management Platform