Skip to main content

Error Format

Errors return JSON with a detail field:
The 401 text says which of three things is wrong: no key was sent, the key does not start with vq_key_, or the key is unknown, revoked or expired. Validation errors (422) keep detail as a short string and put the field-level details in errors:
Read detail for the message and errors[].loc to find the field. Some validation errors carry extra keys on each entry, such as ctx for a failed rule. A 422 from a rule that spans several fields (chapter without titleNumber, a malformed Boolean expression) is a plain detail string with no errors array, as is every other error status.

Status Codes

On 403, webhook delivery on a watch requires the Business plan: use channel: "email" or upgrade. On 429, the watch cap is 3 outside Business and 100 on Business.

400 versus 422

A 422 is schema validation: a field is missing, or its type or range is wrong. The API rejects it before your request reaches any business logic, and the response carries the loc array shown above. A 400 is a request that is well-formed but not allowed. GET and POST /us/statutes/resolve return it for a corpusType or state scope they do not accept, and refund the charge. POST /watches and PATCH /watches/{id} return it for:
  • a scope the board cannot be narrowed by, or an actId scope that does not exist on that board,
  • a source that is not scopable at all, meaning a board whose scopable is false such as the Federal Register,
  • a missing webhookUrl or emailAddress for the channel you asked for, or one set on a channel that does not use it,
  • a webhookUrl the API refuses as a delivery target,
  • a webhookAuth.headerName that is not a legal header name, or outbound auth set on an email-only watch.
The detail string names the specific problem, so read it rather than retrying.

409 on watches

POST /watches returns 409 when you already hold a watch on the same board, the same channel, and the same scope. Watches are unique on that triple, so a 409 means the subscription already exists. Nothing has failed. PATCH /watches/{id} returns it for the same reason, when narrowing a watch’s scope would collide with another watch you already hold. List your watches with GET /watches and reuse the existing one.

Rate Limiting

Limits are enforced per API key and scale with your plan. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (a Unix timestamp) for the per-minute window, and X-RateLimit-Limit-Day and X-RateLimit-Remaining-Day for the daily one. When you exceed one the API returns 429 with a Retry-After header telling you how many seconds to wait. Respect it, and add exponential backoff so retries spread out under sustained pressure.
Pace your requests before you hit the ceiling. A small client-side delay between calls in a paging loop keeps you under the limit and cuts down on retries.

Troubleshooting

Include the Bearer prefix: Authorization: Bearer vq_key_.... The key alone is rejected.
Set Content-Type: application/json. Check the loc field in the response to find the problematic field.

Authentication

Create and manage your API key.

Best practices

Scope queries, cache coverage, and retry well.

SDKs

The official Python and TypeScript SDKs map these errors to typed exceptions and retry the safe ones for you.
Last modified on October 29, 2026