View Categories

GraphQL API Overview & Authentication

6 min read

The rental extension adds a GraphQL API to Magento’s own /graphql endpoint. It covers the parts of a rental business that the standard Magento schema has no concept of: what a product costs for a particular set of dates, whether it is free on those dates, the bookings themselves, the serial-numbered units that go out and come back, the orders due to go out this morning, and the maintenance tickets that take a unit off the fleet.

It is available from version 1.2.198 of the rental extension (and 1.2.48 of the Maintenance add-on for the ticket fields). There is nothing to install or enable beyond updating the extension — the schema merges into the endpoint you already have.

The endpoint #

Everything is served from the one Magento endpoint, https://your-store.com/graphql, over POST with Content-Type: application/json. If GraphQL already works on your store, the rental fields are there too.

curl -X POST https://your-store.com/graphql 
  -H 'Content-Type: application/json' 
  -d '{"query":"{ rentalProductAvailability(input: {sku: "CANON-R6", start_date: "2026-10-03 00:00:00", end_date: "2026-10-05 23:59:00", qty: 1}) { is_available available_qty } }"}'

Two kinds of field #

The schema is split by who is allowed to call it, and it matters which half you are in because it decides what token you need.

Storefront fieldsBack-office fields
rentalProductAvailability
rentalProductCalendar
rentalProductPrice
rentalProductPriceList
addRentalProductsToCart
rental_information on a product
rentalSerialNumbers and its mutations
rentalReservations and its mutations
rentalOrders
sendRentalSerials, returnRentalSerials
rentalMaintenanceTickets and its mutations
Open to anyone who can reach the storefront, the same as products or cart. No token needed.Require a bearer token for an admin user or an integration whose role holds the matching ACL resource.

This is worth being deliberate about. A headless storefront only ever needs the left-hand column, and it should not be shipped with a back-office token embedded in it. The right-hand column is for your own back-office tooling, a warehouse scanner app, or an integration with another system.

Authentication #

An admin token #

Quickest for testing. Exchange an admin username and password for a token, then send it as a bearer token. The token is valid for the lifetime configured under Stores > Configuration > Services > OAuth > Access Token Expiration — four hours by default.

TOKEN=$(curl -s -X POST https://your-store.com/rest/V1/integration/admin/token 
  -H 'Content-Type: application/json' 
  -d '{"username":"your-admin","password":"your-password"}' | tr -d '"')

curl -X POST https://your-store.com/graphql 
  -H 'Content-Type: application/json' 
  -H "Authorization: Bearer $TOKEN" 
  -d '{"query":"{ rentalReservations(pageSize: 5) { total_count items { id product_id start_date end_date state } } }"}'

If two-factor authentication is enabled on your admin (it is by default in Magento 2.4), the token endpoint will refuse a plain username and password. For anything other than a quick test, use an integration instead.

An integration token (recommended) #

An integration is a non-human account with its own role, which is what you want for any system that is going to keep calling the API. Go to System > Extensions > Integrations > Add New Integration, give it a name, and on the API tab grant only the resources it actually needs (see the table below). Save, then Activate it — the access token is shown once at that point.

curl -X POST https://your-store.com/graphql 
  -H 'Content-Type: application/json' 
  -H "Authorization: Bearer your-integration-access-token" 
  -d '{"query":"{ rentalSerialNumbers(pageSize: 5) { total_count } }"}'

A customer token #

The storefront fields do not need a token at all, but a logged-in customer’s token is still useful for two of them: addRentalProductsToCart adds to that customer’s cart rather than a guest cart, and rentalProductPrice uses their customer group for group-specific pricing. Get one with Magento’s standard generateCustomerToken mutation and send it the same way.

Which permission each field needs #

The back-office fields are gated on the same ACL resources as the corresponding admin screens, so a role that can already do the job in the admin can do it through the API.

