Get 7% OFF on winter transfers. Use code ALPSEARLY to apply Get 7% OFF on winter transfers
Home / Developers / Alps2Alps Ski Rental Public API — Developer Documentation

Alps2Alps Ski Rental Public API — Developer Documentation

The Ski Rental Public API is a free, key-less HTTP API that lets AI agents and developer integrations discover ski resorts and rental shops, retrieve real-time rental prices from the same booking engine that powers the website, and generate checkout links that land the end user directly on the booking funnel with everything pre-filled. The entire flow works with GET requests only — no POST needed — so even chat agents that can only open URLs can drive the booking funnel.

The API deliberately does not create orders or take payment — it computes live quotes and hands the user off to the existing booking funnel, which handles checkout and payment. No authentication is required. All endpoints are rate-limited per IP. All responses are application/json.

Base URL: https://ski-rental.alps2alps.com/api/public/v1/

The version lives in the path (v1); breaking changes ship as v2. The browser deep links (/book/quick-search, /book/quick-checkout) sit at the site root, not under /api/public/v1/.

TL;DR for AI agents

Alps2Alps Ski Rental rents skis, snowboards, boots, and helmets in Alpine resorts. This API lets you take a user from “I need skis in Megeve for a week in January” to a ready-to-pay booking page — without any auth and without leaving GET requests if that’s all your runtime supports.

Minimum end-to-end flow (works with GET only):

  1. GET /api/public/v1/resorts/search?q=meg → resolve a name to a resort id (e.g. 39 Megeve).
  2. GET /api/public/v1/rental-options?resort=39&first_day=2027-01-16&days=6 → live prices for every offer tier and equipment pack. The response includes ready-made links — no need to construct URLs manually:
    • response.quote_url — pre-filled link to the offer picker. Use this when the user hasn’t chosen a pack yet.
    • response.offers[i].equipment[j].booking_url — per-pack checkout link with that pack pre-selected. Use this after the user picks an equipment variant.

If you can POST, use POST /api/public/v1/checkout-link when you also need to pre-fill named participants, ages, ski levels, sizing measurements, or per-skier rental windows. That returns a tokenised URL valid for 24 hours.

  1. Resolve the resort — call /resorts/search with the user’s resort name to get a numeric resort id.
  2. (Optional) Pick a shop — call /shops?resort_id=… if the user wants a specific rental shop. Omit it to quote across the resort’s default supplier.
  3. Quote the rental — call /rental-options with the resort, first_day, and days. Present the offer tiers, equipment compositions, and prices. Each variant carries an in_store_price (the walk-in reference price) so you can show the online saving. Participant sizing is not needed to quote — it is collected later at checkout.
  4. Send the user to checkout — once the user picks an equipment variant, use that variant’s booking_url to send them to the funnel with the pack pre-selected. If you need to pre-fill named participants, ages, or sizing as well, use POST /checkout-link instead.
  5. The user pays — the hand-off link lands on the booking funnel. Prices are recomputed server-side at checkout; the API never charges a card or reserves stock.

Lightweight agents (no POST capability)

For chat agents that can only fetch URLs, the same end-to-end flow is possible without ever issuing a POST:

  1. GET /resorts/search?q=… to resolve the resort name to an id.
  2. GET /api/public/v1/rental-options?resort=…&first_day=…&days=… to fetch live prices. The JSON response already contains the links — just use them directly:
    • response.quote_url — lands the user on the offer picker with prices calculated.
    • response.offers[i].equipment[j].booking_url — lands the user on checkout with that pack pre-selected.
    • You can still construct these URLs manually using the deep-link aliases if needed (see Deep links section).

Endpoints

Resolve free-text input to ski resorts. Only active resorts are returned.

Query parameters

ParameterRequiredNotes
qYesSearch term, matched against the resort name. Must be at least 2 characters.

Response

A flat array of matching resorts (no pagination envelope).

[
  {
    "id": 39,
    "name": "Megeve",
    "country": "France",
    "region_id": 1,
    "latitude": 45.8577,
    "longitude": 6.6154
  }
]

Errors

  • 400 INVALID_PARAMSq is missing or shorter than 2 characters.

