---
title: "Error Handling"
description: "Catch API failures, branch on the reason code, and handle refused bookings."
canonical_url: "https://requiply.com/docs/en/developers/typescript-sdk/error-handling"
---
# Error Handling

> Catch API failures, branch on the reason code, and handle refused bookings.

Failed API responses throw `RequiplyError`:

```ts
import { RequiplyError } from '@requiply/api-sdk';

try {
  await requiply.sales.create({ lines: [{ saleItemId, quantity: 1 }], paymentMethod: 'liqpay' });
} catch (error) {
  if (error instanceof RequiplyError && error.reason === 'client_email_required') {
    console.error('LiqPay needs a client with an email');
  } else {
    throw error;
  }
}
```

## Error properties

<table>
<thead>
  <tr>
    <th>
      Property
    </th>
    
    <th>
      What it holds
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      HTTP response status, for example <code>
        400
      </code>
      
      , <code>
        403
      </code>
      
      , <code>
        404
      </code>
      
      , <code>
        409
      </code>
      
      , or <code>
        429
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        message
      </code>
    </td>
    
    <td>
      The API's human-readable message, with an HTTP-status fallback. Show it to people, but do not branch on its text.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        reason
      </code>
    </td>
    
    <td>
      Machine-readable reason, when the API gives one, for example <code>
        feature_disabled
      </code>
      
      . Branch on this.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        data
      </code>
    </td>
    
    <td>
      Extra fields the API sent with the error, including <code>
        reason
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        details
      </code>
    </td>
    
    <td>
      The parsed response body, or the raw response text when it is not JSON.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        retryAfter
      </code>
    </td>
    
    <td>
      Seconds to wait, from the <code>
        Retry-After
      </code>
      
       header of a <code>
        429
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

## Error reasons

`reason` is set by booking requests from publishable keys, by the [Sales](https://requiply.com/docs/en/developers/typescript-sdk/sales) endpoints, and by sale lines on quotes and bookings:

<table>
<thead>
  <tr>
    <th>
      <code>
        reason
      </code>
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        request_only
      </code>
    </td>
    
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      A publishable key called <code>
        bookings.create()
      </code>
      
       without <code>
        asRequest: true
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        feature_disabled
      </code>
    </td>
    
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      <strong>
        Enable sales
      </strong>
      
       is off, or the organization is not on the Advanced plan.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        selling_disabled
      </code>
    </td>
    
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      A publishable key asked for goods for sale while the organization does not <a href="https://requiply.com/docs/en/developers/typescript-sdk/sales#with-a-publishable-key">
        sell to customers
      </a>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        out_of_stock
      </code>
    </td>
    
    <td>
      <code>
        409
      </code>
    </td>
    
    <td>
      A publishable key asked for more units than are on the shelf. <code>
        data.saleItemId
      </code>
      
       names the product, <code>
        data.availableQuantity
      </code>
      
       says how many are left.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        invalid_lines
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      A line has no product or a quantity that is not a whole number above zero.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        duplicate_line
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      The same product appears in two lines.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sale_item_not_found
      </code>
    </td>
    
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      A product does not exist in this organization.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sale_item_archived
      </code>
    </td>
    
    <td>
      <code>
        409
      </code>
    </td>
    
    <td>
      A product is archived and cannot be sold.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        invalid_payment
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      <code>
        paymentMethod
      </code>
      
       is not a known method.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        payment_method_unavailable
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      The organization has no active integration for that payment method.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        client_email_required
      </code>
    </td>
    
    <td>
      <code>
        400
      </code>
    </td>
    
    <td>
      LiqPay sends the invoice by email, so the sale needs a client with an email.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        sale_not_found
      </code>
    </td>
    
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      The sale does not exist.
    </td>
  </tr>
</tbody>
</table>

New reasons can appear, so always keep a fallback branch. Most other errors, such as a missing field or an unknown booking, carry only `status` and `message`.

## Rate limits

Each key can make 100 requests per minute. Further requests throw a `RequiplyError` with `status` `429` and `retryAfter` in seconds. In browsers `retryAfter` is missing, because the API does not expose the header across origins; wait a few seconds before retrying.

## Refused bookings are not errors

`bookings.create()` does not throw when the organization cannot accept a booking, for example because the items are taken for those dates or the pickup is outside working hours. It resolves with `success: false`, a `message`, and sometimes a `reason`. See [Bookings](https://requiply.com/docs/en/developers/typescript-sdk/bookings#handling-the-response).

## Errors that are not `RequiplyError`

Constructor validation and missing runtime APIs throw `TypeError`: a missing or empty `apiKey`, no available `fetch`, or a secret key used in the browser. Network failures and aborted requests reject with the error your `fetch` implementation throws.


## Sitemap

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