Conventions
Every endpoint shares one key, one error shape and one rate limit.
Authentication
Section titled “Authentication”Every request carries an organization read key as Authorization: Bearer sqly_ro_…. A read key is organization-wide (every project), created by an organization admin in Settings → API keys, and expires after 30, 60, 90, 180 days as chosen at creation. There is no renewal: create a new key and revoke the old one. Project ingest keys (sqly_…) are refused with wrong_key_type.
Errors
Section titled “Errors”Every error is {"error": "<sentence>", "code": "<slug>"}. The sentence may be reworded; the slug may not. An id that does not exist and an id that belongs to another organization get the same 404, byte for byte.
| HTTP | Code | When | What to do |
|---|---|---|---|
| 400 | ambiguous_test | Several tests have this name. Pass ?filePath= with one of: tests/a.spec.ts, tests/b.spec.ts. | Repeat the request with ?filePath= set to one of the files the sentence lists. |
| 400 | invalid_parameter | sha must be 4 to 40 hexadecimal characters (a commit SHA or a prefix of one). | Fix the request; do not retry. |
| 400 | invalid_period | days=90 is beyond this plan’s 60-day retention. Use 60 or less. | Use days=7, 30 or 90, within the plan’s retention; do not retry. |
| 401 | invalid_key | Invalid read key. | Check the key for typos or create a new one in Settings → API keys; do not retry. |
| 401 | key_expired | This read key expired on 2026-09-01. Create a new one in Settings > API keys. | Create a new read key in Settings → API keys; keys are not renewed. |
| 401 | key_revoked | This read key has been revoked. | Create a new read key in Settings → API keys; this one will not work again. |
| 401 | missing_key | Missing read key. | Send the read key as Authorization: Bearer sqly_ro_…; do not retry without it. |
| 401 | wrong_key_type | This is a project ingest key. Read routes need an organization read key - Settings > API keys. | Use an organization read key (sqly_ro_…), not a project ingest key; do not retry. |
| 403 | tier_too_low | The read API requires the Standard plan. This organization is on Free. | Upgrade the organization to Standard or Pro; the same key works again after the upgrade. |
| 404 | error_not_found | No error signature with this fingerprint in this project. | Take the fingerprint from GET /projects/{projectId}/errors; do not retry. |
| 404 | project_not_found | No project with this id in this organization. | Take the project id from GET /projects; do not retry. |
| 404 | run_not_found | No run with this id in this project. | Take the run id from GET /projects/{projectId}/runs; do not retry. |
| 404 | test_not_found | No test with this name in this project. | Check the test name and filePath exactly as Squally shows them; do not retry. |
| 429 | — | More than 60 requests within 60 seconds with one read key. | Wait 60 seconds, then retry. The edge counts requests per key in 60-second windows and sends no Retry-After header. |
| 500 | internal_error | Something went wrong on our side. | Retry with exponential backoff, starting at 1 second and capped at 30 seconds. |
429: wait 60 seconds, then retry. The edge counts requests per key in 60-second windows and sends no Retry-After header. 5xx: retry with exponential backoff, starting at 1 second and capped at 30 seconds. 400, 401, 403, 404: never retry - the same request gets the same answer.
Rate limit
Section titled “Rate limit”Currently 60 requests per 60 seconds per key, counted per Authorization header - a read key has its own budget, shared with nothing else. Over the limit the edge answers 429 without a JSON body.
Pagination
Section titled “Pagination”Lists that are computed whole page by number; the runs list pages with a cursor.
page: 1-based page number. A page past the end answers the last page.perPage: Rows per page.cursor: Opaque. Pass nextCursor for older runs, or prevCursor together with direction=prev for newer runs.direction: Which way the cursor pages.
Periods
Section titled “Periods”days: The period, in days, ending now. Must not exceed the plan’s retention (Standard 60, Pro 90 days); a longer period is refused with 400 invalid_period rather than answered with less data.