Example

curl "https://ski-rental.alps2alps.com/api/public/v1/resorts/search?q=meg"

2. GET /api/public/v1/resorts

The full catalogue of active resorts, paginated and sorted by name.

Query parameters

ParameterRequiredDefaultNotes
pageNo11-based page number.
per_pageNo50Page size. Hard-capped at 200.

Response

{
  "data": [
    {
      "id": 39,
      "name": "Megeve",
      "country": "France",
      "region_id": 1,
      "latitude": 45.8577,
      "longitude": 6.6154
    }
  ],
  "meta": {
    "total": 187,
    "page": 1,
    "per_page": 50,
    "total_pages": 4
  }
}

Each resort object carries the same fields as /resorts/search. The meta block describes the pagination window.

Example

curl "https://ski-rental.alps2alps.com/api/public/v1/resorts?page=1&per_page=50"

3. GET /api/public/v1/shops

Rental shops for a resort, with address, coordinates, and opening hours drawn from each shop’s stored profile. Only active shops for the given resort are returned.

Query parameters

ParameterRequiredDefaultNotes
resort_idYesNumeric resort id (from a /resorts lookup).
qNoCase-insensitive substring filter on the shop name.
limitNo20Hard-capped at 50.

Response

{
  "data": [
    {
      "id": 90,
      "name": "Plein Sud",
      "supplier": "skiset",
      "resort_id": 39,
      "address": "123 Rue du Village, Megeve",
      "latitude": 45.8571,
      "longitude": 6.6178,
      "opening_hours": []
    }
  ],
  "total": 4
}

total is the number of matching shops before limit is applied, so the caller can tell when the list was truncated. address, latitude, longitude, and opening_hours are derived from the shop’s stored profile and may be empty when not on file.

Errors

  • 400 INVALID_PARAMSresort_id is missing or not a positive integer.
  • 404 RESORT_NOT_FOUND — the resort is unknown or inactive.

Example

curl "https://ski-rental.alps2alps.com/api/public/v1/shops?resort_id=39&q=sud"

4. GET /api/public/v1/rental-options

Returns real-time prices for every rentable offer at a resort for a given start date and duration. This is the quote endpoint: nothing is reserved and prices are computed on the fly with the same engine as the booking site.

Participant sizing (height, weight, shoe size…) is not required to quote — it is collected later at checkout. A quote only needs a resort, a start date, and a duration.

Query parameters

ParameterTypeRequiredDefaultNotes
resortintYesResort id. Alias: resort_id.
first_daydateYesYYYY-MM-DD. The first rental day.
daysintYesRental duration, 2–13.
shopintNoRestrict offers to this shop’s supplier. Alias: shop_id.
currencystringNoEURISO 4217. Must be an active currency, else 400 CURRENCY_NOT_SUPPORTED.
promostringNoPromo code. Validated live; an invalid code never fails the request.

Response

{
  "disclaimer": "Prices are calculated in real time and are not a reservation.",
  "quote_url": "https://ski-rental.alps2alps.com/book/quick-search?resort_id=39&first_day=2027-01-16&days=6&currency=EUR",
  "resort": { "id": 39, "name": "Megeve", "country": "France" },
  "shop": null,
  "currency": "EUR",
  "rental": { "first_day": "2027-01-16", "last_day": "2027-01-21", "duration_days": 6 },
  "offers": [
    {
      "offer_id": 12,
      "name": "PERFORMANCE",
      "group": "SKI",
      "age_group": "adult",
      "min_age": 13,
      "max_age": null,
      "equipment": [
        {
          "pack_id": 123,
          "pack_ids": [123],
          "composition": "Skis",
          "price": 89.50,
          "in_store_price": 142.00,
          "booking_url": "https://ski-rental.alps2alps.com/book/quick-checkout?resort_id=39&first_day=2027-01-16&days=6&currency=EUR&pack=123"
        },
        {
          "pack_id": 124,
          "pack_ids": [124],
          "composition": "Skis + Boots",
          "price": 119.00,
          "in_store_price": 188.00,
          "booking_url": "https://ski-rental.alps2alps.com/book/quick-checkout?resort_id=39&first_day=2027-01-16&days=6&currency=EUR&pack=124"
        },
        {
          "pack_id": 123,
          "pack_ids": [123, 130],
          "composition": "Skis + Helmet",
          "price": 101.00,
          "in_store_price": 160.00,
          "booking_url": "https://ski-rental.alps2alps.com/book/quick-checkout?resort_id=39&first_day=2027-01-16&days=6&currency=EUR&pack=123,130"
        }
      ]
    }
  ],
  "promo_code": null,
  "promo_code_applied": false
}

