This guide covers the patterns you'll need to build a robust Rex integration — authentication, CRUD operations, pagination, error handling, and rate limit management.
Store your API key as an environment variable, never in source code:
export REX_API_KEY="rex_live_..."
export REX_URL="https://acme.rexgtm.com"
const REX_URL = process.env.REX_URL;
const REX_API_KEY = process.env.REX_API_KEY;
async function rex(path: string, options?: RequestInit) {
const res = await fetch(`${REX_URL}${path}`, {
...options,
headers: {
"X-Api-Key": REX_API_KEY!,
"Content-Type": "application/json",
...options?.headers,
},
});
if (!res.ok) {
const err = await res.json();
throw new Error(`Rex API error: ${err.error.code} — ${err.error.message}`);
}
return res.json();
}
import os, requests
REX_URL = os.environ["REX_URL"]
REX_API_KEY = os.environ["REX_API_KEY"]
HEADERS = {"X-Api-Key": REX_API_KEY, "Content-Type": "application/json"}
def rex(method, path, **kwargs):
resp = requests.request(method, f"{REX_URL}{path}", headers=HEADERS, **kwargs)
resp.raise_for_status()
return resp.json()
const contact = await rex("/contacts", {
method: "POST",
body: JSON.stringify({
email: "ada@example.com",
first_name: "Ada",
last_name: "Lovelace",
stage: "lead",
}),
});
console.log(contact.data.id); // "cont_01HQ..."
const contact = await rex("/contacts/cont_01HQ...");
console.log(contact.data.email);
await rex("/contacts/cont_01HQ...", {
method: "PATCH",
body: JSON.stringify({ stage: "prospect" }),
});
await rex("/contacts/cont_01HQ...", { method: "DELETE" });
Rex uses cursor-based pagination. Here's how to iterate through all pages:
async function fetchAll(path: string) {
const results = [];
let cursor: string | undefined;
do {
const params = new URLSearchParams({ limit: "100" });
if (cursor) params.set("cursor", cursor);
const res = await rex(`${path}?${params}`);
results.push(...res.data);
cursor = res.meta.has_more ? res.meta.cursor : undefined;
} while (cursor);
return results;
}
const allContacts = await fetchAll("/contacts");
Rex returns structured errors. Always check the error.code field for programmatic handling:
try {
await rex("/contacts", {
method: "POST",
body: JSON.stringify({ email: "invalid" }),
});
} catch (err) {
// err.message: "Rex API error: validation_failed — Invalid email address"
}
Common error codes:
| HTTP Status | Code | Meaning |
|---|---|---|
| 400 | validation_failed | Request body didn't pass validation |
| 401 | unauthorized | Missing or invalid API key |
| 403 | forbidden | Key scope doesn't allow this operation |
| 404 | not_found | Entity doesn't exist |
| 409 | conflict | Duplicate or version conflict |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | internal_error | Something broke on our end |
Implement backoff when you hit rate limits:
async function rexWithRetry(path: string, options?: RequestInit, retries = 3) {
for (let i = 0; i < retries; i++) {
const res = await fetch(`${REX_URL}${path}`, {
...options,
headers: {
"X-Api-Key": REX_API_KEY!,
"Content-Type": "application/json",
...options?.headers,
},
});
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "5", 10);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
continue;
}
if (!res.ok) {
const err = await res.json();
throw new Error(`${err.error.code}: ${err.error.message}`);
}
return res.json();
}
throw new Error("Max retries exceeded");
}