Surf Booking.PTAPI
ManifestoAgentIntelligence LayerTechnologyConstitutionAIMCPPartner API
Partner API

One integration.A thousand channels.

Let your customers book surf lessons directly in your platform. REST · JSON · OCTO — real availability from every school, readable by any marketplace or channel manager. Connect once, reach many.

API live · Bearer / X-Partner-API-Key
surfbooking.eu/developers
/api/partner/v1/availabilities
GET
{
  "spot":     "Carcavelos",
  "date":     "2026-06-22",
  "time":     "10:00",
  "level":    "iniciante",
  "capacity": 6,
  "vacancy":  4
}
Open platform

No closed protocol. We speak OCTO — the open standard for activity bookings. Integrate once, reach everyone.

OCTO (octo.travel) is how activity booking systems, channel managers and marketplaces talk to each other. One integration against SurfBooking, and your offer becomes readable by any OCTO buyer. For those who prefer, we also return the native SB format and variants for existing adapters.

Who connects

Five types of partner. One API.

If you distribute experiences — a hotel, an agency, a DMC, a tourism app or an accommodation platform — the SurfBooking API puts real-time surf lesson availability in your hands.

Hotels

Experience at check-in

Offer surf lessons as an add-on for guests, with real-time booking — no phone calls, no manual coordination.

Travel agencies

Surf packages

Include surf lessons in any travel package, bookable directly via API, with idempotency on duplicate requests.

DMCs

Surf in the itinerary

Add surf to any group itinerary and confirm real-time availability, with live opening counts.

Tourism apps

Listings + live availability

Show schools, spots and live availability in your app. The forecast scope adds surf conditions per spot.

Accommodation

Upsell experiences

Short-term rentals and apartments upsell surf at checkout — confirmed in seconds via API, no manual back-and-forth.

How it works

In 5 seconds, any CTO understands it.

Your platform
Hotel · OTA · Agency · App
→
SurfBooking API
REST · JSON · OCTO
→
Schools
Availability
Payments
Bookings

One integration. Every school on the platform. Real openings, real prices, anti-overbooking — confirmed in milliseconds.

Different partners, different access

Each partner connects only to what serves them.

It's not the same API for everyone. Each key is limited (least-privilege) to what that partner needs — never more than that.

Hotels · OTAs · channel managers

Availability + bookings

They read the available lessons and create bookings on behalf of the guest/customer. The v1 path (OCTO). Access for those who distribute lessons.

Equipment brands · apps · AI

Sizing + conditions

They recommend the right board/wetsuit and read the spot conditions. The v2 path, with scope sizing and/or forecast — they never touch bookings.

Partners

Attribution, no API

They bring customers and earn commission via /aff + a 90-day cookie. No key, no technical integration.

Platform keys carry scopes. A sizing key only opens the sizing endpoints — everything else returns 403 insufficient_scope.

What the API does

Three things. Availability, services, bookings.

All in REST + JSON, authenticated by the school's key.

availabilities

Read availability

The real lessons with day, time, spot, level, openings and price. Filter by date and level. Formats sb · fh · bl · octo.

services

List services

The school's lesson types (level, group/private, price range) — in native SB or as OCTO products.

bookings

Create and manage bookings

Create, query and cancel bookings on behalf of the end customer. Idempotent by externalRef, with revalidation of openings and ratios.

Live

One call and you see the API respond.

The descriptor is public. Paste into the terminal:

terminal · API descriptor
# view the API (no key)
curl -s https://www.surfbooking.eu/api/partner/v1

Full, machine-readable contract — designed for AI agents and SDK generators to auto-discover the API: /openapi.json (OpenAPI 3.1) · interactive reference at /docs (Swagger UI).

OCTO
For OTAs & resellers
Products, availability and two-step booking (hold → confirm). Plug-and-play, as in other booking systems.
iCal
Universal, both ways
Import any channel's feed into the SB calendar — and export SB's calendar (with cancellations) back into the channel.
MCP
For AI agents
11 tools — search lessons, live surf score per break, equipment sizing — over the Model Context Protocol.

