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.

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 reference

If 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.

Open beta

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 regionAPI base URL
Europehttps://prod-eu.getbestest.com/api/v1
North Americahttps://prod-us.getbestest.com/api/v1
Indiahttps://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"
The Accept header is not optional

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:

ConceptWhat it means
JSON:APIRequests 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 tokenEvery request is scoped to the Space its token was created for. There is no cross-Space query.
FiltrQL filteringFiltering is a readable expression, not a nest of bracketed parameters: ?filter=status = 'FAILED' AND executedAt > now-7d.
PagingCollections page with page[number] and page[size]. The default page is 25 and the maximum is 100.
AggregatesMost 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:

FieldOnValues
resultexecutionsNOT_EXECUTED, IN_PROGRESS, PASSED, FAILED, BLOCKED, SKIPPED
statustest cyclesDRAFT, ACTIVE, IN_PROGRESS, DONE
statusrequirementsDRAFT, READY, COVERED, ARCHIVED, IN_REVIEW, CHANGES_REQUESTED, APPROVED
significancerequirementsLOW, MEDIUM, HIGH, CRITICAL
complexity, impactrequirementsLOW, MEDIUM, HIGH (these two produce significance)
automationStatustest casesMANUAL, AUTOMATED, TO_BE_AUTOMATED, NOT_AUTOMATABLE
formatModetest casesTRADITIONAL, 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,folder

GET /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=createdAt

Use 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 ofUse
A POST /test_cases per rowPOST /rpc/bestest_domain_importTestCases, up to 10,000 rows in one request
A POST /requirements per rowPOST /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 recordinclude= 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 updatedAt field 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

StatusWhat it usually means
403The token is missing, expired, revoked, aimed at a different region or Space, or is read-only and you tried to write.
404No such resource, or it is outside the Space your token reaches.
406Your Accept header was not application/vnd.api+json.
415Your Content-Type was not application/vnd.api+json on a request with a body.
429Over 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 endpoints
Getting started

Live in about a minute.

  1. ~30 seconds
    1.Install from the Marketplace

    One click on "Get it now" - no sales call, no signup form, no separate login.

  2. ~1 minute
    2.Enable it on a Space

    Flip it on in Space settings. BesTest shows up in the Space menu, where your team already works.

  3. right away
    3.Run your first test

    Create a requirement, link a test case, hit run. No training course required.

Host your data in the EU, US, or IndiaNo Jira issue bloat - your library stays out of Jira’s wayBuilt on Atlassian Forge