> For the complete documentation index, see [llms.txt](https://docs.drizz.dev/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.drizz.dev/writing-tests/api-steps.md).

# API

Drizz calls a registered API in the middle of a test. Register the API once, then call it by name with `API: <name>`.

|                    |                                                                                  |
| ------------------ | -------------------------------------------------------------------------------- |
| **Platforms**      | Android · iOS                                                                    |
| **Register first** | Editor → API → paste a cURL command → save under a name                          |
| **Response refs**  | `API.<name>.response` · `.request` · `.status_code` · plus dotted paths          |
| **Position**       | An API step may come before `OPEN_APP`                                           |
| **Watch out**      | "API file not found" means the name in the script doesn't match a registered API |

## Prerequisites

* Drizz desktop app installed and signed in
* A working cURL command for the endpoint, including headers
* The internal flag enabled on the API, for endpoints reachable only from your own network

## Copy this

```
# ShopEase — the cart total on screen matches the backend

API: shopease_cart
  # BEGIN shopease_cart
  {
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer <your_token>"
    }
  }
  # END shopease_cart

SET cart_total = API.shopease_cart.response.total
SET item_count = API.shopease_cart.response.items.length

OPEN_APP com.shopease.android
Wait Until 5 Seconds

Tap on Cart
Wait Until 2 Seconds

Validate that the Order Total on screen matches {{cart_total}}
Validate that the number of items in the cart matches {{item_count}}
```

## Register an API

APIs are registered, not written inline. A registered name is what the script calls, and a cURL command carries the headers across without retyping.

1. Open **Editor → API** in the desktop app.
2. Paste a cURL command for the endpoint.
3. Enter a name in the **save as** field — `shopease_cart`.
4. Save the API.
5. Write `API: shopease_cart` in a test and run it. Confirm the step reports a status code.

## Calling it

```
API: shopease_cart
  # BEGIN shopease_cart
  { "headers": { "Content-Type": "application/json" }, "body": { "user_id": "42" } }
  # END shopease_cart
```

The request block sits indented under the call line, between `# BEGIN <name>` and `# END <name>`, and holds JSON. Headers and body set here override the registered values for this test.

API blocks stay expanded, unlike module blocks. The payload is part of the test.

`API:` is the current keyword. `EXECUTE_API:` is the legacy form and still runs, so old scripts keep working. Write `API:` in anything new.

## Using the response

| Reference                    | Gives you                     |
| ---------------------------- | ----------------------------- |
| `API.<name>.response`        | The whole response body       |
| `API.<name>.response.<path>` | Any dotted path into the body |
| `API.<name>.request`         | The request that was sent     |
| `API.<name>.status_code`     | The HTTP status code          |

```
SET cart = API.shopease_cart.response
SET item_count = API.shopease_cart.response.items.length
SET first_item = API.shopease_cart.response.items.0.name

Validate that {{first_item}} is visible in the cart
```

Assign a reference to a variable with `SET`, then reference the variable as `{{var}}`. See [Store & SET](/writing-tests/which-variable/store-and-set.md) for what else `SET` accepts.

## Where the call runs from

{% tabs %}
{% tab title="Cloud (default)" %}
API calls go out from Drizz's cloud. No configuration is needed.

A `localhost` or LAN endpoint called from the cloud times out after roughly 24 seconds.

{% hint style="warning" %}
A local stack requires the internal flag on that API. Without it the call leaves from the cloud and cannot reach the endpoint.
{% endhint %}
{% endtab %}

{% tab title="Internal network" %}
An API flagged **internal** is routed through your desktop app. The request leaves from your own machine and hits your own DNS, so VPN-only and internal-network endpoints work without allowlisting any Drizz IPs.

The flag is set **per API**, not per organization, so one test can hit a public endpoint and an internal one. Ask Drizz support to enable it on a given API.
{% endtab %}
{% endtabs %}

## Check the UI against the backend

Assert that what the app shows matches what the backend returned.

1. Launch the app and reach the screen.

   ```
   OPEN_APP com.shopease.android
   Wait Until 5 Seconds
   Tap on Cart
   Wait Until 2 Seconds
   ```
2. Call the API.

   ```
   API: shopease_cart
     # BEGIN shopease_cart
     { "headers": { "Content-Type": "application/json" } }
     # END shopease_cart
   ```
3. Check the call succeeded.

   ```
   Validate that API.shopease_cart.status_code equals 200
   ```
4. Compare the response to the screen.

   ```
   SET cart_total = API.shopease_cart.response.total
   Validate that the Order Total on screen matches {{cart_total}}
   ```

## Seed data before the app opens

An API step can run before `OPEN_APP`, which sets a known state without clicking through the UI to build it.

```
# ShopEase — the promo banner shows for an eligible account

API: shopease_grant_promo
  # BEGIN shopease_grant_promo
  { "headers": { "Content-Type": "application/json" }, "body": { "phone": "9000000000", "promo": "SHOPEASE10" } }
  # END shopease_grant_promo

Validate that API.shopease_grant_promo.status_code equals 200

OPEN_APP com.shopease.android
Wait Until 5 Seconds

Type 9000000000 in the mobile number field
Tap on Get OTP
Type 123456 in the OTP field
Tap on Continue
Wait Until 5 Seconds

Validate that the SHOPEASE10 promo banner is visible
```

## Location-specific content

Fetch what the backend returns for a location, then check the app agrees.

```
# ShopEase — offers for Bangalore match the backend

SET_GPS(latitude=12.9716, longitude=77.5946)

API: shopease_offers
  # BEGIN shopease_offers
  { "headers": { "Content-Type": "application/json" }, "body": { "latitude": 12.9716, "longitude": 77.5946 } }
  # END shopease_offers

SET offer_count = API.shopease_offers.response.offers.length
SET top_offer = API.shopease_offers.response.offers.0.title

OPEN_APP com.shopease.android
Wait Until 5 Seconds

Tap on Offers
Wait Until 3 Seconds

Validate that {{top_offer}} is visible on the offers screen
Validate that the number of offers shown matches {{offer_count}}
```

## Common mistakes

<table data-search="false"><thead><tr><th>What you write</th><th>What happens</th></tr></thead><tbody><tr><td><code>API: shopease_cart</code> when it's registered as <code>shopease-cart</code></td><td>"API file not found". The names must match exactly</td></tr><tr><td>An API step with no registered API behind it</td><td>"API file not found" — register it first</td></tr><tr><td><code># BEGIN shopease_cart</code> … <code># END cart</code></td><td>Names don't match, the block doesn't parse</td></tr><tr><td>Editing the request block expecting it to collapse</td><td>API blocks stay expanded by design. That's not a bug</td></tr><tr><td><code>EXECUTE_API: get_cart(user="42")</code> in a new test</td><td>Legacy form. Still runs, but write <code>API:</code> and put args in the block</td></tr><tr><td>Calling a <code>localhost</code> endpoint without the internal flag</td><td>The call leaves from the cloud and times out after ~24s</td></tr><tr><td><code>SET total = API.shopease_cart.response.total</code> with no status check</td><td>A 500 gives you an empty value and a confusing failure three steps later</td></tr></tbody></table>

## Next

* [Store & SET](/writing-tests/which-variable/store-and-set.md)
* [Validate](/writing-tests/tap/validate.md)
* [Recipes](/writing-tests/recipes.md)

***

*Last updated: 6 August 2026*
