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.
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/.
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):
GET /api/public/v1/resorts/search?q=meg→ resolve a name to a resortid(e.g.39Megeve).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-linkwhen 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.
/resorts/search with the user’s resort name to get a numeric resort id./shops?resort_id=… if the user wants a specific rental shop. Omit it to quote across the resort’s default supplier./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.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.For chat agents that can only fetch URLs, the same end-to-end flow is possible without ever issuing a POST:
GET /resorts/search?q=… to resolve the resort name to an id.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./api/public/v1/resorts/searchResolve free-text input to ski resorts. Only active resorts are returned.
| Parameter | Required | Notes |
|---|---|---|
q | Yes | Search term, matched against the resort name. Must be at least 2 characters. |
A flat array of matching resorts (no pagination envelope).
[
{
"id": 39,
"name": "Megeve",
"country": "France",
"region_id": 1,
"latitude": 45.8577,
"longitude": 6.6154
}
]
400 INVALID_PARAMS — q is missing or shorter than 2 characters.curl "https://ski-rental.alps2alps.com/api/public/v1/resorts/search?q=meg"
→ Try it live: search for “meg”
/api/public/v1/resortsThe full catalogue of active resorts, paginated and sorted by name.
| Parameter | Required | Default | Notes |
|---|---|---|---|
page | No | 1 | 1-based page number. |
per_page | No | 50 | Page size. Hard-capped at 200. |
{
"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.
curl "https://ski-rental.alps2alps.com/api/public/v1/resorts?page=1&per_page=50"
/api/public/v1/shopsRental 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.
| Parameter | Required | Default | Notes |
|---|---|---|---|
resort_id | Yes | — | Numeric resort id (from a /resorts lookup). |
q | No | — | Case-insensitive substring filter on the shop name. |
limit | No | 20 | Hard-capped at 50. |
{
"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.
400 INVALID_PARAMS — resort_id is missing or not a positive integer.404 RESORT_NOT_FOUND — the resort is unknown or inactive.curl "https://ski-rental.alps2alps.com/api/public/v1/shops?resort_id=39&q=sud"
→ Try it live: shops in Megeve (resort 39)
/api/public/v1/rental-optionsReturns 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.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
resort | int | Yes | — | Resort id. Alias: resort_id. |
first_day | date | Yes | — | YYYY-MM-DD. The first rental day. |
days | int | Yes | — | Rental duration, 2–13. |
shop | int | No | — | Restrict offers to this shop’s supplier. Alias: shop_id. |
currency | string | No | EUR | ISO 4217. Must be an active currency, else 400 CURRENCY_NOT_SUPPORTED. |
promo | string | No | — | Promo code. Validated live; an invalid code never fails the request. |
{
"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¤cy=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¤cy=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¤cy=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¤cy=EUR&pack=123,130"
}
]
}
],
"promo_code": null,
"promo_code_applied": false
}
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_ids — pack_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.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_RANGE — days is outside 2–13.422 NO_OFFERS_AVAILABLE — no priced packs exist for that resort, week, and duration.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¤cy=GBP&promo=WINTER10"
/api/public/v1/checkout-linkReturns 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.
Accepts application/json. Top-level fields:
| Field | Type | Required | Notes |
|---|---|---|---|
resort_id | int | Yes | Positive integer. |
first_day | date | Yes | YYYY-MM-DD, not in the past. Default rental start for participants who don’t override it. |
duration_days | int | Yes | 2–13. |
participants | array | Yes | At least one participant (see below). |
shop_id | int | No | Restrict to a shop’s supplier. |
currency_code | string | No | ISO 4217, default EUR. Sent uppercased. |
promo_code | string | No | Silently ignored if invalid. |
booking_reference | string | No | Alps2Alps transfer reference for cross-sell linking. |
customer_email | string | No | Pre-fills the checkout contact field. Must be a valid email. |
| Field | Type | Required | Notes |
|---|---|---|---|
first_name | string | Yes | |
age | int | Yes | Must fall within the pack’s offer min_age/max_age. |
gender | string | Yes | One of man, woman. |
pack_id | int | Yes | Must be quotable for the resort/week/duration. |
ski_level | string | No | One of beginner, intermediate, experienced. |
helmet | bool | No | Adds the matching helmet pack’s surcharge. Accepts true/false (or 1/0/"true"/"false"). |
start_date, end_date | date | No | Override the rental window for this participant. Supply both or neither; YYYY-MM-DD, not past, end_date on or after start_date. |
email | string | No | Per-participant email; must be valid if present. |
height_id, weight_id, shoe_size_id, foot_width_id, calf_width_id, head_size_id | int | No | Sizing measurement ids. If present, each must match a known measurement row; otherwise sizing is completed at checkout. |
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"
{
"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).
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.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.
Both deep-link endpoints translate the same compact query-string aliases. The minimum viable URL is just resort + first_day + days.
| Alias | Meaning | Default | Notes |
|---|---|---|---|
resort | Resort id | — | Required. Alias: resort_id. |
first_day | Start date | — | Required. YYYY-MM-DD. |
days | Duration | — | Required. 2–13. |
shop | Shop id | — | Optional. Alias: shop_id. |
currency | Currency code | EUR | An invalid value silently falls back to EUR so a stale link never breaks a user landing in the funnel. |
promo | Promo code | — | Optional. Same silent-on-failure behaviour as the JSON endpoints. |
email | Contact email | — | Optional pass-through; ignored if not a valid email. |
booking_reference | Transfer reference | — | Optional pass-through. |
pack | Pack 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.
/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.
https://ski-rental.alps2alps.com/book/quick-search?resort=39&first_day=2027-01-16&days=6
/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.
https://ski-rental.alps2alps.com/book/quick-checkout?resort=39&first_day=2027-01-16&days=6&pack=124
Participant names, ages, sizing, and per-skier windows are not supported on the GET flow. Use
POST /checkout-linkfor those.
| Need | Use |
|---|---|
| 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 manually | GET /book/quick-search |
Hand user to checkout with a pre-picked pack — use the ready-made booking_url, or build manually | GET /book/quick-checkout |
| Hand user to checkout with named participants / ages / sizing / a 24h-expiring link | POST /api/public/v1/checkout-link |
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." } }
| HTTP | Code | When |
|---|---|---|
| 400 | INVALID_PARAMS | Missing or invalid request fields, malformed dates, or non-numeric where a number is required. |
| 400 | CURRENCY_NOT_SUPPORTED | Unknown or inactive currency code on a JSON endpoint. |
| 400 | VALIDATION_ERROR | Semantically invalid: bad enum value, invalid email, age vs pack range, unknown measurement id. |
| 404 | RESORT_NOT_FOUND | Unknown or inactive resort. |
| 404 | SHOP_NOT_FOUND | Unknown/inactive shop, or shop not in the given resort. |
| 422 | DURATION_OUT_OF_RANGE | Rental duration outside 2–13 days. |
| 422 | NO_OFFERS_AVAILABLE | No priced packs for the resort/week/duration (/rental-options). |
| 422 | NO_BOOKABLE_PACK | A referenced pack is not quotable (/checkout-link). |
| 429 | RATE_LIMITED | Per-IP request budget exceeded. Back off and retry with exponential backoff. |
| 500 | INTERNAL_ERROR | Catch-all for unhandled exceptions; no details are leaked. |
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." } }
resorts/search → rental-options → optionally checkout-link. The pricing endpoints have a lower allowance than the catalogue lookups.429, back off and retry with exponential backoff rather than retrying immediately.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.
Pass any active ISO 4217 currency code in currency (GET alias) or currency_code (POST) — e.g. EUR, GBP. The default is EUR.
/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./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.
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.
The API is browser-callable with no additional configuration:
Access-Control-Allow-Origin: *Access-Control-Allow-Headers: Content-Type, AcceptAccess-Control-Allow-Methods: GET, POST, OPTIONSOPTIONS preflight returns HTTP 204 with no body.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.