Two-step booking in two calls — hold the spot while your customer pays, then confirm:

# 1 — hold (expira sozinho; a vaga fica segura)
curl -X POST https://www.surfbooking.eu/api/partner/v1/bookings   -H "X-Partner-API-Key: A_TUA_CHAVE" -H "Content-Type: application/json"   -d '{"lessonSlotId":"…","customerName":"…","customerEmail":"…",
       "status":"ON_HOLD","expirationMinutes":30}'

# 2 — confirm (idempotente; hold expirado devolve 410 e liberta a vaga)
curl -X POST https://www.surfbooking.eu/api/partner/v1/bookings/{id}/confirm   -H "X-Partner-API-Key: A_TUA_CHAVE"

With the school's key, you read the availability — here in OCTO format:

terminal · availability (OCTO)
curl -s "https://www.surfbooking.eu/api/partner/v1/availabilities?format=octo&from=2026-07-01&to=2026-07-07" \
  -H "Authorization: Bearer sbpk_xxxxxxxxxxxxxxxxxxxxxxxx"

Limit: 120 requests/min per key. Each response carries X-RateLimit-Limit and X-RateLimit-Remaining; when you exceed it you get 429 with Retry-After: 60.

Private school connector

An assistant inside your school, with a key that is yours alone.

Besides the partner API, each school can connect an MCP assistant to its own operation. It is off by default and has not yet been validated with a real client.

POST

/api/mcp/escola

JSON-RPC 2.0, MCP protocol 2025-06-18. Header Authorization: Bearer sbek_…. The school always comes from the key, never from the arguments.

POST

/api/schools/:id/conector/tokens

Generates the key with the owner's session. Body: label, role, staffId?, scopes (read, or read and propose), expiresInDays (30, 90 or 365). The key is returned only once.

DELETE

/api/schools/:id/conector/tokens/:tokenId

Revokes the key and expires the pending proposals it requested.

POST

/api/schools/:id/conector/propostas/:pid/confirmar

Owner only. Sends the payloadHash they saw; the change goes through the same path as the dashboard, and a lesson that changed in the meantime ends in conflict.

Tools: list_my_schools · get_school_schedule · get_bookings_summary · get_lesson_roster · get_stock_summary · get_conditions_for_my_lessons · prepare_lesson_move · prepare_stock_change · list_pending_proposals

Limits: 60 calls per minute per key and 2000 per day. No money tools, and nothing applies without the owner's confirmation. The details are on the MCP page. /mcp

Authentication

One key per school. You're in control.

Each school generates its own key in SurfBooking OS — and revokes it whenever it wants. The key goes in a header, in either of two formats:

X-Partner-API-Key

Native SB header

X-Partner-API-Key: sbpk_xxxx… — SurfBooking's canonical format.

Authorization: Bearer

OCTO-compatible

Authorization: Bearer sbpk_xxxx… — for clients that speak OCTO. The same key.

Generate and revoke keys in SurfBooking OS → Sales channels → Partner API. We never store the key in plaintext — only its digest.

Reference

The endpoints.

Base: https://www.surfbooking.eu/api/partner/v1

GET

/

API descriptor (root, no key). Returns name, version, auth, octo and the full map of endpoints. Opening it in a browser redirects to this page.

GET

/availabilities

Bookable lessons. Parameters: from, to, level?, format? (sb·fh·bl·octo). Defaults: from=today, to=+30 days.

GET

/availability

OCTO alias of /availabilities — assumes format=octo by default (plug-and-play OCTO).

GET

/services

The school's lesson types. format? (sb·octo) — in OCTO it returns products.

GET

/products

OCTO alias of /services — assumes format=octo by default. Array of OCTO products.

GET

/supplier

Supplier identity (OCTO supplier descriptor): id, name, endpoint, contact, locales, timeZone.

POST

/bookings

Creates a booking. Body: lessonSlotId, customerName, customerEmail, customerPhone?, participants?, externalRef?, notes?.

GET

/bookings/:id

Queries the state of a booking.

DELETE

/bookings/:id

Cancels a booking.