Response fields

  • offers[] — one entry per offer tier (e.g. ECO, EVOLUTION, PERFORMANCE, EXCELLENCE, and child tiers). group is SKI or SNOWBOARD; age_group is adult or a child band; min_age / max_age bound who the tier fits (null = no bound).
  • offers[].equipment[] — the priced compositions for that tier: Skis (or Snowboard), + Boots, + Helmet, + Boots + Helmet. Only compositions with a real price are included.
  • price — the online rental price in the requested currency. in_store_price — the walk-in reference price (or null), useful for showing the online saving.
  • pack_id / pack_idspack_id is the primary pack; pack_ids is every pack that composes the variant (e.g. base ski pack + a separate helmet pack). The pack alias on /book/quick-checkout accepts exactly this set (comma-separated).
  • quote_url — ready-made link to the offer picker with prices calculated. equipment[].booking_url — per-variant checkout link with that pack pre-selected.
  • promo_code / promo_code_applied — echo of the submitted code (or null) and whether it validated. See Promo codes.

Errors

  • 400 INVALID_PARAMS — missing/malformed resort, first_day, or days.
  • 400 CURRENCY_NOT_SUPPORTED — the currency code is not active.
  • 404 RESORT_NOT_FOUND — unknown or inactive resort.
  • 404 SHOP_NOT_FOUND — the shop does not belong to this resort or is inactive.
  • 422 DURATION_OUT_OF_RANGEdays is outside 2–13.
  • 422 NO_OFFERS_AVAILABLE — no priced packs exist for that resort, week, and duration.

Examples

Adult skis in Megeve, 6 days:

curl "https://ski-rental.alps2alps.com/api/public/v1/rental-options?resort=39&first_day=2027-01-16&days=6"

Same, priced in GBP with a promo code:

curl "https://ski-rental.alps2alps.com/api/public/v1/rental-options?resort=39&first_day=2027-01-16&days=6&currency=GBP&promo=WINTER10"

Returns a checkout URL valid for 24 hours that lands the user on the booking funnel with the cart pre-filled — participants, packs, and any sizing you supply. No order row is created until the user completes checkout, and prices are recomputed server-side when the link is opened (the client price is never trusted). Use this when the ready-made booking_url from /rental-options isn’t enough because you need named participants, ages, ski levels, sizing, or per-skier rental windows.

Request body

Accepts application/json. Top-level fields:

FieldTypeRequiredNotes
resort_idintYesPositive integer.
first_daydateYesYYYY-MM-DD, not in the past. Default rental start for participants who don’t override it.
duration_daysintYes2–13.
participantsarrayYesAt least one participant (see below).
shop_idintNoRestrict to a shop’s supplier.
currency_codestringNoISO 4217, default EUR. Sent uppercased.
promo_codestringNoSilently ignored if invalid.
booking_referencestringNoAlps2Alps transfer reference for cross-sell linking.
customer_emailstringNoPre-fills the checkout contact field. Must be a valid email.

Participant fields

FieldTypeRequiredNotes
first_namestringYes
ageintYesMust fall within the pack’s offer min_age/max_age.
genderstringYesOne of man, woman.
pack_idintYesMust be quotable for the resort/week/duration.
ski_levelstringNoOne of beginner, intermediate, experienced.
helmetboolNoAdds the matching helmet pack’s surcharge. Accepts true/false (or 1/0/"true"/"false").
start_date, end_datedateNoOverride the rental window for this participant. Supply both or neither; YYYY-MM-DD, not past, end_date on or after start_date.
emailstringNoPer-participant email; must be valid if present.
height_id, weight_id, shoe_size_id, foot_width_id, calf_width_id, head_size_idintNoSizing measurement ids. If present, each must match a known measurement row; otherwise sizing is completed at checkout.

