View Categories

Launching soon on the Shopify App StoreJoin the waitlist

API overview: keys, versions, limits and errors

10 min read

The Rentals & Bookings app and its add-on apps have an API, so your own systems can work with your rentals: a booking website, a warehouse scanner, an accounting export, or a script that runs every night. This page explains how to get a key, where to send requests and what the answers look like. The reference pages list every operation.

Three ways in #

APIUse it forKey
REST API v1Plain web addresses and JSON. Easy from a script, a spreadsheet tool or anything that can send an HTTP request.Secret API key (sirb_…)
GraphQL APIOne address where you ask for exactly the fields you need, several things in one request, with a typed schema and a browser explorer.Secret API key (sirb_…)
Storefront APISelling rentals on a Hydrogen or custom storefront: the calendar, the price, a hold on the dates and the cart line. REST and GraphQL.Publishable key (sirpk_…)

REST and GraphQL do the same things, with the same keys, rules and error codes. Pick the style your developer prefers. For automations without code, use Shopify Flow.

The reference pages:

Which apps the API covers #

One key works for every Sales Igniter app on your store. Each operation needs its own app: an operation of an app your store doesn’t have answers 403 with the code app_not_installed, and data.app names the app it needs.

AppWhat the API reachesdata.app
Rentals & BookingsRental products, availability, prices, bookings, orders, the Send & Return desk, serial numbers, stock by location, kits, delivery & pickup, balances, calendar sync, rental settingscore
Rental ContractsSigned agreements, their PDFs, the audit trail, verify and resendcontracts
Security DepositsAn order’s deposits, refunds, automatic refunds, product deposit rulesdeposits
Custom Fields (Fields, Waiver & ID)Answers on orders, fees, the damage waiver line, ID check status, field groups, import and exportfields
SI Request a Quote, Hide PriceQuotes: list, create, change, send, convert, notes, messages, PDFquotes

The reference pages mark every endpoint that needs an add-on app. Rental Contracts and Security Deposits work alongside Rentals & Bookings. Custom Fields and SI Request a Quote also work on their own.

Get an API key #

  1. In Rentals & Bookings, open Settings › API. On a store that has only Custom Fields or only SI Request a Quote, open API access in that app’s menu instead. They are the same keys.
  2. Under Create a key, enter a Key name (the system or person that will use it). Tick Allow changes (write) if it needs to change anything. Without it, the key can only read.
  3. Click Create key and copy the key. It is shown once.

A secret key starts with sirb_. Treat it like a password: keep it on a server, never in a website’s code or a mobile app. The list shows each key’s name, the start of the key, its access and when it was last used. Revoke stops a key at once. A store can have up to 20 active keys.

To replace a key without downtime, make a new key, put it in your system, then revoke the old one. Your system can do this itself through the API (POST /api/v1/api-keys or createRentalApiKey). A key can make keys, but never one with more access than its own, and a read key can’t make or revoke keys.

Storefronts use a different, public key (sirpk_…) made under Settings › API › Headless storefronts. See Headless storefronts: the storefront API.

Addresses #

Settings › API (and the API access page) shows your addresses, for example “REST API: https://…/api/v1 · GraphQL API: https://…/graphql/2026-10”. The host is the same for every API. These pages write it as YOUR-API-ADDRESS.

APIAddress
REST API v1https://YOUR-API-ADDRESS/api/v1/…
GraphQL APIhttps://YOUR-API-ADDRESS/graphql/2026-10
GraphQL explorerhttps://YOUR-API-ADDRESS/graphiql (Open the GraphQL explorer on Settings › API)
Storefront API, RESThttps://YOUR-API-ADDRESS/sf/v1/your-store/…
Storefront API, GraphQLhttps://YOUR-API-ADDRESS/graphql/2026-10/storefront/your-store

your-store is your store’s myshopify name, as your-store or your-store.myshopify.com. Requests and answers are JSON over HTTPS, except the CSV and PDF downloads the reference points out.

Authentication and access #

Send the secret key with every request, in the Authorization header:

curl -s "https://YOUR-API-ADDRESS/api/v1/bookings?per_page=5" \
  -H "Authorization: Bearer sirb_your_secret_key"

X-Api-Key: sirb_your_secret_key works too. Everything a key reads or changes belongs to its own store.