The descriptor — GET /api/partner/v1 (no key):

response · descriptor
{
  "name": "SurfBooking Partner API v1",
  "version": "1.0.0",
  "auth": "X-Partner-API-Key header, ou Authorization: Bearer  (compatível OCTO).",
  "octo": {
    "supported": true,
    "formatParam": "octo",
    "supplierEndpoint": "/api/partner/v1/supplier"
  },
  "endpoints": {
    "availabilities": { "method": "GET", "path": "/api/partner/v1/availabilities" },
    "services":       { "method": "GET", "path": "/api/partner/v1/services" },
    "supplier":       { "method": "GET", "path": "/api/partner/v1/supplier" },
    "createBooking":  { "method": "POST", "path": "/api/partner/v1/bookings" },
    "getBooking":     { "method": "GET", "path": "/api/partner/v1/bookings/:id" },
    "cancelBooking":  { "method": "DELETE", "path": "/api/partner/v1/bookings/:id" }
  }
}

Supplier identity — GET /supplier (OCTO):

response · supplier (OCTO)
{
  "id": "school-uuid-…",
  "name": "Nome da Escola",
  "endpoint": "https://www.surfbooking.eu/api/partner/v1",
  "contact": { "website": "https://www.surfbooking.eu", "email": null, "telephone": null, "address": null },
  "locales": ["pt", "en"],
  "timeZone": "Europe/Lisbon",
  "_sb": { "description": "…", "logoUrl": "…" }
}
Examples

Request and response, for real.

The body of a booking and what you get back — exactly as the API responds today.

Create a booking — POST /bookings:

request · POST /bookings
curl -s -X POST https://www.surfbooking.eu/api/partner/v1/bookings \
  -H "Authorization: Bearer sbpk_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "lessonSlotId": "a1b2c3d4-…",
    "customerName": "Ana Silva",
    "customerEmail": "[email protected]",
    "customerPhone": "+351912345678",
    "participants": 2,
    "externalRef": "EXT-7782",
    "notes": "Primeira aula"
  }'

Response — 201 Created:

response · 201
{
  "ok": true,
  "bookingId": "f1e2d3c4-…",
  "createdAt": "2026-06-21T11:42:00.000Z",
  "lessonSlotId": "a1b2c3d4-…",
  "schoolId": "…",
  "status": "confirmed",
  "participants": 2,
  "totalAmountCents": 7000,
  "currency": "EUR"
}

Availability in OCTO — GET /availabilities?format=octo returns an array:

response · availabilities (OCTO)
[
  {
    "id": "a1b2c3d4-…",
    "localDateTimeStart": "2026-07-02T10:00:00",
    "localDateTimeEnd": "2026-07-02T12:00:00",
    "available": true,
    "status": "AVAILABLE",
    "vacancies": 4,
    "capacity": 6,
    "maxUnits": 4,
    "pricing": {
      "original": 3500, "retail": 3500, "net": 3500,
      "currency": "EUR", "currencyPrecision": 2
    },
    "_sb": { "level": "beginner", "spotName": "…", "bookingUrl": "https://www.surfbooking.eu/aula/…" }
  }
]

Prices in cents (minor units) — 3500 = €35.00, per OCTO's currencyPrecision: 2. Dates come in the school's local time (Europe/Lisbon).

The same availability in other formats. Native SB — ?format=sb (envelope with _meta):

response · availabilities (SB)
{
  "availabilities": [
    {
      "id": "a1b2c3d4-…",
      "date": "2026-07-02",
      "startTime": "10:00",
      "endTime": "12:00",
      "level": "beginner",
      "spotsTotal": 6,
      "spotsRemaining": 4,
      "pricePerPerson": 3500,
      "currency": "EUR",
      "instructorName": "…",
      "spotName": "…",
      "schoolId": "…",
      "schoolName": "…",
      "bookingUrl": "https://www.surfbooking.eu/aula/9f3c1a2b7d"
    }
  ],
  "_meta": { "format": "sb", "total": 1, "from": "2026-07-01", "to": "2026-07-07" }
}

Another booking system — ?format=fh (availabilities envelope):

