Developers
Contents
On this page

Using the API

Errors

When a request fails, the status says what kind of failure and the body says which.

The error body#

Every refusal has the same shape, whatever the endpoint:

{
  "error": {
    "code": "VALIDATION_FAILED",          // always — what to branch on
    "message": "Please check …",          // always — English, for a person
    "key": "err_please_check_…",          // sometimes — the sentence's id
    "params": { … },                      // sometimes — what fills it
    "details": { "customer.phone": "…" }, // sometimes — per field, or more about it
    "detailKeys": { … }                   // sometimes — each detail's sentence id
  }
}
  • Branch on code, together with the HTTP status. Codes are part of v1 and do not change.
  • Show message to a person if you like; do not parse it. It is written for whoever is reading your logs and may be reworded at any time.
  • key and params are the sentence's id and its values, the same ones the staff app uses to say it in eight languages. Use them if your own app translates.
  • details says more. For a 422 it names each field that is wrong by its dotted path — customer.phone, serviceIds.0 — with one sentence each.

Error codes#

Every error code
Status and codeWhat it meansHow to fix it
400 BAD_REQUEST A write (POST, PUT or PATCH) was sent with a query string. Every field of a write goes in its JSON body; only GET takes query parameters. Move the fields from the URL into the body, and send the path with nothing after it.
401 UNAUTHORIZED No key was sent, or the key is not one we know: mistyped, revoked, expired, or made by an owner who has since been removed. Check the Authorization header is Bearer and the whole key. If the key was revoked or expired, make a new one.
403 FORBIDDEN The key is good but may not do this: it lacks the scope the request needs, it is limited to some lines and tried to make a new one, or the business is the demo. Read the message — it names the missing scope. Ask the owner to edit the key's permissions in Settings → Integrations.
403 PLAN_LIMIT The business's plan does not include API access. Keys and endpoints are kept, but every call is refused until the plan includes them again. API access and webhooks come with Basic and every plan above it. The owner can change plan in Settings → Billing.
404 NOT_FOUND There is no such line, desk or visit — or there is, but not one this key may see. The two are deliberately the same answer. Check the id. If it is right, the key is limited to other lines or desks; the owner can widen it.
409 CONFLICT The request is fine but the business is not in a state to take it: a desk asked to end its shift while it is serving somebody, a visit that is not at the desk named, or a request with the same Idempotency-Key still running. Read the latest state (GET the visit or desk) and decide again. For an idempotency conflict, wait a moment and retry with the same key.
409 INVALID_TRANSITION The visit cannot go there from where it is now — usually because somebody at a desk moved it first. GET the visit, look at its status, and only send an edge the table on the Visits page allows.
410 HOST_MOVED The request went to an old host. The API is served from business.linemeup.in. Use https://business.linemeup.in/api/v1.
422 VALIDATION_FAILED A field is missing, the wrong type or out of range. details names each field by its dotted path, with a sentence about it. Fix the fields named in details. The reference lists every field's limits.
429 TOO_MANY_REQUESTS The key, the business, or this address has made too many requests this minute. Wait the number of seconds in Retry-After, then retry. See Rate limits.
500 INTERNAL Something went wrong on our side. Nothing about the request is to blame. Retry with backoff. A POST /v1/visits sent with an Idempotency-Key is safe to retry.
503 NOT_AVAILABLE_YET The request is fine, but something it needs is not switched on yet. Settings answers this when webhook signing is not configured; the v1 endpoints are not expected to. Retry later. If it lasts, tell us through Support.
503 WORKER_UNREACHABLE The background worker could not be reached, so the request could not be answered. The v1 endpoints do not depend on it and are not expected to answer this; the platform admin's pages do. Retry later. If it lasts, tell us through Support.
504 WORKER_TIMEOUT The background worker did not answer in time. The request may have been applied. The v1 endpoints are not expected to answer this; the platform admin's pages do. Refresh and check before you send it again, or a retry may do it twice.

400 BAD_REQUEST #

A write (POST, PUT or PATCH) was sent with a query string. Every field of a write goes in its JSON body; only GET takes query parameters.

Fix: Move the fields from the URL into the body, and send the path with nothing after it.

Response (400)
{
  "error": {
    "code": "BAD_REQUEST",
    "message": "This endpoint takes no query parameters. Send every field in the JSON body"
  }
}

401 UNAUTHORIZED #

No key was sent, or the key is not one we know: mistyped, revoked, expired, or made by an owner who has since been removed.

Fix: Check the Authorization header is Bearer and the whole key. If the key was revoked or expired, make a new one.

Response (401)
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid API key"
  }
}

403 FORBIDDEN #

The key is good but may not do this: it lacks the scope the request needs, it is limited to some lines and tried to make a new one, or the business is the demo.

Fix: Read the message — it names the missing scope. Ask the owner to edit the key's permissions in Settings → Integrations.

Response (403)
{
  "error": {
    "code": "FORBIDDEN",
    "message": "This API key does not have the visits:write permission"
  }
}

403 PLAN_LIMIT #

The business's plan does not include API access. Keys and endpoints are kept, but every call is refused until the plan includes them again.

Fix: API access and webhooks come with Basic and every plan above it. The owner can change plan in Settings → Billing.

Response (403)
{
  "error": {
    "code": "PLAN_LIMIT",
    "message": "API keys and webhooks are included in Basic and above.",
    "key": "err_plan_feature_integrations",
    "params": {
      "plan": "Basic"
    },
    "details": {
      "feature": "integrations",
      "plan": "free",
      "needs": "basic"
    }
  }
}

404 NOT_FOUND #