KeyRESTGraphQL
Read (the default)GET requestsQueries
Read and write (Allow changes (write))Every methodQueries and mutations

A missing, unknown or revoked key answers 401 with the code unauthorized. A read key that tries to change something answers 403 with the code forbidden and data.required_scope: "write".

To try the GraphQL API without writing code, click Open the GraphQL explorer on Settings › API, paste a secret key in the explorer’s Headers pane as {"Authorization": "Bearer sirb_your_secret_key"} and run a query. Its Docs pane lists every type and field.

Versions #

  • REST is version 1: /api/v1/…. New fields can appear in answers, so ignore fields you don’t know.
  • GraphQL has dated versions in the address. The current version is 2026-10, the only one so far. Changes that could break your code (a removed or renamed field, a new required argument, a changed meaning) only ship in a new dated version. New fields, types, operations, optional arguments and enum values can appear in the current version. A version stays available for at least 12 months after the next one ships. An unknown version answers 404 with the code unknown_version and the list of versions.

Rate limits #

  • Each secret key may make 120 requests a minute, REST and GraphQL together. Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining.
  • Over the limit, the answer is 429 with the code rate_limited, a Retry-After header (seconds) and data.retry_after. Wait that long, then try again.
  • A GraphQL request is also limited in size: at most 12 levels deep, a complexity of 6,000 (each field costs 1; a list costs 2 plus its page size times what each item asks for), 20,000 bytes of query text, and one operation per request.
  • The storefront API has its own limits per store and shopper: see Headless storefronts.

Pagination #

REST lists take page (from 1) and per_page (default 20, at most 200). They answer a JSON array, with the number of matches in the X-Total header and the number of pages in X-Total-Pages:

curl -s -D - -o /dev/null "https://YOUR-API-ADDRESS/api/v1/bookings?status=reserved&page=2&per_page=50" \
  -H "Authorization: Bearer sirb_your_secret_key"
# HTTP/1.1 200 OK
# X-Total: 132
# X-Total-Pages: 3

A per_page above 200 answers 400 with the code invalid_param.

GraphQL lists are connections: ask for first (or last) items, then pass the last endCursor as after for the next page. Each list has nodes (or edges { cursor node }), pageInfo { hasNextPage endCursor } and totalCount. The page size is 20 by default and at most 200.

query NextPage($after: String) {
  rentalBookings(first: 50, after: $after, filter: {statuses: [RESERVED]}) {
    totalCount
    pageInfo { hasNextPage endCursor }
    nodes { id orderName startDate endDate quantity }
  }
}

A few short lists come whole: API keys, locations, field groups and the products with their own deposit rule. The Custom Fields import and export lists take first (20 by default, at most 50).

Ids, dates and money #

  • Ids. REST uses numbers: Shopify’s ids for products, variants, orders, customers and locations, and the app’s own ids for bookings, serial numbers, deposits and so on. Prefer variant_id; product_id works when the product has one rent variant. GraphQL uses global ids, such as gid://shopify/ProductVariant/44001 for Shopify’s objects and gid://sirentals/Booking/42 for the app’s. Every GraphQL ID argument also takes the bare number.
  • Dates are in your store’s time zone (GraphQL: rentalShop { timezone }). Send 2026-12-01, 2026-12-01 09:00 or 2026-12-01T09:00. Answers use 2026-12-01 09:00:00; the times records were made or changed (created_at, last_used_at …) are ISO 8601 with a time zone.
  • Rental ranges are built the way the cart builds them: a date-only rental from the 1st to the 3rd ends at midnight on the 3rd (2 days), and a rental whose start and end are the same day lasts that day. Answers also give the dates with the product’s turnaround time added (start_with_buffer, end_with_buffer): those are the dates that block the calendar.
  • Money is in the store currency. Prices and quotes are numbers (450); balances, deposits, fees and quotes of SI Request a Quote are decimal strings ("150.00").
  • Enum values are lower case in REST (reserved) and upper case in GraphQL (RESERVED).
  • True and false in a REST address: true / false, 1 / 0 or yes / no.

Errors #

Every REST error has the same shape: a code to branch on, a message written for people (show it to your staff), and data with the HTTP status and any details.

curl -s "https://YOUR-API-ADDRESS/api/v1/bookings?per_page=500" \
  -H "Authorization: Bearer sirb_your_secret_key"