response · availabilities (FH)
{
  "availabilities": [
    {
      "pk": "a1b2c3d4-…",
      "start_at": "2026-07-02T10:00:00",
      "end_at": "2026-07-02T12:00:00",
      "capacity": 6,
      "num_remaining": 4,
      "is_available": true,
      "customer_type_rates": [
        { "pk": "a1b2c3d4-…_default", "total": { "amount": "35.00", "currency": "EUR" } }
      ],
      "_sb_meta": { "level": "beginner", "bookingUrl": "https://www.surfbooking.eu/aula/9f3c1a2b7d" }
    }
  ]
}

Another booking system — ?format=bl (data envelope):

response · availabilities (BL)
{
  "data": [
    {
      "id": "a1b2c3d4-…",
      "date": "2026-07-02",
      "startTime": "10:00",
      "endTime": "12:00",
      "serviceName": "Surf beginner",
      "capacity": 6,
      "spotsLeft": 4,
      "pricePerPerson": 3500,
      "currency": "EUR",
      "_sb_meta": { "level": "beginner", "bookingUrl": "https://www.surfbooking.eu/aula/9f3c1a2b7d" }
    }
  ]
}

In octo the response is a plain array (no envelope). In sb/fh it comes in availabilities; in bl, in data.

Errors

Status codes.

Each error carries a JSON body { "error": "…" } — machine-readable.

401

missing_api_key · invalid_api_key

Key missing, too short, invalid or revoked. Includes a hint when the header is missing.

400

missing_fields · invalid_email · invalid_date_range

Incomplete body, malformed email, or invalid date range (max 180 days, from ≤ to).

404

slot_not_found · booking_not_found · booking_not_found_or_already_cancelled

The lesson or booking doesn't exist at this school — or, on DELETE, it had already been cancelled.

409

insufficient_spots · slot_expired · slot_not_purchasable · ratio_exceeded · external_ref_exists

State conflict: no openings, lesson in the past, closed to sale, instructor ratio exceeded, or repeated externalRef (idempotency — returns the bookingId that already exists).

403

forbidden · not_owner

The key is valid but does not reach that resource: the booking or the lesson belongs to another school. A key only ever sees what belongs to the school that created it.

410

hold_expired

The booking was awaiting confirmation and the window closed. It is not a mistake on your side: the seat went back to whoever wants it. Create it again.

429

rate_limit_exceeded

Above 120 requests/min per key. Wait for Retry-After (60s). Each response carries X-RateLimit-Remaining.

500

internal_error

That one is on us. Retry with the same externalRef: booking creation is idempotent on that key, so retrying duplicates nothing.

Missing or invalid key — 401:

response · 401
# no key header
{
  "error": "missing_api_key",
  "hint": "Set X-Partner-API-Key header (or Authorization: Bearer)"
}

# nonexistent or revoked key
{ "error": "invalid_api_key" }

Not enough openings (anti-overbooking) — 409:

response · 409
# the lesson no longer has room for the number of participants
{
  "error": "insufficient_spots",
  "remaining": 1
}

# repeated externalRef → idempotency: returns the existing bookingId
{
  "error": "external_ref_exists",
  "bookingId": "f1e2d3c4-…"
}

Revalidation of openings and ratios runs inside a transaction with SELECT … FOR UPDATE — sold out is sold out, there's never double-booking.

Limits

What you can ask for, and how much.

120 / min

Per key, not per partner

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining. Past it, a 429 with Retry-After: 60. No guessing: the numbers ride on every response.

180 days

That is the page, and there is no cursor

The from–to range is what bounds the response; over 180 days it returns 400. There is no cursor pagination on purpose: a lesson has a date, and asking by dates is this domain's natural paging. If you need more, that is two requests.

50

Active keys per school

Revoke the ones you do not use before creating new ones. A revoked key stops working on the very next request, with no delay.

These three numbers are read from the code that serves the API, not written here by hand: if they change there, this page becomes wrong and a test says so.

Sizing + conditions · v2

Recommend the right gear. Without touching bookings.

