---
title: "Errors"
description: "Read one error format across the whole API, branch on a small set of error types, and know from the status code whether anything was processed before it failed."
canonical_url: "https://twominutebilling.com/help/developers/api-essentials/errors"
---
# Errors

> Read one error format across the whole API, branch on a small set of error types, and know from the status code whether anything was processed before it failed.

Every error has the same shape:

```json
{
  "error": {
    "type": "invalid_request_error",
    "message": "currency must be a 3-letter ISO code"
  }
}
```

Branch on `type` and the HTTP status. Write `message` to your logs, but never branch on it — we
word it for humans and we will reword it.

Some errors carry an extra `code` naming the specific cause, such as `requires_action` on a
payment that needs 3-D Secure.

## Branch on the error type

<table>
<thead>
  <tr>
    <th>
      Type
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      What it means
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        invalid_request_error
      </code>
    </td>
    
    <td>
      400
    </td>
    
    <td>
      Your request is malformed, or asks for something the current state does not allow. Retrying it unchanged will not help.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        authentication_error
      </code>
    </td>
    
    <td>
      401
    </td>
    
    <td>
      Your API key is missing, malformed, revoked, or in the wrong mode.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        permission_error
      </code>
    </td>
    
    <td>
      403
    </td>
    
    <td>
      You authenticated, but you may not do this.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        conflict_error
      </code>
    </td>
    
    <td>
      409
    </td>
    
    <td>
      Your request conflicts with the resource's current state — a duplicate id, or an already-voided invoice.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        idempotency_error
      </code>
    </td>
    
    <td>
      409
    </td>
    
    <td>
      You reused an <code>
        Idempotency-Key
      </code>
      
       with a different body, or named a request tmbilling cannot safely replay. See <a href="https://twominutebilling.com/help/developers/api-essentials/idempotency">
        Idempotency
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        rate_limit_error
      </code>
    </td>
    
    <td>
      429
    </td>
    
    <td>
      You are over the per-key limit. tmbilling processed nothing. See <a href="https://twominutebilling.com/help/developers/api-essentials/rate-limits">
        Rate limits
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        payment_error
      </code>
    </td>
    
    <td>
      400 / 402
    </td>
    
    <td>
      The gateway refused, or wants your buyer to authenticate. A <code>
        402
      </code>
      
       carries a <code>
        payment_intent
      </code>
      
       and a <code>
        next_action
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        api_error
      </code>
    </td>
    
    <td>
      500
    </td>
    
    <td>
      Something failed on our side.
    </td>
  </tr>
</tbody>
</table>

## Decide whether to retry

The status tells you what your client should do next:

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      What to do
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        400
      </code>
      
      , <code>
        403
      </code>
      
      , <code>
        409
      </code>
    </td>
    
    <td>
      Don't retry. The request fails the same way every time — fix the input or the state first. A <code>
        409
      </code>
      
       on a duplicate id means the resource exists, so fetch it.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401
      </code>
    </td>
    
    <td>
      Don't retry blindly. Check the key and its mode first. A retry loop on a revoked key is noise.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        429
      </code>
    </td>
    
    <td>
      Retry after the delay. tmbilling processed nothing, so wait for <code>
        Retry-After
      </code>
      
       and send the same request again.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        500
      </code>
      
       or a network timeout
    </td>
    
    <td>
      Retry with the <strong>
        same
      </strong>
      
       <code>
        Idempotency-Key
      </code>
      
      . The outcome is unknown, and this is exactly what idempotency exists for.
    </td>
  </tr>
</tbody>
</table>

## Treat a declined card as billing state, not an error

A declined card is not a bug in your request. Your API call succeeded. The charge failed.

You see it as a `payment_error` — with `402` and a `next_action` when your buyer only has to
authenticate — and, more usefully, as state on the objects. The invoice moves to `payment_due`,
a transaction row records the attempt with the gateway's own error code, and dunning starts.
tmbilling drops no gateway refusal. Each one is a row you can query.

See [Failed payments and dunning](https://twominutebilling.com/help/developers/recipes/failed-payments) for what happens next and what you configure.

## Read validation errors

A body that fails validation returns `400` with a message naming the field.

An unknown query parameter also returns `400` rather than being ignored. So a typo in a filter
name tells you, instead of handing back an unfiltered list that looks plausible.

## Related

- [Idempotency](https://twominutebilling.com/help/developers/api-essentials/idempotency)
- [Failed payments and dunning](https://twominutebilling.com/help/developers/recipes/failed-payments)


## Sitemap

See the full [sitemap](https://twominutebilling.com/sitemap.md) for all pages.
