Renidlyrenidly
Core Concepts

Response Envelope

3 min read

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 */ }
}
FieldTypeDescription
successbooleantrue if the call succeeded, false on any failure.
statusCodenumberNumeric status returned alongside message — useful for logs.
messagestringShort human-readable summary. Safe to surface in internal tools, not user-facing.
errorsobject | nullnull on success; structured per-field map on validation failures.
dataobject | nullEndpoint-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 errorsWhen you see itWhere to read the reason
nullGeneral / 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.

IdentifierSubjectDescription
entityIdPerson, activityStable opaque identifier. Safe to persist.
handlePersonPublic URL slug — the human-readable identifier.
idOrganizationStable numeric identifier.
slugOrganizationPublic URL slug.
opportunityEntityIdCareer opportunityOpaque identifier for a single posted role.