The v2 path for equipment brands, apps and AI. Platform key with scope sizing, forecast and/or brand:read — deterministic, no personal data, never touches bookings. Discover your scopes at /api/partner/v2/meta. Base: https://www.surfbooking.eu/api/partner/v2.

GET

/sizing/board

Recommended board by level + weight. Scope sizing.

GET

/sizing/board/conditions

Board tuned to the day: level, weight, swellPeriodS, swellDirectionDeg, bestSwellDirDeg. Scope sizing.

GET

/sizing/wetsuit

Wetsuit size by weight + height. Scope sizing.

GET

/sizing/leash

Leash by boardSize + category (board·soft_board). Scope sizing.

GET

/sizing/boots

Boots by shoeSizeEu (e.g.: 42). Scope sizing.

GET

/spots

Lists the spots you can query — use an id from here in /conditions/:spotId. Scope forecast.

GET

/conditions/:spotId

Spot conditions — the same public shape, sanitized (never exposes model or sources). Scope forecast.

GET

/intelligence/surf-now

The best spots in the country right now, ranked by the live score — limit + level. Scope forecast.

GET

/brand/equipment-stats

For equipment brands: how many times each size of your brand was used in lessons, by month (YYYY-MM). Only your data. Scope brand:read.

GET

/brand/spot-activity

Which spots your brand's equipment was most used at, last months (1–12). Scope brand:read.

Board recommendation — GET /sizing/board:

terminal · sizing (v2)
curl -s "https://www.surfbooking.eu/api/partner/v2/sizing/board?level=beginner&weight=75" \
  -H "X-Partner-API-Key: sbpd_xxxxxxxxxxxxxxxxxxxxxxxx"
response · sizing
{ "recommendation": { "category": "soft_board", "size": "8'0" } }

Platform keys (sbpd_…) are issued by SurfBooking, not by the school. Wrong scope → 403 insufficient_scope. Limit 240 requests/min per key, with Retry-After: 60 when exceeded.

Embeddable widget

Sizing on your site. One line.

A self-contained iframe page, "Powered by SurfBooking". It shows the spot conditions, recommends the board by weight and level, and takes the customer to book a lesson — with your premium partner link built in. PII-free, no tracking cookies.

Live example — this is exactly what your visitors see:
html · embed
<iframe
  src="https://www.surfbooking.eu/w/surf?key=sbpd_xxxx&spot=SPOT_ID&slug=O_TEU_SLUG"
  width="360" height="520" frameborder="0"></iframe>

key = your platform key (scope sizing, and forecast if you want the conditions at the top). spot and slug are optional — the slug links the bookings generated to your /aff (90-day cookie).

Versions

v1 — stable.

The base is /api/partner/v1. Breaking changes go into a new version (/v2) — v1 has no deprecation planned. We may add fields without notice, so read tolerantly: ignore what you don't yet know.

Who it's for

Channel managers, marketplaces, hotels.

Any partner that wants to sell or show real surf lessons. Designed on the open OCTO standard, which many booking systems and channel managers already speak.

Hotels & resorts

Offer real lessons to guests without managing anything — the booking and payment stay on SurfBooking.

Channel managers

Sync availability in real time, no double-booking. OCTO out of the box.

Marketplaces

List the schools' offer and route the booking — with openings and ratios always revalidated on our side.

Your safety

The school is in charge. Always.

KEY

Per school, revocable

Each key sees only its own school's offer. Revoke in one click and access dies instantly.

OPENINGS

Never any overbooking

Each booking revalidates openings and instructor ratios inside a transaction. Sold out is sold out.

PAYMENT

Confirmed on SurfBooking

The money flow stays on our side — the partner routes, SurfBooking confirms.

STUDENTS

Protected data

Only what's needed for the booking goes out. Your students' contacts don't become anyone's list.

Ready to connect

Get a key and start.

The API is live. If you run a surf school, generate your own key in SurfBooking OS. If you're an external partner — an equipment brand, app, AI or hotel — request a platform key (issued by SurfBooking).

I'm a school · Open SurfBooking OS I'm an external partner · Request access View the descriptor