REST API
Read and write BesTest requirements, test cases, cycles and results from scripts and CI pipelines. Setup, authentication, regions and what you can build.
On this page
- What you can do with it
- Set it up
- 1. Create a token
- 2. Find your base URL
- 3. Make your first call
- How to grant access
- The core ideas
- Everything has a key, and the API wants an id
- The values that matter
- Filtering, briefly
- Paging
- Rate limits, and how not to hit them
- What is not here yet
- What "beta" means for stability
- When something goes wrong
- The full reference
- Related
The BesTest REST API lets your CI pipelines, scripts and internal tools read and write the same testing data your team works with by hand: requirements, test cases, cycles, executions and coverage. It is the programmatic door to your single source of truth, so automated results and manual runs end up in one place.
This page is the guide: how to get set up and what is worth building. For the endpoint-by-endpoint detail, use the reference.
Full REST API reference
Every resource, every parameter, every response shape, with copy-paste examples.
Open the REST API referenceIf you would rather start from a working example than from a list of endpoints, the recipes are complete flows you can paste into a terminal: recording a CI run into a cycle, importing in bulk, and gating a release.
The REST API is in open beta. You create your own token from inside the app and start immediately, with no request form and no waiting. The endpoint surface can still change while we are in beta; we will tell you before anything breaks.
What you can do with it
A customer once asked us: "and what would I actually do with the REST API?" The short answer: connect your automation to the same coverage picture your testers use. A few of the things teams build first:
- Push automated results into a cycle. After a nightly Playwright, Cypress, JUnit or Postman run, record each result against the matching test cycle, so automated coverage rolls up beside your manual runs.
- Gate a release from CI. Check whether a cycle is green, or whether every high-significance requirement is covered, and fail the pipeline if it is not.
- Feed your own dashboards. Pull requirements, coverage and execution history into Grafana, Power BI or a data warehouse.
- Keep test cases in sync with code. Create or update test cases from your repo so the catalog matches what is actually automated.
- Bulk import and migrate. Load requirements and test cases from a spreadsheet or another tool in one pass.
- Automate cycle setup. Spin up a test cycle per release or sprint and populate it programmatically.
If you would rather ask for these things in plain English than write code, the MCP integration reaches the same data with the same token.
Set it up
1. Create a token
Open the app menu in BesTest and choose API & MCP tokens, then Create token. Give it a name, pick the Space it may reach, pick an expiry, and choose read only or read and write.
Copy the token immediately. It is shown once and never again. The full walkthrough, including rotation and revocation, is in API tokens.
Pick the narrowest access that does the job. A release gate only reads, so give it a read-only token; only a pipeline that writes results back needs read and write.
2. Find your base URL
Your data lives in one region, and a token only works against that region's server:
| Your data region | API base URL |
|---|---|
| Europe | https://prod-eu.getbestest.com/api/v1 |
| North America | https://prod-us.getbestest.com/api/v1 |
| India | https://prod-in.getbestest.com/api/v1 |
3. Make your first call
curl "https://prod-eu.getbestest.com/api/v1/test_cases?page%5Bsize%5D=5" \
-H "Authorization: Bearer bst_pat_YOUR_TOKEN_HERE" \
-H "Accept: application/vnd.api+json"This API follows the JSON:API standard strictly. A request that asks for application/json is refused with 406 Not Acceptable, even when everything else is correct. Send Accept: application/vnd.api+json on every request, and the same value in Content-Type whenever you send a body. This trips up nearly everyone once.
How to grant access
There is nothing to grant centrally. Anyone who can use BesTest can create their own token, and that token carries their own permissions, in one Space, at the level they chose. Nobody gets more access through the API than they already had in the app.
What a Space admin controls is the other direction: Space settings → API / MCP lists every token pointed at that Space, whoever created it, with its owner, access level, expiry, last use and status. An admin can revoke any of them. That is the lever for someone leaving the team, a leaked token, or an audit.
For a shared pipeline, create the token under an account the team collectively controls rather than a personal one. When that person leaves, a personal token dies with their access and your nightly build goes red at 2am.
The core ideas
Five things carry most of the surface:
| Concept | What it means |
|---|---|
| JSON:API | Requests and responses follow the JSON:API standard: a data envelope, type and id on every resource, and attributes nested under attributes. Errors come back as an errors array. |
| One Space per token | Every request is scoped to the Space its token was created for. There is no cross-Space query. |
| FiltrQL filtering | Filtering is a readable expression, not a nest of bracketed parameters: ?filter=status = 'FAILED' AND executedAt > now-7d. |
| Paging | Collections page with page[number] and page[size]. The default page is 25 and the maximum is 100. |
| Aggregates | Most collections have an /aggregate endpoint for counts and rollups, so you do not page through 10,000 rows to count them. |
Everything has a key, and the API wants an id
In the app you see keys: KAN-CY-38, KAN-TC-42, KAN-RQ-7. Every API path takes a UUID. The bridge is a filter, because the key is an ordinary field on the record:
# The cycle you know as KAN-CY-38
?filter=testCycleKey = 'KAN-CY-38'The field is testCaseKey on test cases, testCycleKey on cycles and requirementKey on requirements. You can also filter across a relationship, which often removes the lookup entirely:
?filter=testCycle.testCycleKey = 'KAN-CY-38' AND testCase.testCaseKey = 'KAN-TC-42'This is the step that catches almost everyone on their first useful call. There is a worked version in Find things by their key.
The values that matter
Automation mostly reads and writes these, so they are worth having in one place:
| Field | On | Values |
|---|---|---|
result | executions | NOT_EXECUTED, IN_PROGRESS, PASSED, FAILED, BLOCKED, SKIPPED |
status | test cycles | DRAFT, ACTIVE, IN_PROGRESS, DONE |
status | requirements | DRAFT, READY, COVERED, ARCHIVED, IN_REVIEW, CHANGES_REQUESTED, APPROVED |
significance | requirements | LOW, MEDIUM, HIGH, CRITICAL |
complexity, impact | requirements | LOW, MEDIUM, HIGH (these two produce significance) |
automationStatus | test cases | MANUAL, AUTOMATED, TO_BE_AUTOMATED, NOT_AUTOMATABLE |
formatMode | test cases | TRADITIONAL, BDD |
Sending a value outside its list is rejected. The reference lists the allowed values on every field that has them, generated from the live schema.
Filtering, briefly
# Failures in the last week
?filter=status = 'FAILED' AND executedAt > now-7d
# High-significance requirements, newest first, 50 at a time
?filter=significance >= 3&sort=-createdAt&page[size]=50
# Side-load related resources in the same request
?include=requirement,folderGET /api/v1/filter-reference returns the complete operator list your deployment supports, with examples. It needs no token, so a client can discover the dialect rather than hard-code it.
Paging
Collections return 25 records by default and at most 100. To walk everything, read meta.totalCount and page until you have it, sorting by a stable field so records do not shuffle between pages:
?page[size]=100&page[number]=2&sort=createdAtUse fields[test_cases]=name,status to return only the columns you want. On the wider entities that is the difference between a 20-field record and a 2-field one, on every row.
Rate limits, and how not to hit them
100 requests a minute per token, shared with MCP. Over it you get 429.
The mistake that produces a support ticket is looping single writes: at 100 a minute, importing 2,000 test cases one POST at a time is a twenty-minute job that dies partway. There are purpose-built ways round it:
| Instead of | Use |
|---|---|
A POST /test_cases per row | POST /rpc/bestest_domain_importTestCases, up to 10,000 rows in one request |
A POST /requirements per row | POST /rpc/bestest_domain_importRequirements, same shape |
| Paging a collection to count it | /aggregate with aggregations[count]=true, one request |
| A follow-up request per related record | include= on the first request |
Worked versions of all four are in Recipes.
What is not here yet
Worth knowing before you design around it:
- No webhooks. Nothing calls you when something changes, so poll on a schedule that suits you. The
updatedAtfield on every entity makes "what changed since my last run" a filter:?filter=updatedAt > now-1h. - No OAuth. Authentication is a bearer token you create yourself. Both are on the roadmap.
- No GraphQL documentation. There is a GraphQL surface, and MCP uses it, but it is not documented for direct use during the beta.
What "beta" means for stability
The API is versioned in its path (/api/v1) and that version is a promise: we will not change the meaning of an existing field or remove an endpoint from v1 without telling you first. Additive changes, meaning new endpoints and new optional fields, can land at any time, so write clients that ignore fields they do not recognise rather than rejecting them.
What beta does mean is that the surface is still growing and we would rather hear from you early. If you build something on this and we are about to make it awkward, we want to warn you, so tell support what you have automated.
Programmatic access is included on every plan, the free tier included. There is no separate API charge and no per-call metering beyond the rate limit above.
When something goes wrong
| Status | What it usually means |
|---|---|
403 | The token is missing, expired, revoked, aimed at a different region or Space, or is read-only and you tried to write. |
404 | No such resource, or it is outside the Space your token reaches. |
406 | Your Accept header was not application/vnd.api+json. |
415 | Your Content-Type was not application/vnd.api+json on a request with a body. |
429 | Over the rate limit. |
The full reference
Every resource, every parameter, every response shape, generated from the API's own schema so it stays current:
REST API reference
Requirements, test cases, cycles, executions, coverage, links, reviews and more, with copy-paste curl for each.
Browse all endpointsRelated
- API tokens - create, rotate and revoke
- Overview - regions, permissions and rate limits
- MCP - the same data in plain English
- Test automation - connecting your CI and coverage
