Skip to content

Call an API from a test

Some setup does not belong in a browser. Creating the user your test signs in as, seeding a product, resetting a counter afterwards — doing it through the UI is slow, and it fails for reasons that have nothing to do with what you are testing.

An API request step does that part directly.

Requests live on the project, not on a single test, so several tests can share one.

  1. Open the Tests page and edit the project (the pencil beside the project selector).
  2. Find API requests and add one.
  3. Give it a reference — a short name like create-user. That is what a step will point at.

If you already have the call as a curl command, paste it. The method, URL, headers and body are filled in from it.

Add a step, choose Call an API request, and put the request’s reference in the target field. Not a URL, and not a page element — the reference of the saved request.

The step passes when the response arrives with the status you expect, and fails with the status and body when it does not, like any other step.

Do not paste a token into a header. Write {{env:NAME}} instead, and the agent reads it from the environment of the machine it is running on:

Authorization: Bearer {{env:CI_TOKEN}}

Two things make that safe rather than convenient:

  • The agent decides what it will expose. It reads an allowlist from your own machine, never from us. Nothing is exposed by default — you list the variable names you are willing to let tests read. A request from our side to name a variable is refused, so a compromised server cannot read your environment.
  • Everything it exposes is masked, unconditionally, wherever a value would otherwise appear — reports, step logs, the live runner. You do not have to mark it sensitive.

A request can save part of the response for later steps. Give the value a name and a dotted path into the JSON:

userId ← data.user.id

Later steps use it as {{userId}}, exactly like a test variable. If the value is a token or anything else worth hiding, mark that save sensitive and it is masked everywhere a value would be shown.

An environment starts able to call nothing. You add the hosts it is allowed to reach.

That is the wrong way round for convenience and the right way round for safety: an environment that has not been given a destination cannot be pointed at one by accident, or by a test someone else wrote.

The list belongs to the environment, so it is checked twice against two different lists, and the difference matters:

  • Saving a request needs the host to be permitted by at least one of the project’s environments — a request written for staging alone is not refused because production has never heard of that host.
  • Running needs it permitted by the one environment that run resolved. So the same saved request runs against staging and is refused against production, which is the point.

A request the run’s environment does not permit never reaches the runner: the URL stays on our side, and the step fails naming both the host and the environment.