{
  "openapi": "3.1.0",
  "info": {
    "title": "BargeFlow Routeplanner API",
    "version": "1.0.0",
    "summary": "Plan inland waterway voyages and produce the voyage plan PDF.",
    "description": "An automated client is treated as an ordinary user. Without credentials you get what an anonymous visitor gets: you can plan routes (1 per minute) and produce a watermarked demo voyage plan. With an API token you act as that token's account. Exporting a clean voyage plan needs the same subscription a person needs.\n\nThe intended sequence is /api/v1/me, then /api/v1/locations for each end, then /api/v1/plan with the chosen coordinates, then /api/v1/voyage-plan with the route you received. Call GET /api/v1/me first: it states your tier, what you may do and your rate limits, so you never have to discover limits by hitting them.\n\nTo get a token, do NOT ask anyone for a password. Use the device flow: POST /api/v1/auth/device, show the returned user_code to the person you act for, ask them to open the verification_uri, then poll POST /api/v1/auth/token until it returns your token. Polling before approval answers 428 authorization_pending; honour the stated interval.\n\nEndpoints not described here are not undocumented features; probing them is logged and rate-limited.\n\nNot a navigation system. Output must not be the sole basis for navigational decisions, and loading depth remains the skipper's judgement.",
    "contact": {
      "email": "info@shipdocs.app",
      "url": "https://www.reisplanner.app/about"
    },
    "license": {
      "name": "Terms of use",
      "url": "https://www.reisplanner.app/about"
    }
  },
  "servers": [
    {
      "url": "https://www.reisplanner.app"
    }
  ],
  "security": [
    {},
    {
      "apiToken": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API token created by the account holder in their profile settings at https://www.reisplanner.app/profile-settings. Optional: without it you are served as an anonymous visitor."
      }
    },
    "schemas": {
      "Vessel": {
        "type": "object",
        "description": "Dimensions decide which fairways, bridges and locks are usable. Values outside the stated range fall back to a default rather than failing. The defaults describe a standard inland cargo vessel, the same profile the app assumes when no vessel is known.",
        "properties": {
          "length": {
            "type": "number",
            "minimum": 1,
            "maximum": 400,
            "default": 85,
            "description": "metres"
          },
          "width": {
            "type": "number",
            "minimum": 1,
            "maximum": 60,
            "default": 9.5,
            "description": "beam in metres"
          },
          "draught": {
            "type": "number",
            "minimum": 0.1,
            "maximum": 20,
            "default": 2.5,
            "description": "metres below the waterline"
          },
          "air_draft": {
            "type": "number",
            "minimum": 0.5,
            "maximum": 40,
            "default": 12.5,
            "description": "height above the WATERLINE, not above the keel"
          },
          "vessel_type": {
            "type": "string",
            "enum": [
              "2",
              "3",
              "4",
              "5",
              "6"
            ],
            "default": "2",
            "description": "2 = cargo ship (default; the only type the planner itself uses). 3 = yacht, 4 = small yacht/cruiser, 5 = open boat, 6 = canoe are pleasure craft, accepted for completeness but outside what the rest of the product is built for."
          },
          "speed": {
            "type": "number",
            "minimum": 1,
            "maximum": 40,
            "default": 15,
            "description": "nominal speed in km/h"
          },
          "max_speed": {
            "type": "number",
            "minimum": 1,
            "maximum": 50,
            "default": 20,
            "description": "maximum speed in km/h"
          },
          "consumption_per_hour": {
            "type": "number",
            "minimum": 0,
            "maximum": 2000,
            "description": "litres per hour at nominal speed; drives the fuel estimate"
          },
          "weight_kg": {
            "type": "number",
            "minimum": 1,
            "description": "total vessel weight in kg"
          }
        }
      },
      "Point": {
        "type": "object",
        "required": [
          "lat",
          "lon"
        ],
        "properties": {
          "lat": {
            "type": "number",
            "minimum": -90,
            "maximum": 90
          },
          "lon": {
            "type": "number",
            "minimum": -180,
            "maximum": 180
          },
          "name": {
            "type": "string",
            "maxLength": 120,
            "description": "Shown as departure/arrival on the voyage plan"
          }
        },
        "description": "A point on the waterway network, ideally a candidate from /api/v1/locations."
      }
    }
  },
  "paths": {
    "/api/v1/me": {
      "get": {
        "operationId": "getCapabilities",
        "summary": "What this caller may do",
        "description": "Call this first. Reports your tier, permissions and rate limits so you do not have to find them by trial and error.",
        "responses": {
          "200": {
            "description": "Capabilities of the current caller",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/plan": {
      "post": {
        "operationId": "planRoute",
        "summary": "Plan a route between two points",
        "description": "Runs the planner's own route logic: sea always avoided for inland voyages, the Waddenzee treated as inland water with the leg split at the nearest gateway, and estuary shipping (Vlissingen to the Belgian coast) only when allow_estuary is true. from, to and via must be coordinates — take them from /api/v1/locations. The response is the planner's route object; pass it unchanged to /api/v1/voyage-plan. The vessel you route with is the vessel the document describes: anything you leave out is filled from the planner's default inland cargo vessel, for the route and for the voyage plan alike.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "from",
                  "to"
                ],
                "properties": {
                  "from": {
                    "$ref": "#/components/schemas/Point"
                  },
                  "to": {
                    "$ref": "#/components/schemas/Point"
                  },
                  "via": {
                    "type": "array",
                    "maxItems": 10,
                    "items": {
                      "$ref": "#/components/schemas/Point"
                    }
                  },
                  "vessel": {
                    "$ref": "#/components/schemas/Vessel"
                  },
                  "allow_estuary": {
                    "type": "boolean",
                    "default": false,
                    "description": "Permit the sanctioned sea leg between the Rede van Vlissingen and the Belgian coastal ports."
                  }
                }
              },
              "example": {
                "from": {
                  "lat": 51.905,
                  "lon": 4.49,
                  "name": "Rotterdam, Nieuwe Maas"
                },
                "to": {
                  "lat": 51.855,
                  "lon": 5.86,
                  "name": "Nijmegen, Waal"
                },
                "vessel": {
                  "vessel_type": "2",
                  "length": 110,
                  "width": 11.45,
                  "draught": 3.2,
                  "air_draft": 6.2
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Route found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "from/to given as place names (use /api/v1/locations), or invalid coordinates"
          },
          "429": {
            "description": "Rate limited. Honour Retry-After."
          },
          "422": {
            "description": "No route connects these points for this vessel. The response repeats the resolved from/to, which is usually where the problem is."
          },
          "424": {
            "description": "The routing service refused the request; upstream_status and upstream_detail say why."
          }
        }
      }
    },
    "/api/v1/voyage-plan": {
      "post": {
        "operationId": "createVoyagePlanPdf",
        "summary": "Produce the voyage plan as PDF",
        "description": "Pass the object returned by /api/v1/plan. Anonymous callers receive a watermarked demo. A token whose account has no subscription receives 402. A subscribed account receives the clean document.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "route"
                ],
                "properties": {
                  "route": {
                    "type": "object",
                    "description": "The `route` object from /api/v1/plan, unchanged"
                  },
                  "from": {
                    "$ref": "#/components/schemas/Place"
                  },
                  "to": {
                    "$ref": "#/components/schemas/Place"
                  },
                  "voyagePlan": {
                    "type": "object",
                    "description": "Optional fields for the document that the planner cannot know: cargo (tonnes), draftFwd/draftAft (m), master/crew, specialNotes. Without them the document shows the template placeholders a person would fill in the wizard.",
                    "properties": {
                      "cargo": {
                        "type": "number"
                      },
                      "draftFwd": {
                        "type": "number"
                      },
                      "draftAft": {
                        "type": "number"
                      },
                      "master": {
                        "type": "string"
                      },
                      "specialNotes": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The voyage plan",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "402": {
            "description": "A subscription is required for this account"
          },
          "429": {
            "description": "Rate limited. Honour Retry-After."
          }
        }
      }
    },
    "/api/v1/auth/device": {
      "post": {
        "operationId": "startDeviceAuthorization",
        "summary": "Ask for a code the user can approve",
        "description": "Returns a short user_code to show the person you act for, and a device_code you keep secret. Codes expire after 15 minutes.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "How you should appear on the approval screen, e.g. the assistant's name."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Codes issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited"
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "operationId": "pollDeviceToken",
        "summary": "Collect the token once approved",
        "description": "Poll no faster than the interval returned by /api/v1/auth/device. A device_code yields a token exactly once.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "device_code"
                ],
                "properties": {
                  "device_code": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Approved; the token is in the response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "428": {
            "description": "Not approved yet. Keep polling at the stated interval."
          },
          "400": {
            "description": "Denied, expired, already redeemed, or an unknown device_code"
          }
        }
      }
    },
    "/api/v1/locations": {
      "get": {
        "operationId": "searchLocations",
        "summary": "Find candidate places on the waterways",
        "description": "Returns the same list a person sees in the planner: every hit snapped to the nearest navigable waterway for the vessel, deduplicated. The planner never picks a hit on its own — a city name resolves to a centre with no water — and neither should you. Choose like a skipper: match the harbour AND the town (\"Houthaven\" alone also returns Zutphen and Oosterhout); read details.waterway, where D9.70 is a depth code and H4.50 a bridge height, against your draught and air draft; and treat a railway station snapped to a small city river as not a cargo endpoint. An empty list means try a shorter or alternative name: \"Oude Houthaven Amsterdam\" found nothing, \"Oude Houthaven\" did.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            },
            "example": "Nijmegen"
          }
        ],
        "responses": {
          "200": {
            "description": "Candidates (may be empty)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited; each search fans out into up to 25 upstream calls"
          }
        }
      }
    }
  }
}