There is no such line, desk or visit — or there is, but not one this key may see. The two are deliberately the same answer.

Fix: Check the id. If it is right, the key is limited to other lines or desks; the owner can widen it.

Response (404)
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Visit not found"
  }
}

409 CONFLICT #

The request is fine but the business is not in a state to take it: a desk asked to end its shift while it is serving somebody, a visit that is not at the desk named, or a request with the same Idempotency-Key still running.

Fix: Read the latest state (GET the visit or desk) and decide again. For an idempotency conflict, wait a moment and retry with the same key.

Response (409)
{
  "error": {
    "code": "CONFLICT",
    "message": "This visit is not at that desk"
  }
}

409 INVALID_TRANSITION #

The visit cannot go there from where it is now — usually because somebody at a desk moved it first.

Fix: GET the visit, look at its status, and only send an edge the table on the Visits page allows.

Response (409)
{
  "error": {
    "code": "INVALID_TRANSITION",
    "message": "This job is already completed and cannot be marked in progress"
  }
}

410 HOST_MOVED #

The request went to an old host. The API is served from business.linemeup.in.

Fix: Use https://business.linemeup.in/api/v1.

Response (410)
{
  "error": {
    "code": "HOST_MOVED",
    "message": "This address has moved: use go.linemeup.in or business.linemeup.in"
  }
}

422 VALIDATION_FAILED #

A field is missing, the wrong type or out of range. details names each field by its dotted path, with a sentence about it.

Fix: Fix the fields named in details. The reference lists every field's limits.

Response (422)
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "Please check the highlighted fields",
    "key": "err_please_check_highlighted_fields",
    "details": {
      "customer.phone": "Enter a phone number — with its country code if it is not Indian"
    },
    "detailKeys": {
      "customer.phone": "err_field_enter_phone_number_country_code_2"
    }
  }
}

429 TOO_MANY_REQUESTS #

The key, the business, or this address has made too many requests this minute.

Fix: Wait the number of seconds in Retry-After, then retry. See Rate limits.

Response (429)
{
  "error": {
    "code": "TOO_MANY_REQUESTS",
    "message": "Too many requests for this API key. Wait and try again"
  }
}

500 INTERNAL #

Something went wrong on our side. Nothing about the request is to blame.

Fix: Retry with backoff. A POST /v1/visits sent with an Idempotency-Key is safe to retry.

Response (500)
{
  "error": {
    "code": "INTERNAL",
    "message": "Something went wrong"
  }
}

503 NOT_AVAILABLE_YET #

The request is fine, but something it needs is not switched on yet. Settings answers this when webhook signing is not configured; the v1 endpoints are not expected to.

Fix: Retry later. If it lasts, tell us through Support.

Response (503)
{
  "error": {
    "code": "NOT_AVAILABLE_YET",
    "message": "Webhooks cannot be signed on this server yet. Try again later."
  }
}

503 WORKER_UNREACHABLE #

The background worker could not be reached, so the request could not be answered. The v1 endpoints do not depend on it and are not expected to answer this; the platform admin's pages do.

Fix: Retry later. If it lasts, tell us through Support.

Response (503)
{
  "error": {
    "code": "WORKER_UNREACHABLE",
    "message": "The background worker is not reachable"
  }
}

504 WORKER_TIMEOUT #

The background worker did not answer in time. The request may have been applied. The v1 endpoints are not expected to answer this; the platform admin's pages do.

Fix: Refresh and check before you send it again, or a retry may do it twice.

Response (504)
{
  "error": {
    "code": "WORKER_TIMEOUT",
    "message": "The background worker did not answer in time; it may have applied the change, so refresh before trying again"
  }
}

One more, before a request reaches the API at all: a body that is not valid JSON, or is larger than 1 MB, is refused with a 400 or a 413 by the web server. Check the Content-Type and the body you send.

Handling errors#

  • Do not retry a 4xx unchanged, except a 429, and a 409 after reading the latest state. A 401 or 403 will answer the same until a person changes the key or the plan.
  • Retry a 5xx or a network failure with backoff — 1 second, then 2, 4, 8, up to a minute — and give up after a few minutes. Reads are always safe to retry.
  • Retrying a write. POST /v1/visits with an Idempotency-Key is safe to retry. Transitions are safe in a different way: the second completed for a visit already completed is a 409, never a second completion — read the visit and carry on. Call next is not safe to retry blindly: a retry after a timeout may call a second person. Read GET /v1/visits?status=called first.
  • Treat a 404 as final for that id with that key. It is the same answer for "never existed", "another business's" and "outside this key's lines".
One way to sort them out
async function call(method, path, body) {
  const res = await fetch(`https://business.linemeup.in/api/v1${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.LINEMEUP_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: body === undefined ? undefined : JSON.stringify(body),
  });
  if (res.ok) return res.json();

  const { error } = await res.json();
  switch (res.status) {
    case 401: // the key is wrong, revoked or expired — stop and tell a person
    case 403: // a scope or the plan — retrying will not help
      throw new Error(`${error.code}: ${error.message}`);
    case 404: // unknown to this key — forget the id, or widen the key
    case 409: // the state moved on — read it again, then decide
    case 422: // fix the fields in error.details
      return { refused: error };
    case 429: // wait Retry-After seconds, then try again
      await new Promise((r) => setTimeout(r, Number(res.headers.get("Retry-After")) * 1000));
      return call(method, path, body);
    default: // 5xx — retry with backoff; POST /v1/visits needs an Idempotency-Key for that
      throw new Error(`Line Me Up answered ${res.status}`);
  }
}