# 400
{
  "code": "rentals_rest_invalid_param",
  "message": "per_page must be at most 200.",
  "data": {"params": {"per_page": "per_page must be at most 200."}, "status": 400}
}

GraphQL uses the same codes without the rentals_rest_ prefix, in two places:

  • A mutation that can’t do what you asked (not enough stock, a closed day, a bad amount) answers normally, with the reason in userErrors { code message field data httpStatus }. httpStatus is the status REST would answer.
  • A request that can’t run answers errors[], each with extensions { code http_status data }: a query that doesn’t match the schema (invalid_query), a mutation with a read key (forbidden), an app the store doesn’t have (app_not_installed), a query that is too deep or too complex. The HTTP status is still 200. Only a request refused before it runs has its own HTTP status: 401 bad key, 404 unknown version, 405 a mutation sent with GET, 413 query too long, 429 rate limit, 400 not one JSON operation.
mutation {
  createRentalBooking(input: {variantId: "44001", startDate: "2026-12-01", endDate: "2026-12-03", quantity: 3, block: true}) {
    booking { id }
    userErrors { code message field data httpStatus }
  }
}
# {"data": {"createRentalBooking": {"booking": null, "userErrors": [{"code": "not_available",
#   "message": "Only 1 unit(s) available for those dates.", "field": null, "data": {"available": 1}, "httpStatus": 409}]}}}

The codes you will meet most:

CodeStatusWhen
unauthorized401No key, or an unknown or revoked key.
forbidden403A read key tried to change something (data.required_scope).
app_not_installed403The operation belongs to an app the store doesn’t have (data.app).
rate_limited429Too many requests for this key (Retry-After, data.retry_after).
invalid_param400A parameter is missing, the wrong type or out of range (data.params names each one).
invalid400A value the operation can’t accept (data.field names it).
invalid_dates400A date doesn’t parse, or the end isn’t after the start.
not_found404No such booking, serial number, agreement, key … in this store.
no_route404REST only: no such path, or the wrong method for it.
not_available409Not enough units for those dates (data.available).
closed_day, start_too_early, min_length, max_length, too_far_ahead, fixed_length, fixed_date, guests409The dates break one of the store’s booking rules.
batch_refused409One item of an all-or-nothing batch was refused, so nothing was recorded (data.failures).
shopify_error, draft_order_failed502Shopify refused a call the app made for you.
server_error500Something went wrong on our side. Try again.
invalid_query, access_denied, max_depth_exceeded, max_complexity_exceeded, query_too_long, introspection_disabled, unknown_version, method_not_allowed, invalid_requestGraphQL only: the request itself was refused.

Each reference page lists the codes of its own operations.

Webhooks and events #

Rentals & Bookings sends its events to Shopify Flow: Rental booked, checked out, due back tomorrow, overdue, conflict found, balance due and more. To send them to your own system, build a Flow workflow with one of those triggers and Flow’s Send HTTP request action. See Shopify Flow, API and headless storefronts.

SI Request a Quote can post a signed JSON message to your own address for every quote event. Set it up under Settings › Slack, Teams and webhooks: enter Your webhook address, then click Show the signing secret and copy it into your system.

Eventsquote.requested, quote.sent, quote.changes_requested, quote.accepted, quote.ordered, quote.declined, quote.expired, quote.rejected, quote.cancelled, quote.message
HeadersContent-Type: application/json, User-Agent: SI-Request-a-Quote/1, X-SIQuotes-Event: quote.accepted, X-SIQuotes-Signature: sha256=<hex HMAC-SHA256 of the raw body>
Body{"event", "shop", "occurredAt", "quote": {id, number, publicId, status, statusLabel, source, customer, total, currency, expiresAt, createdAt, updatedAt, orderId, orderName, draftOrderId, tags, …}}
RetriesAnswer with a 2xx status within 10 seconds. A failed delivery is tried twice more, after 1 and then 5 minutes.

Check the signature on the raw body, before you parse the JSON:

import crypto from 'node:crypto';

// rawBody: the request body exactly as it arrived (a Buffer); secret: your signing secret
export function isFromQuotes(rawBody, signatureHeader, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const a = Buffer.from(signatureHeader || '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Make a new secret on the same screen replaces the secret at once; update your system right after.