Errors
HTTP status codes the JobsPipe API returns, and how to handle them.
Errors return a JSON body with an error message:
{ "error": "Rate limit exceeded" }Status codes
| Status | error message | Cause | What to do |
|---|---|---|---|
400 | Invalid search filters: ... | A filter name we do not have, or a value of the wrong type. | Read the message; it names the field and, for an unknown name, suggests the real one. |
401 | Unauthorized / Invalid API key | Missing, malformed, or revoked key. | Check the Authorization header and key validity. |
402 | Monthly request quota exceeded | You are out of credits: this month's allowance, or on Free the one-time 1,000 credits. | Wait for the monthly reset, buy credits, or upgrade. On Free, pick a package. |
403 | Blocked: ... | The account is not allowed to use the API. | See 403 Forbidden. Retrying the same request will not help. |
404 | company_not_found / Not Found | No record for the key, or no such route. | On a company lookup or a company tech stack, company_not_found lists candidates; pick one. Costs nothing. |
429 | Rate limit exceeded | Too many requests in one second for your plan. | Back off and retry (see below). |
502 | varies | The request could not be completed. | Retry. |
504 | varies | The search timed out. | Retry; if it persists, narrow your filters. |
500 | Internal Server Error | Unexpected server error. | Retry; contact support if it persists. |
See rate limits & plans for the exact per-second and monthly thresholds.
402 Payment Required
A 402 means the account is out of credits. The body says how many credits were included and used, and carries upgrade_url, the billing page sized to your usage. On Free it also carries checkout_url: a one-time link, valid for seven days, that opens Stripe Checkout for the Builder package (25,000 credits a month) for this account with nothing else to fill in. An agent can hand that link to its user as it is. The same link is emailed once when the credits run out.
{
"error": "Monthly request quota exceeded",
"message": "...Open checkout_url to buy the Builder package (25,000 credits a month for $49) in one step.",
"credits_used": 1000,
"credits_included": 1000,
"upgrade_url": "https://jobspipe.dev/dashboard/billing?credits=25",
"checkout_url": "https://api.jobspipe.dev/v1/checkout/start?t=..."
}403 Forbidden
A 403 uses the same { "error": "..." } shape, and the message says what to do. It is not a rate limit, so retrying does not clear it.
Blocked account
Accounts that break the signup rules, for example accounts registered with a disposable email address, are refused on every authenticated request (API key, dashboard and MCP):
{
"error": "Blocked: this account was created with a disposable email address, which JobsPipe does not allow. Sign up again with your own work email, or book a call at https://jobspipe.dev/book to get verified."
}Sign up again with your own work email, or book a call to get the account verified.
Reading a 400
Unknown filter names are rejected, not ignored. The message names every unknown key and suggests the closest real one:
{
"error": "Invalid search filters: unknown filters: 'query', 'location'. Did you mean query -> job_title_or, location -> job_location_or?"
}A value a filter does not accept is reported the same way, with the value you most likely meant when one is clear. A value we do not track (for example an employer type of government) lists the allowed values without a guess:
{
"error": "Invalid search filters: employer_type_not.0: Invalid enum value. Expected 'employer' | 'agency' | 'broker', received 'staffing'. Did you mean 'agency'?"
}A body that is not usable JSON says why: it is empty, it has a trailing comma or single quotes (with the line and column), or the filters are wrapped in a string field instead of sent as the JSON object itself.
Numbers and booleans sent as strings are accepted ("posted_at_max_age_days": "10" means 10). An explicit null is treated as an absent field.
Handling 429 with backoff
When you hit the per-second limit, pause and retry with exponential backoff:
import time, requests
def search_with_retry(body, headers, tries=4):
for i in range(tries):
resp = requests.post(
"https://api.jobspipe.dev/v1/jobs/search", headers=headers, json=body
)
if resp.status_code != 429:
resp.raise_for_status()
return resp.json()
time.sleep(0.25 * (2 ** i))
raise RuntimeError("rate limited")async function searchWithRetry(body, headers, tries = 4) {
for (let i = 0; i < tries; i++) {
const resp = await fetch("https://api.jobspipe.dev/v1/jobs/search", {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (resp.status !== 429) return resp.json();
await new Promise((r) => setTimeout(r, 250 * 2 ** i));
}
throw new Error("rate limited");
}Backing off does not help with 402 (out of credits) or 403 (blocked account). For a 402, wait for the monthly reset, buy credits or upgrade; on Free, pick a package. For a 403, see 403 Forbidden.