GET /v1/jobs 400 unknown_parameter: a typo'd filter no longer widens
A misspelled query key on GET /v1/jobs, such as state for status, now returns 400 with a suggestion instead of a full unfiltered page. Handle it in TypeScript.

GET /v1/jobs?state=failed does not return a page of failed jobs. It returns 400 unknown_parameter with the message Unknown query parameter: 'state'. Did you mean 'status'? The list route accepts limit, scope, run_id, thread_id, type, status and starting_after, and refuses any other query key.
Why the API is strict here
A dropped filter does not lose a field, it widens the result. The API source records that GET /v1/jobs?thread_id=... once answered 200 with every job in the workspace when the filter was not honored. A caller who narrowed a search then received a larger page that looked like an answer. Rejecting unknown keys makes the mistake loud.
The response body
The error carries details.errors, one entry for each unknown key, with path, loc (which is ["query", key] for a query string), type: "unknown_parameter" and a suggestion when a known key is within an edit distance of 2. details.accepted lists every valid key, sorted.
| You sent | Suggestion | Outcome |
|---|---|---|
| state=failed | status | 400 unknown_parameter |
| runid=arun_1 | run_id | 400 unknown_parameter |
| cursor=abc | none within distance 2 | 400 unknown_parameter, see details.accepted |
| status=failed | (valid) | 200, one page of failed jobs |
Handle it in a client
Treat this error as a bug in your code, not a retryable failure. Print the suggestion and stop. The helper below builds the query from a typed allow-list, so a typo fails at compile time before it reaches the API.
type JobsQuery = {
limit?: number; scope?: "thread" | "workspace"; run_id?: string;
thread_id?: string; type?: string; starting_after?: string;
status?: "queued" | "processing" | "completed" | "failed" | "canceled";
};
export function jobsUrl(base: string, q: JobsQuery): string {
const url = new URL("/v1/jobs", base);
for (const [k, v] of Object.entries(q)) if (v !== undefined) url.searchParams.set(k, String(v));
return url.toString();
}
console.log(jobsUrl("https://api.sume.com", { status: "failed", limit: 50 }));
// https://api.sume.com/v1/jobs?status=failed&limit=50Paging
Results are newest first and capped at 100 for each page. When data.next_cursor is present, send it back as starting_after. That key is on the allow-list as well, so a paging loop is unaffected by the strict check.
Sources
Related posts
More in Developers
- Sume job status headers: cache-control no-store and x-sume-poll-after
GET /v1/jobs/:id/status is never cacheable and sends x-sume-poll-after: 2. What each header means for CDNs, browsers and a TypeScript poll loop.
- Cold Sume API key burst: first requests get the Free 120-write floor
The API reads your plan after auth, so a first-seen key is judged at the Free 120 write floor. Warm a Pro or Scale key with GET /v1/me before a burst.
- Sume ratelimit-limit: read the budget from the header, not a table
Plan numbers are 120 to 1200 writes a minute, but dev and self-hosted deployments can differ. Calibrate a Python client from ratelimit-limit at startup.
- Sume ratelimit-reset: sleep until the 60-second window ends (Python)
Every Sume /v1 response carries ratelimit-remaining and ratelimit-reset. Stop at zero and sleep the reset seconds instead of eating a 429. A Python wrapper.
Written by Sume