Skip to content

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.


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.

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.

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.

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.

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:

list-members.ts
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.