Rate Limiting

The Worksome GraphQL API enforces rate limits to ensure fair usage and maintain platform stability for all consumers. Every authenticated request counts toward your rate limit quota.

Rate Limit Tiers

Rate limits are applied per API token. Each token is allocated a fixed number of requests per minute within a rolling window.

Tier Requests per Minute
Default 60

Warning

The exact rate limit for your integration may vary depending on your plan and agreement. Contact your Worksome account representative if you need a higher throughput allocation.

Unauthenticated requests (where permitted) are rate-limited by IP address at a lower threshold.

Warning

The federation gateway in front of the platform does not currently propagate X-RateLimit-* response headers. Build your rate-limit awareness around your own request count and the 429/error response described below, not around per-response headers.

Query Complexity

Because GraphQL allows clients to request arbitrarily nested data in a single call, the API also evaluates query complexity to protect against expensive operations.

Each field in a query has an associated cost. Deeply nested queries, or queries that request large collections of related objects, consume more complexity budget than basic field lookups.

How to keep complexity low:

  • Request only the fields you need — avoid selecting every field on a type.
  • Limit the depth of nested relationships (e.g., avoid chaining company → workers → contracts → invoices in a single query).
  • Use pagination with reasonable page sizes (first: 25 rather than first: 100).
# Good — focused query with limited depth
query {
  hires(first: 25) {
    data {
      id
      worker {
        id
        name
        email
      }
    }
    paginatorInfo {
      hasMorePages
    }
  }
}
# Avoid — deeply nested query with excessive data
query {
  hires(first: 100) {
    data {
      id
      worker {
        id
        name
        hires(first: 100) {
          data {
            latestContract {
              id
              files {
                id
                name
                url
              }
            }
          }
        }
      }
    }
  }
}

Warning

If a query exceeds the maximum allowed complexity, the API will reject it with an error before execution. Simplify your query or split it into multiple smaller requests.

Handling Rate Limit Errors

When you exceed the rate limit, the platform returns HTTP 429 Too Many Requests with a Retry-After header. The federation gateway forwards this as a DOWNSTREAM_SERVICE_ERROR in the GraphQL errors array (HTTP 200 at the gateway, even though the underlying response was 429):

{
  "errors": [
    {
      "message": "Too Many Requests",
      "extensions": {
        "serviceName": "platform",
        "code": "DOWNSTREAM_SERVICE_ERROR"
      }
    }
  ]
}

Treat repeated DOWNSTREAM_SERVICE_ERROR responses with a “Too Many Requests” message as the rate-limit signal and back off. See Error Handling for the full error envelope.

Exponential Backoff

The recommended strategy is to implement exponential backoff — wait progressively longer between retries to avoid flooding the API:

async function fetchWithRetry(query, variables, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch('https://api.worksome.com/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer YOUR_TOKEN',
      },
      body: JSON.stringify({ query, variables }),
    });

    const result = await response.json();

    const isThrottled = result.errors?.some(
      (e) =>
        e.extensions?.code === 'DOWNSTREAM_SERVICE_ERROR' &&
        /too many requests/i.test(e.message),
    );

    if (isThrottled) {
      const backoff = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
      const jitter = Math.random() * 1000;
      await new Promise((resolve) => setTimeout(resolve, backoff + jitter));
      continue;
    }

    return result;
  }

  throw new Error('Rate limit exceeded after retries');
}

Warning

Retrying immediately or in a tight loop may result in extended throttling. Use exponential backoff with jitter.

Best Practices

  • Batch related data into fewer queries. GraphQL lets you request multiple resources in a single call — use that to your advantage instead of making many small requests.
  • Cache responses where the data does not change frequently. Avoid polling the same query repeatedly when a webhook or less frequent interval would suffice.
  • Paginate wisely. Use Lighthouse pagination (data { ... } plus paginatorInfo) and stop iterating once paginatorInfo.hasMorePages is false. See Pagination & Filtering.
  • Request only needed fields. Smaller responses are faster to process and reduce complexity costs.
  • Spread requests over time. If you have a batch job, distribute requests evenly across the rate limit window rather than bursting them all at once.

Monitoring Your Usage

Because the gateway does not currently expose rate-limit headers, track your usage on the client side. The pattern below counts successful calls per minute for a single token and slows down before hitting the documented limit:

const PER_MINUTE_LIMIT = 60;
const window = []; // timestamps of recent successful calls

function recordCall() {
  const now = Date.now();
  while (window.length && now - window[0] > 60_000) window.shift();
  window.push(now);
}

async function rateLimitedFetch(query, variables) {
  if (window.length >= PER_MINUTE_LIMIT - 5) {
    const wait = 60_000 - (Date.now() - window[0]);
    await new Promise((resolve) => setTimeout(resolve, wait));
  }

  const response = await fetch('https://api.worksome.com/graphql', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_TOKEN',
    },
    body: JSON.stringify({ query, variables }),
  });

  const result = await response.json();
  recordCall();
  return result;
}

Tip

Build rate limit awareness into your integration from the start. Proactively slowing down before you hit the limit is far better than reacting to throttle errors.