{
  "openapi": "3.1.0",
  "info": {
    "title": "Swell Cabin API",
    "version": "1.0.0",
    "description": "Surf towns, surf spots, places to stay (incl. surf camps), cafés, food, surf schools, what’s on and each town’s feed of new surf videos. Free, no key. Please link to the town page when you use the data."
  },
  "servers": [
    {
      "url": "https://swellcabin.com"
    }
  ],
  "paths": {
    "/api/towns": {
      "get": {
        "operationId": "searchTowns",
        "summary": "Surf towns, filtered",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Text: town, country or region",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Region name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Good surf in this month: towns in season then, ranked by measured good + great days that month, each with inMonth { goodDays, greatDays, seaTempC, waveHeightM }",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12
            }
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "description": "beginner | intermediate | advanced",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "break",
            "in": "query",
            "required": false,
            "description": "beach | reef | point | rivermouth",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Towns"
          }
        }
      }
    },
    "/api/towns/{slug}": {
      "get": {
        "operationId": "getTown",
        "summary": "One town with its surf spots, and surfByMonth: how often the surf was good and great in each month, measured over the last three years",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Town"
          }
        }
      }
    },
    "/api/places": {
      "get": {
        "operationId": "nearbyPlaces",
        "summary": "Places to stay (hotels, hostels, surf camps, homestays...), cafés, food or surf shops/schools near a town; reviewed photos come as photo { thumb, page, author, license }",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": true,
            "description": "Town slug",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "stay | coffee | food | surf | spots",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "radiusKm",
            "in": "query",
            "required": false,
            "description": "Search radius",
            "schema": {
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Places, nearest first"
          }
        }
      }
    },
    "/api/events": {
      "get": {
        "operationId": "whatsOn",
        "summary": "What's on: weekly nights and upcoming events in a town",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": true,
            "description": "Town slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Weekly schedule and events"
          }
        }
      }
    },
    "/api/feed": {
      "get": {
        "operationId": "townFeed",
        "summary": "A town's Feed: recent YouTube surf videos about the town (last 30 days, at most two per channel), the latest posts from its local Instagram accounts and public Bluesky/Mastodon posts, each through Swell Cabin's quality check (ads, off-topic, unwatched and repeated posts left out) and with langs (the languages it is written in, when clear; the site shows each visitor what is in English or their language); part=classics gives the surf clips picked for its breaks",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": true,
            "description": "Town slug, or \"all\" for every town at once (each video and post then has its town)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "part",
            "in": "query",
            "required": false,
            "description": "videos, posts or classics (optional)",
            "schema": {
              "type": "string",
              "enum": [
                "videos",
                "posts",
                "classics"
              ]
            }
          },
          {
            "name": "review",
            "in": "query",
            "required": false,
            "description": "1 to also list what the quality check left out, with why (dropped)",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "videos, posts, instagram { accounts, posts }, quality { checked, kept, dropped, langs }"
          }
        }
      }
    },
    "/api/forecast": {
      "get": {
        "operationId": "surfForecast",
        "summary": "7-day surf forecast for a town and each of its spots, rated per spot hour by hour (Flat … Epic, best time, why): surf height, swell, wind, tides, model confidence. Add live=1 for the nearest buoy and airport wind report right now",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": true,
            "description": "Town slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Forecast by day and for the next 48 hours"
          }
        }
      }
    },
    "/api/surf-now": {
      "get": {
        "operationId": "surfNow",
        "summary": "Where the surf is good around the world in the next days: towns whose best day from today is at least min (fair, good, great, epic), best and soonest first, with that day's rating at the best spot, the spot, its best time and the surf in words, and today's rating; from the forecast for every town, worked out twice a day",
        "parameters": [
          {
            "name": "min",
            "in": "query",
            "required": false,
            "description": "fair | good | great | epic (default good)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "within",
            "in": "query",
            "required": false,
            "description": "Days from today, 1–4 (default 3)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 4
            }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Region name",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "generated, stale, towns [{ slug, name, lat, lng, url, best { date, when, rating, spot, from, to, surf }, today }]"
          }
        }
      }
    },
    "/api/cam-now": {
      "get": {
        "operationId": "camNow",
        "summary": "What the surf cams show right now: every 3 hours in daylight an AI reads a still from each free surf cam and says what it shows in the forecast's own words (size, surface, surfers out, how sure) and what that means at the spot (Looks good, Surfable, Looks poor, Flat); the latest reading per spot from the last 3 hours, with the cam's link and what the forecast said then. A reading of a picture, not a measurement",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": false,
            "description": "Town slug; leave out for every town with a reading now",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "good",
            "in": "query",
            "required": false,
            "description": "1: only the spots that look good",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One town: readings [{ spot, time, local, words, surface, crowd, sure, verdict, label, reason, cam, provider, forecast { words, rating, steps } }], leftOut; every town: towns [{ slug, name, url, readings }]"
          }
        }
      }
    },
    "/api/social": {
      "get": {
        "operationId": "socialPosts",
        "summary": "Swell Cabin's social posts from its own data (swell alerts, the best day this week, the weekend's best towns, season openings, surf events, where to surf this month, town spotlights, about Swell Cabin, and the weekly stories), each written for Instagram, Facebook, Threads, X, Bluesky and Mastodon with its time, link and card image; without a town, the latest daily proposal and what is scheduled",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": false,
            "description": "Town slug (optional)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "posts [{ kind, summary, card, posts [{ channel, text, dueAt }], facts }]"
          }
        }
      }
    },
    "/api/card": {
      "get": {
        "operationId": "socialCard",
        "summary": "The image (PNG) for one of Swell Cabin's social posts, drawn from the query that /api/social gives each post (card, story)",
        "parameters": [
          {
            "name": "k",
            "in": "query",
            "required": true,
            "description": "Kind: swell-alert, best-day, weekend, season, event, month, spotlight, intro, swell-track, numbers, locals, beginners, night or spot",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "row",
            "in": "query",
            "required": false,
            "description": "On a list, the row to draw as its own photo slide",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "f",
            "in": "query",
            "required": false,
            "description": "story for 1080×1920, og for the 1200×630 link preview (default 1080×1350)",
            "schema": {
              "type": "string",
              "enum": [
                "story",
                "og"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "PNG image"
          }
        }
      }
    },
    "/api/spot-photos": {
      "get": {
        "operationId": "spotPhotos",
        "summary": "Photos of surf at each spot in a town, with credits",
        "parameters": [
          {
            "name": "town",
            "in": "query",
            "required": true,
            "description": "Town slug",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Photos by spot name"
          }
        }
      }
    }
  }
}