Response Envelope
Every Renidly endpoint — successful or not — returns the same five-field JSON envelope. Knowing it cold makes every other endpoint feel familiar from the first call.
The shape
{
"success": true,
"statusCode": 200,
"message": "Data retrieved successfully",
"errors": null,
"data": { /* endpoint-specific payload */ }
}| Field | Type | Description |
|---|---|---|
| success | boolean | true if the call succeeded, false on any failure. |
| statusCode | number | Numeric status returned alongside message — useful for logs. |
| message | string | Short human-readable summary. Safe to surface in internal tools, not user-facing. |
| errors | object | null | null on success; structured per-field map on validation failures. |
| data | object | null | Endpoint-specific payload. null when success is false. |
On failure
When the call fails, the envelope keeps the same shape. data becomes null, message carries the human-readable reason, and errors is either null (general / business failure) or a per-field map (input validation failure).
General failure — errors is null
When the failure isn't tied to a specific input field — a missing required parameter, a business rule violation, an auth problem — Renidly puts the reason in message and leaves errors as null:
{
"success": false,
"statusCode": 200,
"message": "either 'entityId' or 'handle' parameter is required",
"errors": null,
"data": null
}Validation failure — errors is a field map
When one or more input fields fail validation, messageis a fixed “validation error” line and errors is a { fieldName: reason } map naming every offending field:
{
"success": false,
"statusCode": 200,
"message": "validation error; Please recheck your inputs",
"errors": {
"keyword": "keyword is required"
},
"data": null
}| Shape of errors | When you see it | Where to read the reason |
|---|---|---|
null | General / business / auth failure. | body.message |
{ field: reason, ... } | Per-field input validation failure. | body.errors[field] |
One helper, every endpoint
Because the envelope is consistent, you can wrap fetch once and never repeat envelope-handling. Here is the pattern we use internally:
// One helper, every endpoint. Branch on body.success — never on HTTP status.
type Envelope<T> = {
success: boolean;
statusCode: number;
message: string;
errors: Record<string, string> | null; // null for general failures, map for validation
data: T | null;
};
export async function renidly<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`https://renidly.com${path}`, {
...init,
headers: {
"X-renidly-apikey": process.env.RENIDLY_API_KEY!,
...(init.headers || {}),
},
});
const body = (await res.json()) as Envelope<T>;
if (!body.success || body.data === null) {
// Validation failures put offending fields in body.errors. Everything else
// (auth, business rules, missing required params) lives in body.message.
const detail = body.errors
? Object.entries(body.errors).map(([k, v]) => `${k}: ${v}`).join("; ")
: body.message;
throw new RenidlyError(body.message, detail, body.errors);
}
return body.data;
}Common identifiers in data
Most payloads share a small set of identifier fields. They're consistent across endpoints, so the same parsing code works everywhere.
| Identifier | Subject | Description |
|---|---|---|
entityId | Person, activity | Stable opaque identifier. Safe to persist. |
handle | Person | Public URL slug — the human-readable identifier. |
id | Organization | Stable numeric identifier. |
slug | Organization | Public URL slug. |
opportunityEntityId | Career opportunity | Opaque identifier for a single posted role. |