Conventions
Three things apply to every request: how a failed call is reported, what to do when traffic is throttled, and how lists are paged. They are on one page because an integration usually needs all three when something breaks.
When a call fails
Section titled “When a call fails”A GraphQL response can carry errors with HTTP status 200. Check the errors array on every
response, not only the status code. A response can also contain both: data for the fields that
resolved, and errors for the ones that did not.
{ "data": null, "errors": [ { "path": ["memberEvents"], "errorType": "RESULT_TOO_LARGE", "message": "Query result exceeds the API response limit.", "data": { "code": "RESULT_TOO_LARGE", "query": "memberEvents", "itemCount": 842, "maxItems": 610, "remedy": "Narrow the between date range or filter with memberIdIn until pagination is available." } } ]}Branch on errors[].data.code, not on message. Messages are written for people and can change.
Codes do not.
Rejected before Temprix sees the request
Section titled “Rejected before Temprix sees the request”| What you see | Cause | What to do |
|---|---|---|
HTTP 401, UnauthorizedException |
The key is missing, malformed, revoked, or sent to the wrong region. | Check the header and the endpoint. If the key was revoked, create a new one in the app. Do not retry. |
errorType: "Unauthorized" on one field |
The operation is app-only and cannot be called with a key. | Do it in the app. See the list on API overview. |
Revocation takes effect within 30 seconds. A key revoked a moment ago can still succeed briefly.
Common error codes
Section titled “Common error codes”These codes appear often across integrations. Other operations can return additional codes; match
on errors[].data.code and read data.remedy when it is present.
| Code | Where | Cause | What to do |
|---|---|---|---|
RESULT_TOO_LARGE |
memberEvents, groupEvents |
The result would exceed the response size limit, usually a wide between range in a large workspace. |
Narrow between, or filter with memberIdIn, and retry with the smaller request. data also carries counts and limits for debugging. |
API_KEY_SCOPE_DENIED |
Any mutation without write scope on the key | A read-only key called a mutation. errorType is Unauthorized; match on data.code. |
Create a key with write access, or use a signed-in session. See API keys. |
MEMBER_RESTRICTED |
Creating, changing or deleting member events | The member is restricted because the workspace has more active members than its plan allows. | An administrator resolves it in the app, by changing plan or deactivating members. Retrying does not help until then. |
Permissions. A key acts as the member who created it. If that member could not do something in the app, for example change another team’s settings without being its admin, the key cannot do it either. The fix is on the member, not the key.
Invalid arguments. Values outside what a field accepts, such as a coverageOutlook range longer
than 180 days, are rejected with a message naming the argument. Correct the request; retrying it
unchanged returns the same error.
Rate limits
Section titled “Rate limits”Currently, most endpoints do not have endpoint-specific rate limits. That may change as the service evolves.
Requests are still subject to platform-wide throttling. If throttled, the response is HTTP 429 or a throttling error. Back off exponentially before retrying: wait about a second, then double the wait on each further failure, with some random jitter, and stop after a few attempts. Do not retry in a tight loop.
Two habits reduce load: fetch related data in one request by asking for several root fields at once, rather than one request per record; and sync on a schedule rather than polling every few seconds.
Pagination
Section titled “Pagination”Every list query returns the same envelope:
members(limit: Int, nextToken: String): MembersList!
type MembersList { items: [Member!]! nextToken: String}To read a whole list, keep requesting with the nextToken from the previous page until it comes
back null.
limit is the most items you want on a page. You can receive fewer. Leave it out to use the
default.
nextToken is opaque. Do not parse it, build it, or store it for later. A token is valid for the
loop that produced it and can stop working after a Temprix release. If one is rejected, restart the
loop from the first page.
Lists come back in no guaranteed order, except the audit log (auditEvents) and alert send history
(notificationDispatches), which are newest first.
A complete loop in TypeScript, using fetch and no client library:
const ENDPOINT = "https://graphql.euc1.temprix.app/graphql";const TOKEN = process.env.TEMPRIX_API_KEY!;
type Member = { id: string; displayName: string };type MembersPage = { members: { items: Member[]; nextToken: string | null };};
const QUERY = ` query Members($limit: Int, $nextToken: String) { members(limit: $limit, nextToken: $nextToken) { items { id displayName } nextToken } }`;
async function request<T>( query: string, variables: Record<string, unknown>,): Promise<T> { const res = await fetch(ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json", Authorization: TOKEN }, body: JSON.stringify({ query, variables }), }); if (!res.ok) throw new Error(`HTTP ${res.status}`);
const body = await res.json(); if (body.errors?.length) throw new Error(JSON.stringify(body.errors)); return body.data as T;}
export async function listAllMembers(): Promise<Member[]> { const members: Member[] = []; let nextToken: string | null = null;
do { const page: MembersPage = await request<MembersPage>(QUERY, { limit: 100, nextToken, }); members.push(...page.members.items); nextToken = page.members.nextToken; } while (nextToken !== null);
return members;}The same loop works for every list. Only the query and the field name change.