Concepts
Errors
Errors use standard HTTP status codes and always have the same JSON shape.
Error format#
{
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "This API key does not have the scope posts:write"
}
}
codeis stable: use it in your code.messageis for humans and may change.- Some errors add extra fields next to
codeandmessage(for example the invalid field of a validation error).
Error codes#
| Status | Code | Meaning · what to do |
|---|---|---|
| 400 | VALIDATION_ERROR | A parameter or body field is missing or invalid. Fix the request; do not retry as is. |
| 400 | INVALID_JSON | The body is not valid JSON. Send Content-Type: application/json and a valid body. |
| 401 | INVALID_API_KEY | Missing, malformed or revoked key. Check the Authorization header. |
| 403 | INSUFFICIENT_SCOPE | The key lacks the scope this endpoint needs. Create a key with the right scopes. |
| 403 | DEVELOPER_ACCESS_REVOKED | Developer access for this account was suspended or revoked. Contact developers@linework.app. |
| 403 | PROFILE_INCOMPLETE | Add a profile photo and a cover to the account before using write endpoints. |
| 403 | CHAT_NOT_ALLOWED | The anti-spam rule does not allow this message: the person has not written to you and does not follow you. |
| 403 | CHAT_BLOCKED | One of you blocked the other. Do not retry. |
| 403 | STORE_BLOCKED | This store blocked you. |
| 403 | USER_UNAVAILABLE | The user is not available (blocked, private or suspended). |
| 403 | FORBIDDEN | The action is not allowed for this account (for example editing somebody else's post). |
| 404 | NOT_FOUND | The resource does not exist or you cannot see it. |
| 409 | CONFLICT | The request conflicts with the current state (for example shipping an order twice). |
| 413 | PAYLOAD_TOO_LARGE | The body or the uploaded file is too large. See the media limits in Post with media. |
| 429 | RATE_LIMITED | Too many requests, or a daily quota is used up. Wait for Retry-After. See Rate limits. |
| 500 | INTERNAL_ERROR | Something went wrong on our side. Retry later with backoff. |
New codes may be added over time: treat an unknown code with the generic meaning of its HTTP status.
Retry or not?#
| Status | Retry? |
|---|---|
| 400, 401, 403, 404, 409, 413 | No. Fix the request, the key or the account first. |
| 429 | Yes, after Retry-After. |
| 500, 502, 503, 504 | Yes, with exponential backoff. For writes that are not idempotent (for example POST /posts), check first that the action did not already happen. |
Example handler#
const res = await fetch(url, options);
if (!res.ok) {
const { error } = await res.json().catch(() => ({ error: { code: "UNKNOWN" } }));
switch (error.code) {
case "RATE_LIMITED": /* wait Retry-After, then retry */ break;
case "INVALID_API_KEY":
case "DEVELOPER_ACCESS_REVOKED": /* stop the bot and alert someone */ break;
case "CHAT_NOT_ALLOWED": /* skip this user */ break;
default: throw new Error(`${res.status} ${error.code}: ${error.message}`);
}
}
r = s.post(url, json=body, timeout=30) # s = requests.Session() with the Authorization header
if not r.ok:
err = r.json().get("error", {})
code = err.get("code")
if code == "RATE_LIMITED":
... # wait Retry-After, then retry
elif code in ("INVALID_API_KEY", "DEVELOPER_ACCESS_REVOKED"):
... # stop the bot and alert someone
elif code == "CHAT_NOT_ALLOWED":
... # skip this user
else:
raise RuntimeError(f"{r.status_code} {code}: {err.get('message')}")