Errors
Status codes the Parsel API returns and how to handle them.
The Parsel API uses standard HTTP status codes. All error responses share one body shape, so client code can branch on errors.status regardless of which endpoint produced the error.
Status codes
| Status | Meaning | When you'll see it |
|---|---|---|
200 OK | Success | The request was authenticated, the resource exists, and the body contains the data. |
400 Bad Request | Validation error | The request body or parameters failed validation. Returned by endpoints that accept input. |
401 Unauthorized | Missing or invalid token | The Authorization header is absent, malformed, or carries a token that has been revoked or expired. Applies to every authenticated endpoint. |
404 Not Found | Resource not visible | The resource ID does not exist, or it exists but belongs to a different account. The two cases are not distinguished — both return 404. |
422 Unprocessable Entity | Business-rule rejection | The request was syntactically valid but rejected by a business rule (for example, an invoice in the wrong state for the requested action). |
The Billing read endpoints (GET /billing/invoices, GET /billing/invoices/{id}, GET /billing/invoices/{id}/line_items) return only 200, 401, and 404. Endpoints that accept request bodies may additionally return 400 or 422.
Error body shape
All non-2xx responses share this shape:
{
"errors": {
"message": "The requested resource could not be found",
"status": "Not Found"
}
}Branch on errors.status for programmatic handling, or display errors.message to a human.
401 responses are produced by the auth middleware before reaching any controller. Treat any 401 as "stop and refresh credentials" — see Authentication.
Suggested handling
async function callParsel(url: string, init?: RequestInit) {
const res = await fetch(url, {
...init,
headers: {
Authorization: `Bearer ${process.env.PARSEL_TOKEN}`,
...(init?.headers ?? {}),
},
});
switch (res.status) {
case 200:
return res.json();
case 401:
throw new Error("Parsel token rejected — refresh credentials");
case 404:
return null; // not found, or not yours — same response either way
case 400:
case 422: {
const body = await res.json();
throw new Error(`Parsel rejected request: ${body.errors?.message}`);
}
default:
throw new Error(`Unexpected Parsel response: ${res.status}`);
}
}def call_parsel(url, **kwargs):
resp = requests.get(
url,
headers={"Authorization": f"Bearer {os.environ['PARSEL_TOKEN']}"},
**kwargs,
)
if resp.status_code == 200:
return resp.json()
if resp.status_code == 401:
raise RuntimeError("Parsel token rejected — refresh credentials")
if resp.status_code == 404:
return None
if resp.status_code in (400, 422):
body = resp.json()
raise RuntimeError(f"Parsel rejected request: {body.get('errors', {}).get('message')}")
resp.raise_for_status()Treat 404 on a known-valid resource ID as "you don't own this resource" rather than "the resource was deleted." Endpoints scope access to the authenticated account.