Conventions
Every endpoint shares one key, one error shape and one rate limit.
Authentication
Section titled “Authentication”Every request goes to https://app.squally.dev/api/v1 plus the path and carries an organization read key as Authorization: Bearer sqly_ro_…. With the key in SQUALLY_API_KEY - the variable the MCP server reads too - the first request lists the organization’s projects:
curl -H "Authorization: Bearer $SQUALLY_API_KEY" https://app.squally.dev/api/v1/projects
A read key is organization-wide (every project), created by an organization admin in Organization › Read 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 one of 7, 14, 30. | Use days=7, 14, 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 Organization › Read keys; do not retry. |
| 401 | key_expired | This read key expired on 2026-09-01. Create a new one in Organization › Read keys. | Create a new read key in Organization › Read keys; keys are not renewed. |
| 401 | key_revoked | This read key has been revoked. | Create a new read key in Organization › Read 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 - Organization › Read keys. | Use an organization read key (sqly_ro_…), not a project ingest key; do not retry. |
| 403 | organization_deleted | This organization has been deleted. It can be restored until 2026-11-22: contact support@squally.dev. | Ask Squally support to restore the organization before the date the sentence names; 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 | rate_limited | More than 60 requests within 60 seconds with one read key. | Wait the seconds Retry-After names (60), then retry. Requests are counted per key in 60-second windows. |
| 500 | internal_error | Something went wrong on our side. | Retry with exponential backoff, starting at 1 second and capped at 30 seconds. |
429: wait the seconds Retry-After names (60), then retry. Requests are counted per key in 60-second windows. 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 - a read key has its own budget, shared with nothing else. Over the limit the API answers 429 with Retry-After: 60 and the usual error body, {"error": "Too many requests with this read key. Wait 60 seconds, then retry.", "code": "rate_limited"}. A caller far beyond the limit can be stopped earlier by the edge firewall with a bare 429 - no body, no Retry-After: wait 60 seconds just the same.
Pagination
Section titled “Pagination”/projects and /projects/{projectId}/branches answer with every item at once, no paging. /runs pages with a cursor (cursor, direction, perPage); /tests and /errors page by number (page, 1-based - a page past the end answers the last page - and perPage).
listTests and listErrors page by number: Squally computes the whole list
and returns the page you ask for. listRuns pages with a cursor.
page: 1-based page number. A page past the end answers the last page.perPage: Rows per page.listRunsandlistErrorstake 10, 25 or 50 (default 10),listTests1 to 100 (default 50).cursor: Opaque. Pass nextCursor for older runs, or prevCursor together with direction=prev for newer runs.direction: Which way the cursor pages.
Counts and run numbers
Section titled “Counts and run numbers”A run’s total, passed, failed, flaky and skipped count tests, each test once over all the run’s shards by its final attempt: passed on the first try, flaky after a retry, failed when the final attempt failed; total = passed + failed + skipped + flaky. Figures over a period - a test’s runs, an error’s occurrences - count finished runs of the source parameter’s kind, as each field’s description says.
runNumber is the run’s number in its project, the #12 the app shows: counted up from 1 per project, one number for all shards of a run, never given twice. Runs removed by the plan’s retention leave gaps. The run routes take the run’s id, not its number.
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.
Which runs count
Section titled “Which runs count”The test metrics and the error figures count runs whose shards all finished,
a sharded run once. Six endpoints - getOverview, listTests,
getTestMetrics, getTestHistory, listErrors and getError - take a
source parameter that picks the runs by where they ran (since 1.0.0-beta.8;
on the two error endpoints since 1.0.0-beta.9):
source: Which runs count, by where they ran: all - every run (the default since 1.0.0-beta.18; ci before); ci - runs a shard reported a CI provider for; local - runs no shard did. The response’s population names the one counted.
Without source, every endpoint counts all runs, CI and local (since
1.0.0-beta.18; CI runs only before). Send source=ci for CI runs only - the
figures the endpoints gave before beta.18. Any value other than ci, local
or all answers 400 invalid_parameter.
listRuns takes source too (since 1.0.0-beta.14), but it lists runs and
counts nothing, so it lists every run when source is absent - the
dashboard’s runs list does the same. Each run says whether it was local:
source on listRuns: Which runs are listed, by where they ran: all - every run (the default); ci - runs a shard reported a CI provider for; local - runs no shard did (each run’s local field). Since 1.0.0-beta.14.
The response’s population names the runs it counted. The test metrics -
listTests, getTestMetrics and the overview’s testMetrics - count each
test per Playwright project, as the Test explorer does; the app’s Overview
counts a test once across projects, so its Failing tests and Flaky tests can
be lower. They leave out runs that were a mass failure as a whole:
Which runs every number counts: runs whose shards all finished, each counted once however many shards it ran in, placed in the period by the start of their first shard - without runs that were a mass failure as a whole (at least 20 tests, over 80% of them failed). Every branch unless the branch parameter names one. A run finishes when all its shards have, or - once it is overdue by the project’s run timeout, counted from its latest shard’s start - when every shard that started has completed, timed out or been stopped by the monthly result limit; such a run counts like a cancelled one, with the results its shards reported (since 1.0.0-beta.10; the monthly result limit since 1.0.0-beta.11). Runs still running are not counted. The prefix names the source parameter: ci_ - CI runs (a shard reported a CI provider); local_ - runs no shard reported a CI provider for; all_ - both, the default since 1.0.0-beta.18 (ci_ before it; since 1.0.0-beta.8; before it, ci_groups_finished_not_mass_failure was the only value, and since 1.0.0-beta.6 - it was ci_completed_not_excluded, which counted every completed shard on its own).
The error figures of listErrors and getError count those runs, and say how
many of an error’s runs were one (massFailureRunCount):
Which runs every error figure counts: runs whose shards all finished, each counted once however many shards it ran in, placed in the period by the start of their first shard - runs that were a mass failure as a whole included (see massFailureRunCount). A run finishes when all its shards have, or - once it is overdue by the project’s run timeout, counted from its latest shard’s start - when every shard that started has completed, timed out or been stopped by the monthly result limit; such a run counts like a cancelled one, with the results its shards reported (since 1.0.0-beta.10; the monthly result limit since 1.0.0-beta.11). Runs still running are not counted. The prefix names the source parameter: ci_ - CI runs (a shard reported a CI provider); local_ - runs no shard reported a CI provider for; all_ - both, the default since 1.0.0-beta.18 (ci_ before it; since 1.0.0-beta.9).
Which runs that is, in plain words: How Squally measures tests and Local runs.