# Choose what a blocked request receives

> Reusable firewall block responses: the status code, content type, body and headers a request blocked by an ApiTraffic WAF policy receives, shared across policies and enforced offline.

Source: https://www.apitraffic.io/docs/waf/block-responses

A block response is what a request your WAF policy blocks receives: a status code, a content type, a body and a few caching headers. You write a response once, on the **Block Responses** tab of **WAF**, and pick it in any policy. Change a response and every policy that uses it is updated and pushed to your connected servers.

A policy that uses no response answers blocked requests with `403 Forbidden`.

## Use cases

- **Take part of your API offline.** Add a custom rule that blocks the paths you are working on, and give the policy a `503` response with a `Retry-After` header and a message saying when the service is back.
- **Explain a refusal.** A `451` for regions you cannot serve, or a `403` that tells the caller who to contact.
- **Match your error format.** Send the same JSON error shape the rest of your API sends, so client code handles a block like any other error.

## Create a response

*The Block Responses tab, with the policies that use each response.*

![Block Responses tab listing three responses with their status, content type and the policies that use them](https://www.apitraffic.io/docs/images/block-responses/list.png)

1. Open **WAF** and select the **Block Responses** tab.
2. Select **Add Block Response**.
3. Enter a name, then set the status code, content type, body and headers.
4. Check the preview: it shows the status line, headers and body exactly as a blocked client receives them.
5. Save.

*Editing a response: the body editor beside a preview of exactly what a blocked client receives.*

![Edit Block Response dialog with status, content type, a JSON body editor, headers and the response preview](https://www.apitraffic.io/docs/images/block-responses/editor.png)

To use it, edit a policy and open its **Block Response** step. You can also create a response from that step.

*A policy's Block Response step.*

![Policy editor on the Block Response step with a response selected and its preview](https://www.apitraffic.io/docs/images/block-responses/policy-step.png)

> **Note:** A response is only sent when the policy blocks. In **Monitor** mode a policy records what it would block and lets every request through.

## Content type

| Content type | Sent as | The body must be |
|---|---|---|
| Auto | `application/json` when the body is a JSON object or array, otherwise `text/plain` | anything |
| JSON | `application/json; charset=utf-8` | a JSON object or array |
| Problem Details | `application/problem+json; charset=utf-8` | a JSON object |
| Plain text | `text/plain; charset=utf-8` | anything |

[Problem Details (RFC 9457)](https://www.rfc-editor.org/rfc/rfc9457) is the standard JSON error format for HTTP APIs, and the default for a new response:

```json
{
  "type": "about:blank",
  "title": "Service Unavailable",
  "status": 503,
  "detail": "Billing is offline for maintenance until 14:00 UTC."
}
```

A Problem Details body's top-level `status` is rewritten to the status the response is actually sent with: when a rate limit answers `429` with it, servers change the member to `429`, as RFC 9457 requires. The body is sent as written when its top-level `status` is missing or not a number, or when the rewrite would take it past the 4096-byte limit.

Every response also carries `X-Content-Type-Options: nosniff`. HTML, XML and SVG are not available: the body is served from your own domain, and a browser would render those as a page.

> **Info:** SDK 2.x releases from before content types answer with **Auto** whatever the response says: a Problem Details response then goes out as `application/json`, which clients still parse. Releases before 2.0.0 send the body with your framework's default content type (`text/html` on Express), so upgrade before relying on a JSON response.

## Reference the blocked request

Write `{context_id}` anywhere in the body, and each blocked request's response carries that request's ApiTraffic context id (`ctx_…`) in its place. A caller who quotes it in a support ticket hands you the exact request, when it was recorded: paste the id into the request log's **Advanced** search box, or search for `context_sid == ctx_…`, to open it with the firewall's verdict and the rules that matched. The search covers the log's time range, so widen it to when the request was blocked.

In Problem Details, put it in `instance`, the member RFC 9457 has for "this occurrence":

```json
{
  "type": "about:blank",
  "title": "Forbidden",
  "status": 403,
  "detail": "This request was blocked by our firewall. Quote the instance below when you contact support.",
  "instance": "{context_id}"
}
```

Select **Insert `{context_id}`** to put it where the cursor is. In a JSON object with the cursor outside a string, it is added as `instance` in Problem Details (or `context_id` when `instance` is taken), or as a `context_id` member. When both are taken, or in a JSON array, put the cursor inside a string. The preview shows it with an example id.

- Every `{context_id}` in the body is filled in. The id is 31 characters, and the 4,096-byte limit counts it filled in. A body the id would take past the limit is sent with the placeholder left empty.
- Inside a JSON string, the body stays valid JSON.
- The body differs for every request, so shared caches must not store it. Without a `Cache-Control` header, servers send it with `Cache-Control: no-store`; a `Cache-Control` you set must include `private` or `no-store` (one that doesn't is refused on save, and servers leave the id out).
- The id is the one the request is captured under, so it needs the ApiTraffic SDK capturing the request, with `apitraffic.middleware()` mounted before the firewall as in [Set up the firewall](https://www.apitraffic.io/docs/waf/setup). Without it, the placeholder is left empty.
- It needs `@apitraffic/waf` 2.1.0 or later. Older versions send `{context_id}` as written.

## Body and headers

- The status is 200 to 599, except `204`, `205` and `304`, which carry no body.
- The body is at most 4,096 bytes and cannot contain `<`. SDK releases before 2.0.0 serve the body as HTML, so markup is refused for every response.
- Headers are limited to `Retry-After`, `Cache-Control` and `RateLimit-*`. Cookies, redirects and CORS headers cannot be set from a block response.
- A block answers one client: one that hit a rate limit, or comes from a denied IP or country. Give `Cache-Control` `private` or `no-store`, or a CDN in front of your API can store that block and serve it to every client of the URL. The editor warns when it does not.

## What uses the response

Every block a policy makes sends its response: managed rules, custom rules, IP, country, user-agent and path lists, and structural limits. **Rate limits** send the response body with their own status (`429` by default) and `RateLimit-*` headers, and **behavioral denies** send it with their own status.

## When ApiTraffic is unreachable

A policy carries its response's content, so the response is part of the policy your servers receive, and of every [offline definitions](https://www.apitraffic.io/docs/reference/offline-definitions) tier: the disk cache, the CDN mirror and the pinned `.apitraffic/definitions/sdk.json`. A server that boots while ApiTraffic is unreachable answers blocked requests exactly as a live one does.

Changing a response changes the definitions of every policy that uses it. Like any rule change, it updates the definitions pull request in a linked repository, and `definitions check` flags a pinned file that predates it.

## Delete a response

A response in use cannot be deleted. Pick another response for the policies that use it, then delete it from the **Block Responses** tab.

## API

Block responses are managed at `/v1/accounts/{accountSid}/waf-block-responses`. A policy names its response with `blockResponseSid`, and its `defaultBlock` then follows that response: your servers enforce `defaultBlock`, and the response's content is copied into it whenever either changes.

- `blockResponseSid: null`, or a `defaultBlock` sent without `blockResponseSid`, gives the policy its own response, as before block responses existed. A `defaultBlock` that carries the linked response's content back unchanged (as a client that predates block responses sends it) keeps the link.
- An update that sends neither keeps what the policy has.
- A body may contain `{context_id}`, filled with each blocked request's context id. See [Reference the blocked request](#reference-the-blocked-request).
- An update of a response (`PUT`) changes only the fields it sends. Send `expectedUpdatedAt` (the `updatedAt` you loaded) to have it refused with `409` if someone changed the response since; the error's `data.updatedAt` is the version stored now.
