Errors & Retries

Response envelope format, HTTP status codes, and retry guidance for the FieldCamp API.

Every response carries a top-level success boolean. What sits next to it varies by which layer answered, so write your client to read defensively:

  • On success, the resource endpoints return data, plus meta on the list endpoints. Some endpoints also spread the payload's own fields alongside data for older consumers — read data and ignore the duplicates.
  • On failure, expect message first and fall back to error; both usually carry the same string. Authentication, scope and rate-limit failures also carry a stable code (UNAUTHORIZED, FORBIDDEN, RATE_LIMITED, INTERNAL_ERROR), while a validation message raised by an endpoint may have no code at all. A few older endpoints return error as a nested object rather than a string.

In short: branch on the HTTP status, read code when it is there, and use message ?? error for display.

Success envelope

{
  "success": true,
  "data": {
    "job": { "id": "job_abc123", "...": "..." }
  }
}

The shape of data varies by endpoint. The per-endpoint reference pages document each one.

Error envelope

{
  "success": false,
  "error": "jobNumber is required"
}

Status codes

CodeMeaningRetry?
200Success—
400Validation error — fix your payloadNo
401Authentication failed — missing, invalid, or expired API keyNo
403Authorization failed — your tenant doesn't have accessNo
404Resource not foundNo
409Conflict — e.g. duplicate jobNumberNo, handle idempotently
429Rate limitedYes, with backoff
500Server errorYes, with exponential backoff
502 503 504Gateway / upstream issuesYes, with exponential backoff
  • 5xx: retry up to 3 times with exponential backoff — 1 s, 2 s, 4 s.
  • 429: same pattern; if responses include a Retry-After header, honor it.
  • 4xx: do not retry. Fix the payload or the auth, then re-submit.
  • Network errors: retry up to 3 times with the same backoff as 5xx.

Reference implementation:

async function retryingRequest(fn, attempt = 0) {
  try {
    return await fn();
  } catch (err) {
    const status = err.response?.status;
    const retryable = !status || status >= 500 || status === 429;
    if (retryable && attempt < 3) {
      await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
      return retryingRequest(fn, attempt + 1);
    }
    throw err;
  }
}

Common errors

401 Unauthorized — Invalid or expired token

Your fc_live_ API key is missing, invalid, or expired. The v1 API accepts only fc_live_ keys — a support-issued JWT returns this error.

Check:

  • You send the key in one of these, with the full fc_live_ prefix:
    • Authorization: Bearer fc_live_... (one space, no quotes), or
    • X-Api-Key: fc_live_...
  • You are not also sending a stale X-Api-Key header. X-Api-Key is read first, so an old JWT left in that header overrides a correct Authorization key and still fails with this error. Remove it.
  • The key has no whitespace or newlines.

Create or rotate keys yourself from Settings → API Tokens.

400 Validation error — jobNumber is required

Your POST /api/jobs payload is missing a required field. Most common causes:

  • You sent the request as JSON instead of multipart/form-data. See the Jobs reference.
  • jobData contains a JSON string, but is missing one of the required top-level fields. See the NewJobData schema.
  • A field name is misspelled — field names are case-sensitive.
409 Conflict — duplicate jobNumber

A job with that jobNumber already exists in your tenant. See Idempotency for the recommended lookup-then-create pattern so retries are safe.

500 Internal server error

Something went wrong on our side. Retry with exponential backoff. If it persists beyond three retries, capture the request ID from response headers and email support@fieldcamp.ai.

ID fields

Quirk: the same object can come back with different ID field names across endpoints — id, recordId, or _id. Code defensively:

const resourceId =
  response.data.data.resource.id ??
  response.data.data.resource.recordId ??
  response.data.data.resource._id;

Prefer id when present. Normalization is on the roadmap.

On this page