FieldACL resourceWhere that is in the admin role tree
All rentalSerialNumber* fieldsSalesIgniter_Rental::serialRental > Reports > serial
All rentalReservation* fieldsSalesIgniter_Rental::manualeditRental > General > Manually Reserve Inventory
rentalOrdersSalesIgniter_Rental::rentalcalRental > Rental Calendar by start/end or order
sendRentalSerialsSalesIgniter_Rental::sendRental > Send and Return > Ship Items
returnRentalSerialsSalesIgniter_Rental::returnRental > Send and Return > Return Items
All rentalMaintenanceTicket* fieldsSalesIgniter_Maintenance::ticketsRental > Maintenance > Tickets

Two storefront fields have a back-office capability hidden inside them. rentalProductAvailability and rentalProductCalendar both accept exclude_reservation_ids and exclude_order_id, which answer “what would availability look like if these bookings did not exist” — that is how an order-edit screen stops a booking from blocking itself. Supplying either of them requires SalesIgniter_Rental::manualedit; without it the query is refused rather than quietly ignoring them.

Dates #

  • Every date in and out of this API is Y-m-d H:i:s2026-10-03 09:00:00. Fields that are naturally whole days (the calendar window, a serial’s acquisition date) also accept Y-m-d, which is read as midnight.
  • They are in the store’s configured timezone, not UTC, and not the caller’s. This matches what the rental tables hold and what the admin screens show.
  • Anything else is rejected with an error naming the field. Nothing is guessed — a format like 03/04/2026 is ambiguous between March and April, and an API that guessed would be wrong about half the time in a way nobody would notice until a booking came back on the wrong day.
  • A field that has no date (a booking with no end date, a serial never marked as acquired) returns null, not a zero date.

Rental dates versus turnover dates #

Every booking carries two date ranges and it is important not to confuse them.

  • start_date and end_date are the customer’s dates — what they picked, what the order confirmation says, what a picking list should print.
  • start_date_with_turnover and end_date_with_turnover add the product’s turnover buffers either side. These are the dates that actually block the calendar. If a camera needs four hours to be cleaned and checked between hires, its turnover-inclusive range is four hours longer than the customer’s, and that is the range availability is computed against.

When you create a booking you supply the customer’s dates and the turnover ones are worked out for you from the product’s configuration. Pass not_use_turnover: true to skip that, which is occasionally what you want for a manual hold that is not a real hire.

Errors #

Errors come back in the standard GraphQL shape, in an errors array alongside whatever data could still be resolved. Each one carries a category you can branch on.

CategoryWhat it means
graphql-authorizationYour token is missing the ACL resource for this field, or you sent no token to a back-office field.
graphql-inputThe arguments are wrong — a bad date, an end before a start, a quantity below one, a SKU that is not a rental product, a calendar window over 400 days, a page past the end of the results.
graphql-no-such-entityThe SKU, booking, serial or ticket does not exist.
graphql-already-existsCreating something that is already there — most often a serial number that product already has.

A booking that cannot be made because the dates are not free is not an exception in the availability queries — rentalProductAvailability returns is_available: false with a structured error { code message }, because “no” is a perfectly good answer to a question. It is an error when you try to write a booking onto unavailable dates, unless you pass force: true.

Paging, filtering and sorting #

Every list field works the same way and follows Magento’s own conventions, so a client written against products will feel familiar.

{
  rentalSerialNumbers(
    filter: { status: { eq: "available" }, serial_number: { match: "CANON" } }
    sort: { serial_number: ASC }
    pageSize: 50
    currentPage: 1
  ) {
    total_count
    items { id serial_number status }
    page_info { page_size current_page total_pages }
  }
}
  • pageSize defaults to 20 and is capped at 200.
  • currentPage is 1-based. Asking for a page past the last one is an error rather than an empty list, so a paging loop with an off-by-one fails loudly instead of spinning.
  • Filter fields take { eq: } or { in: [] }, text fields take { match: } for a substring, and date fields take { from:, to: }. Several filters combine with AND.
  • sort takes exactly one field. Supplying two is an error rather than one of them being silently ignored.

Where to go next #

  • Building a headless storefront? Start with Booking from a Headless Storefront, then Availability & Calendar and Pricing.
  • Integrating a warehouse or scanner app? Sending & Returning Rentals and Serial Numbers.
  • Syncing with another system? Reservations and Retrieving Orders by Rental Date.