Request example

curl -X POST -H "Content-Type: application/json" \
  -d '{
        "resort_id": 39,
        "first_day": "2027-01-16",
        "duration_days": 6,
        "currency_code": "EUR",
        "promo_code": "WINTER10",
        "booking_reference": "A2A-123456",
        "customer_email": "guest@example.com",
        "participants": [
          {
            "first_name": "Anna",
            "age": 34,
            "gender": "woman",
            "pack_id": 124,
            "helmet": true,
            "ski_level": "intermediate"
          }
        ]
      }' \
  "https://ski-rental.alps2alps.com/api/public/v1/checkout-link"

Response

{
  "checkout_url": "https://ski-rental.alps2alps.com/ski-rental/booking/book/?t=<token>",
  "expires_at": "2027-01-08T14:00:00+00:00",
  "currency": "EUR",
  "total_price": 125.00,
  "promo_code_applied": true
}

total_price is the sum of each participant’s pack price (plus any helmet surcharge) — an indicative total; the funnel recomputes the authoritative price at checkout. The token is single-use and expires after 24 hours (expires_at is UTC, ISO 8601).

Errors

  • 400 INVALID_PARAMS — malformed body, missing required field, non-numeric age/pack_id, bad date, missing participants.
  • 400 CURRENCY_NOT_SUPPORTED — the currency code is not active.
  • 400 VALIDATION_ERROR — bad gender/ski_level/helmet value, invalid email, age outside the pack range, or an unknown measurement id.
  • 404 RESORT_NOT_FOUND / 404 SHOP_NOT_FOUND — unknown resort or shop.
  • 422 DURATION_OUT_OF_RANGE — duration outside 2–13.
  • 422 NO_BOOKABLE_PACK — a referenced pack is not quotable for the resort/week/duration.
  • A GET to this endpoint returns 405 Method Not Allowed.

A stateless family of browser GET links that lets simple AI agents and chat tools drive the booking flow with plain URLs — no POST needed, no DB row, no expiry. In most cases the quote_url and booking_url fields returned by GET /rental-options are already constructed for you — the manual URL building below is for cases where you need full control over parameters or cannot make a prior API call.

The trade-off vs POST /checkout-link: deep links cannot carry participant names, ages, sizing, or per-skier windows (those require server-side validation). Use POST /checkout-link when you need those extras.

Friendly alias → parameter mapping

Both deep-link endpoints translate the same compact query-string aliases. The minimum viable URL is just resort + first_day + days.

AliasMeaningDefaultNotes
resortResort idRequired. Alias: resort_id.
first_dayStart dateRequired. YYYY-MM-DD.
daysDurationRequired. 2–13.
shopShop idOptional. Alias: shop_id.
currencyCurrency codeEURAn invalid value silently falls back to EUR so a stale link never breaks a user landing in the funnel.
promoPromo codeOptional. Same silent-on-failure behaviour as the JSON endpoints.
emailContact emailOptional pass-through; ignored if not a valid email.
booking_referenceTransfer referenceOptional pass-through.
packPack id(s)/book/quick-checkout only. Repeatable or comma-separated pack ids (the pack_ids set from /rental-options). Omit → the cheapest adult pack.

Unknown query keys (e.g. utm_source=…) are silently ignored. On invalid input these links redirect to the start of the booking funnel rather than rendering an error page — the user never sees an error.

5a. GET /book/quick-search (land on the offer picker)

Stateless deep link that lands the browser on the offer picker with prices already calculated. Equivalent to the quote_url returned by GET /rental-options. Useful when you want to construct the link manually without a prior API call.

5b. GET /book/quick-checkout (land on checkout)

Stateless equivalent of POST /checkout-link. Lands the browser on checkout with the equipment pack(s) pre-selected. Equivalent to the booking_url field on each equipment variant in the GET /rental-options response. The pack parameter pre-picks equipment; omit it to default to the cheapest adult pack.

