> This page is for version v2 (default).
> For other versions, use one of these documentation indexes:
> - v2 (default): https://docs.paid.ai/v-2/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.paid.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paid.ai/_mcp/server.

# Void a signal (Early access)

> Take one signal out of what a customer is billed for, and know what a void can and cannot reach

A void takes one signal out of your usage. Use it when a signal should never
have counted: a test event sent to production, a signal sent for the wrong
customer, or a retry your system sent under a second key.

**Early access.** Signal voiding is new and still being extended.

A voided signal stops counting as usage in every billing period it was counted
in. Paid keeps the signal. By default the signal reads leave it out, and you can
read it with [`includeVoided=true`](#reading-voided-signals).

A void is permanent. There is no way to un-void a signal. To correct a signal,
void it and send a new one under a new idempotency key.

## Prerequisites

* An organization [API key](https://app.paid.ai/settings/api-keys) with the
  `write:usage` scope. Full-access keys have it. Voiding needs an API key; a
  dashboard session cannot void a signal. A void is not available through the
  MCP server or the in-product assistant yet.
* Signal voiding turned on for your organization. Ask your Paid contact to
  enable it. Until then, the void endpoints answer `404`, as for a path that
  does not exist. That `404` carries `API_ENDPOINT_NOT_FOUND` under `error.code`.
  A missing signal answers `404` with `SIGNAL_NOT_FOUND` in a top-level `code`
  instead. While voiding is being turned on for you, a request can briefly get
  `403` with `FEATURE_NOT_ENABLED` instead of the `404`.
* The signal's ID, or the idempotency key you sent it with. Every signal in
  [`GET /api/v2/signals`](/api-reference/api-reference/signals/list-signals)
  carries its `id`.

## Void a signal by ID

`PUT /api/v2/signals/{id}/void` voids the signal with that ID. The body is
optional. Send a `reason` of up to 500 characters if you want one kept with the
void.

```bash
curl -X PUT https://api.agentpaid.io/api/v2/signals/6890b0e2a6f2c30012f0a1b3/void \
  -H "Authorization: Bearer $PAID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "Test event sent to production"}'
```

```json
{
  "signalCorrectionId": "0f6c2f4e-8d0b-4c1a-9a53-6a7d3a1b2c90",
  "alreadyVoided": false,
  "factsReverted": 1,
  "pendingCancelled": 0,
  "cells": [
    {
      "orderLineAttributeId": "4b7a1d3e-52c9-4e0f-8f21-9c3d6e5a7b18",
      "nextBillingDate": "2026-12-01T00:00:00.000Z",
      "quantity": 1,
      "nonBillable": false
    }
  ],
  "credits": []
}
```

| Field                          | Meaning                                                                                                                                                                                                                                                                                              |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `signalCorrectionId`           | The ID of the void itself.                                                                                                                                                                                                                                                                           |
| `alreadyVoided`                | `true` when the signal had been voided before and nothing changed now.                                                                                                                                                                                                                               |
| `factsReverted`                | How many of the signal's usage records the void took back: one for each entry in `cells`. `0` when the signal was not counted in any billing period. On a repeat of an earlier void this is that void's count, and `cells` is empty.                                                                 |
| `pendingCancelled`             | How many of the signal's credit charges that had not yet been applied the void cancelled. `0` when there were none. On a repeat of an earlier void this is that void's count.                                                                                                                        |
| `cells`                        | The usage the void took back, one entry per order attribute and billing period the signal was counted in, and a second entry for the same period when it also held tracked-only usage (`nonBillable` is `true`). Empty when the signal was not counted anywhere, and on a repeat of an earlier void. |
| `cells[].orderLineAttributeId` | The ID of the order attribute (an order line's priced item) whose usage this void took back.                                                                                                                                                                                                         |
| `cells[].nextBillingDate`      | The billing date of the billing period this usage was counted in, as an RFC 3339 timestamp in UTC. With `orderLineAttributeId` it identifies the period.                                                                                                                                             |
| `cells[].quantity`             | How much usage was taken back from this billing period, in the attribute's own usage units: the signal's net quantity there.                                                                                                                                                                         |
| `cells[].nonBillable`          | `true` when the usage taken back was tracked but never billed, because the signal's customer unit is not billable. `false` for billable usage.                                                                                                                                                       |
| `credits`                      | One entry for each credit currency the signal spent. Empty when it spent none. See [Prepaid credits](#prepaid-credits).                                                                                                                                                                              |
| `credits[].creditsCurrencyId`  | The credit currency.                                                                                                                                                                                                                                                                                 |
| `credits[].restored`           | The credits the void gave back, as an exact decimal string: credits returned to the customer's balance, plus any overage the void cleared (usage beyond the customer's credits, which would have been charged).                                                                                      |
| `credits[].forfeited`          | The credits the void could not return, as an exact decimal string, for example because the credits they were spent from have expired or were used up.                                                                                                                                                |
| `credits[].reversals`          | How many of the signal's credit spends the void reversed.                                                                                                                                                                                                                                            |

### Voiding twice is safe

Voiding a signal that is already voided answers `200` with the same
`signalCorrectionId` and `alreadyVoided: true`. Your usage, billing and credits
are not changed a second time, so you can repeat a void after a timeout without
checking first. A voided signal reads as not found by default, but voiding it
again still answers `200`. Your audit log records the repeat. The repeat's `cells` array is
empty, because the billing effects were returned when the void was first made.

## Void a signal by idempotency key

`PUT /api/v2/signals/idempotency-key/{idempotencyKey}/void` voids the signal
you sent with that idempotency key. The answer is the same as for a void by ID.

* **The key is matched exactly as stored.** URL-encode it, in particular if it
  contains `/`, `?` or `#`.
* **A signal sent with a customer API key is stored under a namespaced key.**
  Void it by its ID.
* **A key that names no signal answers `404`** with `SIGNAL_NOT_FOUND`. A signal
  that arrived moments ago may not be stored yet, so try again shortly.
* **A key that names more than one signal answers `409`** with
  `AMBIGUOUS_IDEMPOTENCY_KEY` and voids nothing. Void the signal you mean by
  its ID.
* **A key of `.` or `..` cannot be used in the path.** An HTTP client resolves
  those two segments before it sends the request, so the request never names
  the key. Void that signal by its ID.

## After a void

| What                     | What happens                                                                                                                                                                                                                                                                                                              |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Usage and billing        | The signal no longer counts in any billing period it was counted in.                                                                                                                                                                                                                                                      |
| Reading signals          | The three signal reads leave a voided signal out by default. See [Reading voided signals](#reading-voided-signals).                                                                                                                                                                                                       |
| Sending the signal again | A signal sent again under the key of a voided signal is not stored. `POST /api/v2/signals/bulk` counts it in `voided`, never in `duplicates`. A key stays tied to its signal for good, voided or not. To send a corrected signal, use a new idempotency key. See [Resending a voided signal](#resending-a-voided-signal). |
| Prepaid credits          | The credits the signal spent are returned to the customer's balance where possible. Credits that cannot be returned are forfeited. See below.                                                                                                                                                                             |

### Reading voided signals

A voided signal is one you withdrew, so the three signal reads leave it out by
default:

| Read                          | A voided signal by default                                                 |
| ----------------------------- | -------------------------------------------------------------------------- |
| `GET /api/v2/signals`         | Left out of the list. `pagination.total` counts only the signals returned. |
| `GET /api/v2/signals/{id}`    | Answered as an unknown ID: `404` with `SIGNAL_NOT_FOUND`.                  |
| `POST /api/v2/signals/lookup` | Listed under `notFound`, with the IDs Paid cannot find.                    |

No signal in these default responses carries a `voided` field, because none
returned is voided.

Pass `includeVoided=true` on any of the three reads to include voided signals.
Every signal returned then carries `voided`: `true` for a voided signal and
`false` for any other. `includeVoided=false` and no value behave the same.

These reads apply to organizations with signal voiding turned on. For other
organizations, nothing is filtered, no signal carries `voided`, and
`includeVoided` is accepted and ignored.

When signal voiding is on for your organization and Paid cannot read your voided
signals, a read answers an error instead of showing a voided signal as live:
`503` for the list, and `500` for the read by ID and the lookup. Try again. If
Paid cannot tell whether signal voiding is on for your organization, a read
behaves as if it were off: it leaves nothing out, and no signal carries
`voided`. The list answers `422` when your organization has
more voided signals than one request can leave out. Set `includeVoided=true` or
narrow `createdAtFrom`.

### Resending a voided signal

A voided signal keeps its idempotency key, so a signal you resend under that key
is not stored. The bulk ingest response counts it apart from duplicates:

```json
{ "ingested": 3, "duplicates": 1, "failed": 0, "voided": 1 }
```

This response comes from a batch of 3 new signals, 1 resend of an earlier signal
and 1 resend of a voided signal. `voided` is `0` when none apply. The four counts
add up to the number of signals in the batch, so add `voided` if you reconcile
the counts.

### Prepaid credits

A signal can spend a customer's prepaid credits. Voiding it gives them back:

* The ledger gets a **reversal**: a new entry that returns the credits to the
  pool they were spent from. The original spend is never edited. The reversal is
  recorded when you void, and it counts in the period of the original spend.
* If that pool can no longer take the credits back, for example because its
  period has ended, they are **forfeited** and read as expired. The `forfeited`
  amount in the answer says how much.
* Two cases are refused instead. See `CREDIT_POOL_VOIDED` and
  `CREDIT_POOL_AMENDED` below.

## When a void is refused

A refused void changes nothing in your usage, billing or credits. The one
exception is a `502` or `503` from a service failure, where the void may or may
not have been made: see the last rows of the table. The answer carries a `code`
and an `error` message. For a refusal about one billing period, `details` names
the order line and the period.

```json
{
  "error": "the signal belongs to workflow \"run_8841\" of meter \"Research run\", which has closed; the workflow is charged once it closes, so a void now would not reach the charge; once the period is invoiced, a credit note corrects it",
  "code": "WORKFLOW_CLOSED"
}
```

| Code                                                       | Status       | When                                                                                                                                                                                                                                                                                                                                                                                                                | What to do                                                                                                                                          |
| ---------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `WORKFLOW_CLOSED`                                          | `400`        | The signal is a step of a workflow that has already closed. Paid charges a workflow once, when it closes, so a void now would not reach the charge. This can also be the answer shortly before the charge is written.                                                                                                                                                                                               | Once the period is invoiced, correct the charge with a credit note.                                                                                 |
| `PERIOD_CLOSED`                                            | `400`        | The billing period the signal was counted in is settled.                                                                                                                                                                                                                                                                                                                                                            | Correct it with a credit note.                                                                                                                      |
| `VOID_LIMIT_EXCEEDED`                                      | `400`        | The customer has reached the limit on how many signals can be voided in a rolling time window. By default the limit is 100,000 voided signals per customer in any 30 days. It is a per-organization setting, and the error message names the limit that applies. This is not the API rate limit. Retrying right away does not help; the void is allowed again once earlier voids age out of the window.             | Ask your Paid contact about a larger correction.                                                                                                    |
| `COST_PLUS_LINE`                                           | `400`        | The signal was billed on a cost-plus price, which a void does not yet reach.                                                                                                                                                                                                                                                                                                                                        | Correct it with a credit note.                                                                                                                      |
| `CREDIT_POOL_VOIDED`                                       | `400`        | A credit the signal spent sits in a pool that was voided with its order, or with an invoice.                                                                                                                                                                                                                                                                                                                        | Reactivate the order, then void the signal.                                                                                                         |
| `CREDIT_POOL_AMENDED`                                      | `400`        | A credit the signal spent sits in a pool that an order change replaced, and the credits cannot yet follow the usage to the new pool.                                                                                                                                                                                                                                                                                | Correct the customer with a credit note or a grant.                                                                                                 |
| `COVERED_BY_ACTIVATION_CHARGE`                             | `400`        | The signal added to or took away from usage that an order's activation already charged in prepaid credits. That charge covers all the usage of the billing period so far as a single charge that names no signal, so the credits it spent cannot be given back for one signal. Nothing is voided and nothing is returned, including any other charge the signal has. `details` names the order line and the period. | Correct the customer with a credit grant or a credit note. See [Signals counted by an activation charge](#signals-counted-by-an-activation-charge). |
| `SIGNAL_NOT_FOUND`                                         | `404`        | No signal in your organization has that ID or key.                                                                                                                                                                                                                                                                                                                                                                  | Check the ID or key. If the signal arrived moments ago, try again shortly.                                                                          |
| `AMBIGUOUS_IDEMPOTENCY_KEY`                                | `409`        | The key names more than one signal.                                                                                                                                                                                                                                                                                                                                                                                 | Void the signal you mean by its ID.                                                                                                                 |
| `SIGNAL_NOT_INDEXED`, `RETRY_LATER`                        | `400`        | The signal is not ready to be voided yet, or cannot be voided right now.                                                                                                                                                                                                                                                                                                                                            | Try again in a few minutes.                                                                                                                         |
| `SIGNAL_HAS_NO_CUSTOMER`                                   | `400`        | The signal has no customer, so there is nothing to void.                                                                                                                                                                                                                                                                                                                                                            | None needed.                                                                                                                                        |
| `INVALID_REQUEST`                                          | `400`        | The ID or key in the path has a malformed percent-escape.                                                                                                                                                                                                                                                                                                                                                           | Percent-encode the key properly.                                                                                                                    |
| `VALIDATION_ERROR`                                         | `400`        | The request is not valid: an ID that is not a signal's 24-character hex ID, or a `reason` of more than 500 characters.                                                                                                                                                                                                                                                                                              | Fix the ID or the body and send it again.                                                                                                           |
| `UNAUTHORIZED`                                             | `401`        | The request has no valid API key.                                                                                                                                                                                                                                                                                                                                                                                   | Send a valid API key.                                                                                                                               |
| `INSUFFICIENT_SCOPE`                                       | `403`        | The API key does not have the `write:usage` scope. `details` names the scope.                                                                                                                                                                                                                                                                                                                                       | Use a key with `write:usage`.                                                                                                                       |
| `FEATURE_NOT_ENABLED`                                      | `403`        | Signal voiding is not turned on for your organization yet, as seen while it is being enabled.                                                                                                                                                                                                                                                                                                                       | Try again shortly, or ask your Paid contact.                                                                                                        |
| `SIGNAL_ORGANIZATION_MISMATCH`, `SIGNAL_CUSTOMER_MISMATCH` | `409`        | The signal's billing records belong to a different organization or customer than the request. Nothing is voided.                                                                                                                                                                                                                                                                                                    | Ask your Paid contact.                                                                                                                              |
| `CUSTOMER_KEY_FORBIDDEN`                                   | `403`        | The request used a customer API key, which is scoped to one customer and cannot call this endpoint.                                                                                                                                                                                                                                                                                                                 | Use an organization API key.                                                                                                                        |
| `API_KEY_REQUIRED`                                         | `403`        | The request did not use an API key, for example a dashboard session.                                                                                                                                                                                                                                                                                                                                                | Send an API key.                                                                                                                                    |
| `SIGNAL_LOOKUP_FAILED`                                     | `502`        | Paid could not read the signal, so it sent no void. An earlier void of the signal may already have gone through.                                                                                                                                                                                                                                                                                                    | Send the same request again.                                                                                                                        |
| `BILLING_SERVICE_UNAVAILABLE`                              | `502`, `503` | Paid could not finish the void. It may or may not have been made.                                                                                                                                                                                                                                                                                                                                                   | Send the same request again. A repeat is safe and tells you which. If it keeps failing, ask your Paid contact.                                      |
| `RATE_LIMIT_EXCEEDED`                                      | `429`        | You have hit the API rate limit.                                                                                                                                                                                                                                                                                                                                                                                    | Wait the number of seconds in the `Retry-After` header, then retry.                                                                                 |

### Signals counted by an activation charge

When an order is activated, a usage line priced in prepaid credits from a meter
charges the usage that came in before activation once. The charge is a single
spend that names no signal. A void of one of those signals is refused with
`COVERED_BY_ACTIVATION_CHARGE`, because there is no spend of that signal's to
give back.

For example, a customer's order is activated on 15 November, and its billing
period runs 1 to 30 November. A usage line on it is priced in prepaid credits
from a meter that sums `tokens`. A signal dated 10 November with 500 tokens
came in before activation. At activation, Paid charged the customer's credits
once for all the usage of that period so far.

* Voiding the 10 November signal is refused. Nothing is voided and no credits
  are returned. To correct the customer, grant credits for the 500 tokens or
  issue a credit note.
* A signal dated 20 November, sent after activation, is charged on its own and
  voids as usual. One sent within about a minute of the activation charge may
  still be refused.
* A signal dated in October, before the period the charge covered, voids as
  usual.
* If the order has been deleted, a void of such a signal is accepted and returns no credits.

### Settled and invoiced periods

Once a billing period is settled, no signal counted in it can be voided.
A period settles when Paid closes it for invoicing. After that, the
correction is a credit note, never a void.

The period that decides is the one the signal was billed in, not the date on the
signal. Usage that arrived late and was carried forward into an open period is
still voidable there.

A line priced from a meter that bills once per period is the exception. This
covers any such meter: count, sum, maximum, minimum, average, latest value or
unique count. For these lines, the period that holds the signal's own time also
decides, whether or not that meter counts the signal. If the customer has such
a line and that period is settled, the void is refused with `PERIOD_CLOSED`.

## Voiding signals that feed a workflow

A **workflow** groups the signals of one run, such as the steps of one agent
task. A workflow meter charges it once, when it closes, by writing one charge
signal.

* **A step of an open workflow.** An open workflow adds no refusal of its own.
  The other checks in this guide still apply, for example the void limit, a
  settled period and `COST_PLUS_LINE`. When a void goes through, the charge,
  when it is written, leaves the step out.
* **A step of a closed workflow.** The void is refused with `WORKFLOW_CLOSED`.
  Voiding a step never voids the workflow's charge, and Paid does not offer a
  way to void a step together with its charge.
* **The charge itself.** You can void a workflow's charge directly, like any
  signal. A charge carries `_derived` in its `data`. The charge is voided,
  the workflow's steps stay live, and the workflow is not charged again. The
  same checks apply as for any signal, for example a settled period.
* **Late steps.** Steps that arrive after the workflow closed, or after its
  charge was written, are handled as they were before voiding. Voiding adds no
  new refusal when you send signals.

Voiding does not void other signals for you, and it does not rebuild a charge
from the steps that remain.

## Related

* [Void a signal](/api-reference/api-reference/signals/void-signal) and
  [Void a signal by idempotency key](/api-reference/api-reference/signals/void-signal-by-idempotency-key)
* [List signals](/api-reference/api-reference/signals/list-signals) and
  [Create signals in bulk](/api-reference/api-reference/signals/create-signals)
* [Send signals with a customer API key](/documentation/signals/send-signals-with-a-customer-api-key)