ResytechResytech Docs
Plugins

OCTO API reference

Authentication, endpoints, booking lifecycle, and error contract for resellers integrating against Resytech's OCTO supplier API.

This page is for the developer on the reseller side — an OTA, marketplace, or channel manager connecting to a Resytech operator through OCTO, the open connectivity standard for tours, activities, and attractions. Operator-side setup lives on the plugin page.

Resytech implements the OCTO core specification — supplier, products, availability, and the full booking lifecycle. If you have an existing OCTO client, point it at the endpoint below with your token and it should work unmodified.

Connection

Endpointhttps://octo.resytech.com (no trailing slash; also returned by GET /supplier)
AuthenticationAuthorization: Bearer {token} — the operator issues you an rpat_… token from their dashboard
Content typeapplication/json
CapabilitiesSend Octo-Capabilities if you like; no optional capabilities are implemented yet, so the response header always echoes the empty set. Core fields only.

Your token is scoped to one operator location — the supplier — and carries scopes granted by the operator:

  • octo:read — supplier, products, availability
  • octo:book — the entire /bookings surface

All datetimes are ISO 8601. Availability times carry the local UTC offset of the supplier's timezone (e.g. 2026-09-15T14:00:00-04:00); the product's timeZone field is the IANA id. utc… fields are UTC with a Z suffix.

Rate limit: 120 requests per minute per token. Exceeding it returns 429; back off and retry.

Access is controlled by the operator: they can pause your account or disable the plugin at any time, which makes every request fail with 403 FORBIDDEN immediately. Treat a sudden 403 as "contact the operator", not as a retryable error.

Errors

Failures return the OCTO envelope:

{ "error": "INVALID_PRODUCT_ID", "errorMessage": "The productId was not found", "productId": "…" }
CodeHTTPWhen
UNAUTHORIZED401No bearer token sent
FORBIDDEN403Invalid/revoked token, paused reseller, plugin disabled, or missing scope
INVALID_PRODUCT_ID / INVALID_OPTION_ID / INVALID_UNIT_ID / INVALID_AVAILABILITY_ID / INVALID_BOOKING_UUID400The referenced id doesn't exist or isn't yours
BAD_REQUEST400Malformed body, bad dates, oversized range
UNPROCESSABLE_ENTITY400Valid request that can't be honored — departure sold out, party too large, hold expired
TOO_MANY_REQUESTS429Rate limit
INTERNAL_SERVER_ERROR500Our fault — safe to retry with backoff

Products

GET /supplier
GET /products
GET /products/{id}

A product is one bookable activity. Mapping notes:

  • availabilityType is always START_TIME; availabilityRequired is always true — you must obtain an availabilityId before booking.
  • Options are the activity's durations (a 60-minute and a 120-minute variant are two options). One option is flagged default.
  • Units are guest types. Per-person products list the operator's demographics (ADULT, CHILD, …, with the demographic's uuid as unitId); flat-priced products expose a single unit with id guest.
  • availabilityLocalStartTimes lists the schedule's possible local departure times for that option; the availability endpoints are the source of truth for what's actually open.
  • restrictions.minUnits/maxUnits on the option reflect the operator's per-booking guest minimum/limit.
  • deliveryMethods is ["VOUCHER"] with redemptionMethod: "MANIFEST" — the traveler is on the operator's manifest; there are no per-ticket barcodes.

Availability

POST /availability/calendar   — one object per day, for range views (≤ 92 days/request)
POST /availability            — one object per departure; REQUIRED before booking (≤ 31 days/request)

Both take { productId, optionId } plus either localDate, or localDateStart + localDateEnd (yyyy-MM-dd). The availability check also accepts availabilityIds to re-check specific departures.

Departure objects:

FieldMeaning
idThe availabilityId you pass to book. Deterministic per departure, but always re-check before booking — it's revalidated server-side either way.
localDateTimeStart / localDateTimeEndDeparture window with local offset
statusAVAILABLE, LIMITED (≤ 3 spots), SOLD_OUT, or CLOSED (operator blackout)
vacanciesRemaining spots when the product tracks them; null when untracked
maxUnitsMost units a single booking can carry right now
utcCutoffAtBooking deadline (the departure instant — sales cutoffs are already applied to status)

Calendar responses are cached for up to 60 seconds — fine for range views, which is why booking requires the uncached availability check.

Booking lifecycle

Reserve → confirm, with an expiring hold in between:

POST /bookings                      octo:book — reserve (ON_HOLD)
POST /bookings/{uuid}/confirm      octo:book — ON_HOLD → CONFIRMED
POST /bookings/{uuid}/extend       octo:book — push the hold deadline
POST /bookings/{uuid}/cancel      octo:book — cancel a hold or a confirmed booking
GET  /bookings/{uuid}              octo:book
GET  /bookings?resellerReference=&localDate=   octo:book — your bookings (newest 200)

Reserve

{
  "uuid": "9be6f9c7-…",
  "productId": "…", "optionId": "…",
  "availabilityId": "…",
  "unitItems": [ { "unitId": "…adult uuid…" }, { "unitId": "…adult uuid…" }, { "unitId": "…child uuid…" } ],
  "expirationMinutes": 30,
  "resellerReference": "YOUR-ORDER-123",
  "contact": { "fullName": "…", "emailAddress": "…", "phoneNumber": "…" }
}
  • uuid is your idempotency key. Retrying with the same uuid returns the reservation's current state and never double-books. Always send one.
  • One unitItems entry per traveler. unitId is a unit id from the product (guest for single-unit products).
  • expirationMinutes is clamped to 5–1440; default 30. The response's utcExpiresAt is authoritative.
  • contact is optional at reserve — send it at confirm if you don't have it yet.
  • A successful reserve blocks the operator's live inventory immediately. An unconfirmed hold expires automatically at utcExpiresAt and the inventory frees itself; the booking then reads EXPIRED.

The response is the OCTO booking object: status (ON_HOLD), supplierReference (the operator-side booking id — quote it in support conversations), your resellerReference, utcExpiresAt, availability, and your unitItems.

Confirm, extend, cancel

  • Confirm finalizes the booking (CONFIRMED). Include contact — the traveler's name, email, and phone attach to the operator's booking and drive their confirmations and manifest. Confirming an already-confirmed booking is idempotent.
  • Extend ({ "expirationMinutes": 60 }) pushes the hold deadline of an ON_HOLD booking. Expired holds cannot be extended — reserve again.
  • Cancel ({ "reason": "…" }) works on holds and confirmed bookings. The response's cancellation.refund is always "NONE": you are the merchant of record — refunding the traveler is on your side; the cancel releases the operator's inventory and their books.

Statuses

StatusMeaning
ON_HOLDReserved, inventory blocked, awaiting confirm before utcExpiresAt
CONFIRMEDFinalized
EXPIREDHold lapsed without confirmation; inventory released
CANCELLEDCancelled by you (or by the operator on their side)

Not implemented (yet)

Optional OCTO capabilities — octo/pricing, octo/content, octo/notifications, pickups — are not yet advertised; requesting them initializes nothing. Availability-change push notifications are on the roadmap; until then, poll the calendar endpoint within the rate limit.

On this page