Participant names, ages, sizing, and per-skier windows are not supported on the GET flow. Use POST /checkout-link for those.

POST vs GET — which to use

NeedUse
Live prices as JSON (chat, email, machine-to-machine)GET /api/public/v1/rental-options
Hand user to the offer picker — use the ready-made quote_url, or build manuallyGET /book/quick-search
Hand user to checkout with a pre-picked pack — use the ready-made booking_url, or build manuallyGET /book/quick-checkout
Hand user to checkout with named participants / ages / sizing / a 24h-expiring linkPOST /api/public/v1/checkout-link

Error envelope & error codes

Every non-2xx response on a JSON endpoint shares the same envelope:

{ "error": { "code": "DURATION_OUT_OF_RANGE", "message": "Rental duration must be between 2 and 13 days." } }
HTTPCodeWhen
400INVALID_PARAMSMissing or invalid request fields, malformed dates, or non-numeric where a number is required.
400CURRENCY_NOT_SUPPORTEDUnknown or inactive currency code on a JSON endpoint.
400VALIDATION_ERRORSemantically invalid: bad enum value, invalid email, age vs pack range, unknown measurement id.
404RESORT_NOT_FOUNDUnknown or inactive resort.
404SHOP_NOT_FOUNDUnknown/inactive shop, or shop not in the given resort.
422DURATION_OUT_OF_RANGERental duration outside 2–13 days.
422NO_OFFERS_AVAILABLENo priced packs for the resort/week/duration (/rental-options).
422NO_BOOKABLE_PACKA referenced pack is not quotable (/checkout-link).
429RATE_LIMITEDPer-IP request budget exceeded. Back off and retry with exponential backoff.
500INTERNAL_ERRORCatch-all for unhandled exceptions; no details are leaked.

Rate limits

The API requires no authentication and is rate limited per client IP. When the limit is exceeded the API returns HTTP 429 with the standard error envelope:

{ "error": { "code": "RATE_LIMITED", "message": "Too many requests. Please slow down and retry." } }
  • Pace your requests. A normal end-to-end flow is 2–3 calls: resorts/searchrental-options → optionally checkout-link. The pricing endpoints have a lower allowance than the catalogue lookups.
  • On a 429, back off and retry with exponential backoff rather than retrying immediately.
  • Shared IPs — users behind a shared/office IP share the same budget and can hit the limit sooner.

Exact per-minute numbers are intentionally not published so they can be tuned without changing the contract. Treat 429 as “slow down”, not a permanent failure.


Currencies

Pass any active ISO 4217 currency code in currency (GET alias) or currency_code (POST) — e.g. EUR, GBP. The default is EUR.

  • JSON endpoints (/rental-options, /checkout-link) — an unknown or inactive currency code returns 400 CURRENCY_NOT_SUPPORTED. Agents calling these endpoints should handle that error rather than assuming an EUR fallback. Codes are matched after upper-casing, so send them uppercase.
  • Browser deep links (/book/quick-search, /book/quick-checkout) — an invalid currency= value silently falls back to EUR so a stale parameter never breaks a user landing in the funnel.

Non-EUR amounts are converted with the live rate; the exact checkout total may shift slightly due to FX between the quote and payment.


Promo codes

Pass promo (GET alias) or promo_code (POST) on either /rental-options or /checkout-link. The code is validated live server-side. If validation fails or the promo service is temporarily unreachable, the code is silently ignored and promo_code_applied returns false — the request itself still succeeds and prices are returned at full rate.

When a code is submitted, the response echoes promo_code (the submitted code) and reports promo_code_applied as true or false.


CORS & HTTP methods

The API is browser-callable with no additional configuration:

  • Access-Control-Allow-Origin: *
  • Access-Control-Allow-Headers: Content-Type, Accept
  • Access-Control-Allow-Methods: GET, POST, OPTIONS
  • OPTIONS preflight returns HTTP 204 with no body.
  • CSRF validation is disabled on all public API endpoints.

Per endpoint: /resorts*, /shops, and /rental-options accept GET; /checkout-link accepts POST. The /book/* deep links accept GET and redirect the browser into the funnel.