{"openapi":"3.1.0","info":{"title":"Loonaut API","version":"1.0.0","summary":"Find the nearest public toilet.","description":"Free, no account. Every response with toilet data carries an attribution object: © OpenStreetMap contributors, ODbL-1.0. Errors use RFC 9457 application/problem+json. Each IP gets 60 requests per minute, and 300 for listToiletsInArea. Responses carry RateLimit headers. The version is part of the path. A change that breaks clients gets a new path. Before an operation goes away, it is marked deprecated here and its answers carry the Deprecation and Sunset headers. No operation is deprecated today."},"servers":[{"url":"https://loonaut.com"}],"security":[],"externalDocs":{"description":"Developer guide: quickstart, limits, errors and versioning.","url":"https://loonaut.com/developers"},"paths":{"/api/v1/toilets/nearest":{"get":{"operationId":"findToiletsNear","summary":"Find the nearest public toilets","description":"Use this when the user asks for the nearest public toilet. Send place (text) or lat and lon. When both are missing the answer has status needs_location.","parameters":[{"name":"place","in":"query","description":"Street, station, landmark or town, for example Darmstadt Hauptbahnhof.","schema":{"type":"string","minLength":2,"maxLength":200}},{"name":"country","in":"query","description":"ISO 3166-1 alpha-2 code. Narrows the place lookup.","schema":{"type":"string","pattern":"^[A-Za-z]{2}$"}},{"name":"lat","in":"query","description":"Latitude, WGS84. Send with lon.","schema":{"type":"number","minimum":-90,"maximum":90}},{"name":"lon","in":"query","description":"Longitude, WGS84. Send with lat.","schema":{"type":"number","minimum":-180,"maximum":180}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":20,"default":5}},{"name":"open_now","in":"query","description":"Drop toilets that are closed now. Toilets with unknown hours stay in the list and show open_now unknown.","schema":{"type":"boolean"}},{"name":"wheelchair","in":"query","description":"Only wheelchair-accessible toilets.","schema":{"type":"boolean"}},{"name":"changing_table","in":"query","description":"Only toilets with a changing table.","schema":{"type":"boolean"}},{"name":"free","in":"query","description":"Only toilets known to be free of charge.","schema":{"type":"boolean"}},{"name":"no_purchase_needed","in":"query","description":"Only toilets that need no purchase: public, permissive or scheme access.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"Toilets sorted by distance, or a status that asks for a location.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Deprecation":{"$ref":"#/components/headers/Deprecation"},"Sunset":{"$ref":"#/components/headers/Sunset"},"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FindResult"}}}},"400":{"description":"A parameter is invalid. invalid_params lists each one.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Server error.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The place lookup or the database is unavailable. Wait Retry-After seconds. A failed place lookup also accepts lat and lon.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets":{"get":{"operationId":"listToiletsInArea","summary":"List the toilets in a map area","description":"Use this to fill a map: all toilets in a box, the ones nearest to its centre first. Use /api/v1/toilets/nearest to find the toilet nearest to a person. A map asks for a new box after each move, so this endpoint has its own limit of 300 requests per minute per IP.","parameters":[{"name":"bbox","in":"query","required":true,"description":"West, south, east and north in decimal degrees (WGS84). West is less than east. Each side is at most 0.2 degrees. A larger box can get status 400.","schema":{"type":"string","pattern":"^-?\\d+(\\.\\d+)?(,-?\\d+(\\.\\d+)?){3}$"},"example":"8.64,49.87,8.66,49.88"},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":100}},{"name":"open_now","in":"query","description":"Drop toilets that are closed now. Toilets with unknown hours stay in the list and show open_now unknown.","schema":{"type":"boolean"}},{"name":"wheelchair","in":"query","description":"Only wheelchair-accessible toilets.","schema":{"type":"boolean"}},{"name":"changing_table","in":"query","description":"Only toilets with a changing table.","schema":{"type":"boolean"}},{"name":"free","in":"query","description":"Only toilets known to be free of charge.","schema":{"type":"boolean"}},{"name":"no_purchase_needed","in":"query","description":"Only toilets that need no purchase: public, permissive or scheme access.","schema":{"type":"boolean"}}],"responses":{"200":{"description":"The toilets in the box. A cache may keep the answer for 5 minutes.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Deprecation":{"$ref":"#/components/headers/Deprecation"},"Sunset":{"$ref":"#/components/headers/Sunset"},"Cache-Control":{"schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AreaResult"}}}},"400":{"description":"A parameter is invalid or the box is too large. invalid_params lists each one.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Server error.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The place lookup or the database is unavailable. Wait Retry-After seconds. A failed place lookup also accepts lat and lon.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/submissions":{"post":{"operationId":"submitToilet","summary":"Propose a new toilet","description":"For the web page of this site only. It needs a Turnstile token unless an account is signed in. A moderator reviews the proposal before it goes live.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["lat","lon"],"properties":{"name":{"type":"string","maxLength":120},"lat":{"type":"number","minimum":-90,"maximum":90},"lon":{"type":"number","minimum":-180,"maximum":180},"access":{"type":"string","enum":["public","customers","permissive","unknown"]},"fee":{"type":"string","enum":["free","fee","unknown"]},"opening_hours":{"type":"string","maxLength":200,"description":"OpenStreetMap opening_hours syntax."},"wheelchair":{"type":"string","enum":["yes","limited","no","unknown"]},"changing_table":{"type":"string","enum":["yes","no","unknown"]},"note":{"type":"string","maxLength":500},"turnstile_token":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token from the page of this site. A signed-in account needs none."}}}}}},"responses":{"201":{"description":"The proposal is saved and waits for a moderator.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the Turnstile check failed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Contributions are off.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}/confirmations":{"post":{"operationId":"confirmToilet","summary":"Confirm that a toilet is still there","description":"For the web page of this site only. It needs a Turnstile token unless an account is signed in. One confirmation per toilet and address per day. The answer holds the time of the confirmation and the count of confirmations in the last 12 months.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"turnstile_token":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token from the page of this site. A signed-in account needs none."}}}}}},"responses":{"201":{"description":"The confirmation is saved.","content":{"application/json":{"schema":{"type":"object","required":["id","last_confirmed_at","confirmations"],"properties":{"id":{"type":"string"},"last_confirmed_at":{"type":"string","format":"date-time"},"confirmations":{"type":"integer","minimum":1}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the Turnstile check failed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No toilet has this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Contributions are off.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}/ratings":{"post":{"operationId":"rateToilet","summary":"Rate how clean a toilet is","description":"For the web page of this site only. It needs a Turnstile token unless an account is signed in. A rating needs no confirmation. Each person has one rating per toilet, and the latest rating counts. The answer holds the new average and count. One address adds one new rater per toilet per day.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rating"],"properties":{"device_id":{"type":"string","pattern":"^[0-9a-f]{32}$","description":"Random number that the browser makes once and keeps. The server stores only its hash. A signed-in account needs none."},"rating":{"type":"integer","minimum":1,"maximum":5,"description":"1 is dirty. 5 is spotless."},"turnstile_token":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token from the page of this site. A signed-in account needs none."}}}}}},"responses":{"201":{"description":"The rating is saved and counted.","content":{"application/json":{"schema":{"type":"object","required":["id","rating","cleanliness"],"properties":{"id":{"type":"string"},"rating":{"type":"integer","minimum":1,"maximum":5},"cleanliness":{"type":"object","required":["average","count"],"additionalProperties":false,"properties":{"average":{"type":"number","minimum":1,"maximum":5},"count":{"type":"integer","minimum":1}}}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the Turnstile check failed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No toilet has this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Contributions are off.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}/reports":{"post":{"operationId":"reportToilet","summary":"Report a problem with a toilet","description":"For the web page of this site only. It needs a Turnstile token. A report about an OpenStreetMap toilet can become an anonymous OpenStreetMap Note.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["gone","closed","wrong_place","wrong_hours"]},"note":{"type":"string","maxLength":500},"turnstile_token":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token from the page of this site. A signed-in account needs none."}}}}}},"responses":{"201":{"description":"The report is saved and waits for a moderator.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the Turnstile check failed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No toilet has this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Contributions are off.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}/claims":{"post":{"operationId":"claimToilet","summary":"Claim a toilet for a business","description":"For the web page of this site only. It needs a signed-in account. A moderator reviews each claim. An approved claim lets the business buy a Verified Partner listing. A listing never changes the order of results.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["business_name","contact_email"],"properties":{"business_name":{"type":"string","minLength":2,"maxLength":80},"contact_email":{"type":"string","format":"email","maxLength":200},"contact_phone":{"type":"string","maxLength":40},"website":{"type":"string","format":"uri","maxLength":200},"note":{"type":"string","maxLength":500}}}}}},"security":[{"session":[]}],"responses":{"201":{"description":"The claim is saved and waits for a moderator.","content":{"application/json":{"schema":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending"]}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No account is signed in.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No toilet has this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"This account already claimed the toilet, or another business holds it.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/partner/listings/{id}":{"patch":{"operationId":"updatePartnerProfile","summary":"Change the conditions, the opening hours and the live switch of a listing","description":"For the web page of this site only. It needs a signed-in account that owns the listing, an approved claim and a running plan. Send any of the three fields. open_now false closes the toilet for 12 hours, then it follows the hours again. open_now true ends that at once. Conditions and hours are statements of the business. They never change the order of results.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^pl_[0-9a-f]{32}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":[],"properties":{"conditions":{"anyOf":[{"type":"string","enum":["everyone","customers"]},{"type":"null"}],"description":"What the business says about who may use the toilet. everyone: free for everyone. customers: customers only. Null when the business said nothing. It is the word of the business, not a checked fact."},"opening_hours":{"type":["string","null"],"maxLength":200,"description":"OpenStreetMap opening_hours syntax, for example Mo-Fr 08:00-18:00. An empty string or null clears it."},"open_now":{"type":"boolean"}}}}}},"security":[{"session":[]}],"responses":{"200":{"description":"The profile is saved.","content":{"application/json":{"schema":{"type":"object","required":["conditions","opening_hours","closed_until","open_now"],"properties":{"conditions":{"anyOf":[{"type":"string","enum":["everyone","customers"]},{"type":"null"}],"description":"What the business says about who may use the toilet. everyone: free for everyone. customers: customers only. Null when the business said nothing. It is the word of the business, not a checked fact."},"opening_hours":{"type":["string","null"]},"closed_until":{"type":"integer","description":"Milliseconds since the epoch. 0 when the live switch is on."},"open_now":{"type":"boolean","description":"The state of the live switch. false: closed for now."}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No account is signed in.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The account has no listing with this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The claim is not approved, or the listing has no running plan.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/billing/checkout":{"post":{"operationId":"startCheckout","summary":"Start the checkout of a Verified Partner subscription","description":"For the web page of this site only. It needs a signed-in account and an approved claim. The answer holds the address of a Checkout page that Stripe hosts. Prices are net. Stripe adds VAT.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["listing","tier"],"properties":{"listing":{"type":"string","pattern":"^pl_[0-9a-f]{32}$"},"tier":{"type":"string","enum":["basic","plus"]}}}}}},"security":[{"session":[]}],"responses":{"200":{"description":"The Stripe Checkout address.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"The Stripe Checkout address."}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No account is signed in.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The account has no listing with this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The claim is not approved, or a subscription or a checkout exists.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Stripe did not answer.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Partner payments are not open.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/billing/portal":{"post":{"operationId":"openBillingPortal","summary":"Open the billing portal of a listing","description":"For the web page of this site only. It needs a signed-in account that holds a subscription. The portal shows invoices, the payment method, the plan and the cancellation.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["listing"],"properties":{"listing":{"type":"string","pattern":"^pl_[0-9a-f]{32}$"}}}}}},"security":[{"session":[]}],"responses":{"200":{"description":"The Stripe portal address.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"The Stripe portal address."}}}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No account is signed in.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"The account has no listing with this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"The claim is not approved, or no subscription exists.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Stripe did not answer.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Partner payments are not available.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/billing/webhook":{"post":{"operationId":"receiveStripeEvent","summary":"Receive a Stripe event","description":"For Stripe only. The Stripe-Signature header must carry a valid signature of the raw body. The handler reads the subscription from Stripe and does not trust the event body. A repeated event id changes nothing.","security":[{"stripeSignature":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"The event is processed, or it was processed before.","content":{"application/json":{"schema":{"type":"object","required":["received"],"properties":{"received":{"const":true},"duplicate":{"const":true}}}}}},"400":{"description":"The signature or the event is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"413":{"description":"The body is larger than 512 KB.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"Stripe did not answer. Stripe sends the event again.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Partner payments are not available.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}/votes":{"post":{"operationId":"correctToilet","summary":"Correct facts about a toilet","description":"For the web page of this site only. It needs a Turnstile token unless an account is signed in. Each person has one vote per fact, and the latest vote counts. A value replaces the shown value when it gets more than half of all votes in the last 24 months and at least 2 votes. The source value counts as one vote. On a tie the shown value stays. A majority for existence gone hides nothing: a moderator decides. One address adds one new voter per toilet per day.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["votes"],"properties":{"device_id":{"type":"string","pattern":"^[0-9a-f]{32}$","description":"Random number that the browser makes once and keeps. The server stores only its hash. A signed-in account needs none."},"votes":{"type":"array","minItems":1,"maxItems":10,"items":{"type":"object","required":["attribute","value"],"properties":{"attribute":{"type":"string","enum":["access","fee","fee_amount","wheelchair","changing_table","gender","opening_hours","name","operator","existence"]},"value":{"type":"string","maxLength":200,"description":"access: public, permissive, customers or scheme. fee: free or paid. fee_amount: amount and ISO 4217 code, for example 0.50 EUR. wheelchair: yes, limited or no. changing_table: yes or no. gender: unisex, female and male, separated by commas. opening_hours: OpenStreetMap opening_hours syntax. name and operator: text. existence: yes or gone."}}}},"turnstile_token":{"type":"string","maxLength":2048,"description":"Cloudflare Turnstile token from the page of this site. A signed-in account needs none."}}}}}},"responses":{"201":{"description":"The votes are saved and counted.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"description":"The body is invalid.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"The request came from another origin, or the Turnstile check failed.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"No toilet has this id.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"Contributions are off.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}},"/api/v1/toilets/{id}":{"get":{"operationId":"getToilet","summary":"Get one toilet","description":"Use this when the user asks about one toilet from an earlier answer: hours, access rule, last confirmation, a photo.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[A-Za-z0-9_-]{1,64}$"}}],"responses":{"200":{"description":"The toilet.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Deprecation":{"$ref":"#/components/headers/Deprecation"},"Sunset":{"$ref":"#/components/headers/Sunset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ToiletResult"}}}},"404":{"description":"No toilet has this id, or a moderator marked it gone.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Rate limit reached. Wait Retry-After seconds.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimit-Policy"},"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"500":{"description":"Server error.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimit-Limit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimit-Remaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimit-Reset"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"503":{"description":"The place lookup or the database is unavailable. Wait Retry-After seconds. A failed place lookup also accepts lat and lon.","headers":{"Retry-After":{"$ref":"#/components/headers/Retry-After"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}}}}},"components":{"schemas":{"Attribution":{"type":"object","required":["text","license","url","license_notice_url"],"additionalProperties":false,"properties":{"text":{"type":"string","examples":["© OpenStreetMap contributors"]},"license":{"type":"string","examples":["ODbL-1.0"]},"url":{"type":"string","format":"uri"},"license_notice_url":{"type":"string","format":"uri","description":"The page that lists every source with its licence and offers the data dumps."}}},"FindResult":{"type":"object","required":["status","origin","results","attribution","display_note"],"additionalProperties":false,"properties":{"status":{"type":"string","enum":["ok","no_results","needs_location","place_not_found"],"description":"needs_location and place_not_found are not errors. Ask the user the question in ask_user."},"origin":{"anyOf":[{"type":"object","required":["lat","lon","label","source"],"additionalProperties":false,"properties":{"lat":{"type":"number"},"lon":{"type":"number"},"label":{"type":"string","description":"The place and its city. A position without a name shows its coordinates.","examples":["Luisenplatz, Darmstadt"]},"source":{"type":"string","enum":["place","coordinates","host_hint"]}}},{"type":"null"}]},"results":{"type":"array","items":{"type":"object","required":["id","name","lat","lon","access","access_rule","scheme_name","fee","opening_hours","open_now","closes_at","wheelchair","changing_table","gender","operator","last_confirmed_at","verification_state","confirmations","cleanliness","confidence","source","partner","provenance","url","directions","geo","distance_m","walk_min","bearing"],"additionalProperties":false,"properties":{"id":{"type":"string","examples":["lt_01J9ZQ"]},"name":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"access":{"type":"string","enum":["public","permissive","customers","scheme","unknown"],"description":"Who may use the toilet. public: anyone. permissive: anyone, no purchase needed. customers: customers only. scheme: a public scheme opens a business toilet to everyone."},"access_rule":{"type":"string","enum":["free","fee","customers","key","scheme","unknown"],"description":"free and fee: public toilet. customers: purchase or customer status needed. key: key needed. scheme: a public scheme opens a business toilet to everyone. unknown: fee not known."},"scheme_name":{"type":["string","null"]},"fee":{"type":"object","required":["required","amount","currency"],"additionalProperties":false,"properties":{"required":{"type":["boolean","null"]},"amount":{"type":["number","null"]},"currency":{"type":["string","null"]}}},"opening_hours":{"type":["string","null"],"description":"OpenStreetMap opening_hours syntax."},"open_now":{"description":"Evaluated at the toilet's local time. The value unknown means the source has no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset. Null when unknown or when it never closes."},"wheelchair":{"type":"string","enum":["yes","limited","no","unknown"]},"changing_table":{"type":"string","enum":["yes","no","unknown"]},"gender":{"type":"array","items":{"type":"string","enum":["unisex","female","male"]},"description":"Who the toilet is for. Empty when not known."},"operator":{"type":["string","null"]},"last_confirmed_at":{"type":["string","null"]},"verification_state":{"type":"string","enum":["unverified","source_claimed","community_confirmed","partner_confirmed","disputed"],"description":"disputed: an open user report says the toilet is gone, closed or wrong."},"confirmations":{"type":"integer","minimum":0,"description":"User confirmations in the last 12 months."},"cleanliness":{"anyOf":[{"type":"object","required":["average","count"],"additionalProperties":false,"properties":{"average":{"type":"number","minimum":1,"maximum":5},"count":{"type":"integer","minimum":1}}},{"type":"null"}],"description":"User ratings from 1 (dirty) to 5 (spotless). Each person has one rating, and the latest counts. Null when nobody rated it."},"confidence":{"type":"string","enum":["high","medium","low"]},"source":{"type":"string"},"partner":{"anyOf":[{"type":"null"},{"type":"object","required":["name","tier","conditions","opening_hours","open_now","closes_at"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"Business name that the partner gave and a moderator approved."},"tier":{"type":"string","enum":["basic","plus"]},"conditions":{"anyOf":[{"type":"string","enum":["everyone","customers"]},{"type":"null"}],"description":"What the business says about who may use the toilet. everyone: free for everyone. customers: customers only. Null when the business said nothing. It is the word of the business, not a checked fact."},"opening_hours":{"type":["string","null"],"description":"Opening hours that the business entered, in OpenStreetMap opening_hours syntax. Null when it entered none."},"open_now":{"description":"Open state from the hours of the business and its live switch, at the toilet's local time. false: the business closed the toilet for now, or its hours say closed. unknown: the business gave no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset, from the hours of the business. Null when unknown or when it never closes."}}}],"description":"Set when the business behind the toilet pays for a Verified Partner listing. It buys visibility and a label. It never changes the order of the results. Show it as a label only. Conditions, opening_hours and open_now are statements of the business. The toilet's own fields (access, opening_hours, open_now) stay as they are."},"provenance":{"type":"object","required":["source","checked","decided"],"additionalProperties":false,"description":"Where the facts come from. People can correct every fact. A value that most people vote for replaces the source value. Every fact comes from the source, unless decided lists it.","properties":{"source":{"type":"string","description":"Source of the toilet record, for example osm."},"checked":{"type":["string","null"],"description":"Month of the last check in the source, for example 2025-06."},"decided":{"type":"object","description":"Facts that people or a moderator decided. The keys are access, fee, fee_amount, wheelchair, changing_table, gender, opening_hours, name, operator and existence.","propertyNames":{"enum":["access","fee","fee_amount","wheelchair","changing_table","gender","opening_hours","name","operator","existence"]},"additionalProperties":{"type":"object","required":["by","votes_for","votes_total","as_of"],"additionalProperties":false,"properties":{"by":{"type":"string","enum":["votes","moderator"],"description":"votes: most people voted for the shown value. moderator: a moderator locked the value."},"votes_for":{"type":["integer","null"],"minimum":0,"description":"People who voted for the shown value. The source value counts as one vote. Null for a moderator."},"votes_total":{"type":["integer","null"],"minimum":0,"description":"All votes in the last 24 months, including the source value."},"as_of":{"type":"string","description":"Date of the last count."}}}}}},"url":{"type":"string","format":"uri"},"directions":{"type":"object","required":["google","apple"],"additionalProperties":false,"properties":{"google":{"type":"string","format":"uri"},"apple":{"type":"string","format":"uri"}}},"geo":{"type":"string","description":"geo: URI."},"distance_m":{"type":"integer","minimum":0},"walk_min":{"type":"integer","minimum":1},"bearing":{"type":"integer","minimum":0,"maximum":359,"description":"Degrees from north."}}}},"bbox":{"type":"array","items":{"type":"number"},"minItems":4,"maxItems":4,"description":"west, south, east, north of the origin and the results."},"ask_user":{"type":"string"},"attribution":{"type":"object","required":["text","license","url","license_notice_url"],"additionalProperties":false,"properties":{"text":{"type":"string","examples":["© OpenStreetMap contributors"]},"license":{"type":"string","examples":["ODbL-1.0"]},"url":{"type":"string","format":"uri"},"license_notice_url":{"type":"string","format":"uri","description":"The page that lists every source with its licence and offers the data dumps."}}},"display_note":{"type":"string"}}},"AreaResult":{"type":"object","required":["results","truncated","attribution","display_note"],"additionalProperties":false,"properties":{"results":{"type":"array","items":{"type":"object","required":["id","name","lat","lon","access","access_rule","scheme_name","fee","opening_hours","open_now","closes_at","wheelchair","changing_table","gender","operator","last_confirmed_at","verification_state","confirmations","cleanliness","confidence","source","partner","provenance","url","directions","geo"],"additionalProperties":false,"properties":{"id":{"type":"string","examples":["lt_01J9ZQ"]},"name":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"access":{"type":"string","enum":["public","permissive","customers","scheme","unknown"],"description":"Who may use the toilet. public: anyone. permissive: anyone, no purchase needed. customers: customers only. scheme: a public scheme opens a business toilet to everyone."},"access_rule":{"type":"string","enum":["free","fee","customers","key","scheme","unknown"],"description":"free and fee: public toilet. customers: purchase or customer status needed. key: key needed. scheme: a public scheme opens a business toilet to everyone. unknown: fee not known."},"scheme_name":{"type":["string","null"]},"fee":{"type":"object","required":["required","amount","currency"],"additionalProperties":false,"properties":{"required":{"type":["boolean","null"]},"amount":{"type":["number","null"]},"currency":{"type":["string","null"]}}},"opening_hours":{"type":["string","null"],"description":"OpenStreetMap opening_hours syntax."},"open_now":{"description":"Evaluated at the toilet's local time. The value unknown means the source has no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset. Null when unknown or when it never closes."},"wheelchair":{"type":"string","enum":["yes","limited","no","unknown"]},"changing_table":{"type":"string","enum":["yes","no","unknown"]},"gender":{"type":"array","items":{"type":"string","enum":["unisex","female","male"]},"description":"Who the toilet is for. Empty when not known."},"operator":{"type":["string","null"]},"last_confirmed_at":{"type":["string","null"]},"verification_state":{"type":"string","enum":["unverified","source_claimed","community_confirmed","partner_confirmed","disputed"],"description":"disputed: an open user report says the toilet is gone, closed or wrong."},"confirmations":{"type":"integer","minimum":0,"description":"User confirmations in the last 12 months."},"cleanliness":{"anyOf":[{"type":"object","required":["average","count"],"additionalProperties":false,"properties":{"average":{"type":"number","minimum":1,"maximum":5},"count":{"type":"integer","minimum":1}}},{"type":"null"}],"description":"User ratings from 1 (dirty) to 5 (spotless). Each person has one rating, and the latest counts. Null when nobody rated it."},"confidence":{"type":"string","enum":["high","medium","low"]},"source":{"type":"string"},"partner":{"anyOf":[{"type":"null"},{"type":"object","required":["name","tier","conditions","opening_hours","open_now","closes_at"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"Business name that the partner gave and a moderator approved."},"tier":{"type":"string","enum":["basic","plus"]},"conditions":{"anyOf":[{"type":"string","enum":["everyone","customers"]},{"type":"null"}],"description":"What the business says about who may use the toilet. everyone: free for everyone. customers: customers only. Null when the business said nothing. It is the word of the business, not a checked fact."},"opening_hours":{"type":["string","null"],"description":"Opening hours that the business entered, in OpenStreetMap opening_hours syntax. Null when it entered none."},"open_now":{"description":"Open state from the hours of the business and its live switch, at the toilet's local time. false: the business closed the toilet for now, or its hours say closed. unknown: the business gave no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset, from the hours of the business. Null when unknown or when it never closes."}}}],"description":"Set when the business behind the toilet pays for a Verified Partner listing. It buys visibility and a label. It never changes the order of the results. Show it as a label only. Conditions, opening_hours and open_now are statements of the business. The toilet's own fields (access, opening_hours, open_now) stay as they are."},"provenance":{"type":"object","required":["source","checked","decided"],"additionalProperties":false,"description":"Where the facts come from. People can correct every fact. A value that most people vote for replaces the source value. Every fact comes from the source, unless decided lists it.","properties":{"source":{"type":"string","description":"Source of the toilet record, for example osm."},"checked":{"type":["string","null"],"description":"Month of the last check in the source, for example 2025-06."},"decided":{"type":"object","description":"Facts that people or a moderator decided. The keys are access, fee, fee_amount, wheelchair, changing_table, gender, opening_hours, name, operator and existence.","propertyNames":{"enum":["access","fee","fee_amount","wheelchair","changing_table","gender","opening_hours","name","operator","existence"]},"additionalProperties":{"type":"object","required":["by","votes_for","votes_total","as_of"],"additionalProperties":false,"properties":{"by":{"type":"string","enum":["votes","moderator"],"description":"votes: most people voted for the shown value. moderator: a moderator locked the value."},"votes_for":{"type":["integer","null"],"minimum":0,"description":"People who voted for the shown value. The source value counts as one vote. Null for a moderator."},"votes_total":{"type":["integer","null"],"minimum":0,"description":"All votes in the last 24 months, including the source value."},"as_of":{"type":"string","description":"Date of the last count."}}}}}},"url":{"type":"string","format":"uri"},"directions":{"type":"object","required":["google","apple"],"additionalProperties":false,"properties":{"google":{"type":"string","format":"uri"},"apple":{"type":"string","format":"uri"}}},"geo":{"type":"string","description":"geo: URI."}}},"description":"Toilets in the box, the ones nearest to its centre first."},"truncated":{"type":"boolean","description":"True when the box holds more toilets than limit. Send a smaller box to see them."},"attribution":{"type":"object","required":["text","license","url","license_notice_url"],"additionalProperties":false,"properties":{"text":{"type":"string","examples":["© OpenStreetMap contributors"]},"license":{"type":"string","examples":["ODbL-1.0"]},"url":{"type":"string","format":"uri"},"license_notice_url":{"type":"string","format":"uri","description":"The page that lists every source with its licence and offers the data dumps."}}},"display_note":{"type":"string"}}},"ToiletResult":{"type":"object","required":["toilet","attribution","display_note"],"additionalProperties":false,"properties":{"toilet":{"type":"object","required":["id","name","lat","lon","access","access_rule","scheme_name","fee","opening_hours","open_now","closes_at","wheelchair","changing_table","gender","operator","last_confirmed_at","verification_state","confirmations","cleanliness","confidence","source","partner","provenance","url","directions","geo","image"],"additionalProperties":false,"properties":{"id":{"type":"string","examples":["lt_01J9ZQ"]},"name":{"type":["string","null"]},"lat":{"type":"number"},"lon":{"type":"number"},"access":{"type":"string","enum":["public","permissive","customers","scheme","unknown"],"description":"Who may use the toilet. public: anyone. permissive: anyone, no purchase needed. customers: customers only. scheme: a public scheme opens a business toilet to everyone."},"access_rule":{"type":"string","enum":["free","fee","customers","key","scheme","unknown"],"description":"free and fee: public toilet. customers: purchase or customer status needed. key: key needed. scheme: a public scheme opens a business toilet to everyone. unknown: fee not known."},"scheme_name":{"type":["string","null"]},"fee":{"type":"object","required":["required","amount","currency"],"additionalProperties":false,"properties":{"required":{"type":["boolean","null"]},"amount":{"type":["number","null"]},"currency":{"type":["string","null"]}}},"opening_hours":{"type":["string","null"],"description":"OpenStreetMap opening_hours syntax."},"open_now":{"description":"Evaluated at the toilet's local time. The value unknown means the source has no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset. Null when unknown or when it never closes."},"wheelchair":{"type":"string","enum":["yes","limited","no","unknown"]},"changing_table":{"type":"string","enum":["yes","no","unknown"]},"gender":{"type":"array","items":{"type":"string","enum":["unisex","female","male"]},"description":"Who the toilet is for. Empty when not known."},"operator":{"type":["string","null"]},"last_confirmed_at":{"type":["string","null"]},"verification_state":{"type":"string","enum":["unverified","source_claimed","community_confirmed","partner_confirmed","disputed"],"description":"disputed: an open user report says the toilet is gone, closed or wrong."},"confirmations":{"type":"integer","minimum":0,"description":"User confirmations in the last 12 months."},"cleanliness":{"anyOf":[{"type":"object","required":["average","count"],"additionalProperties":false,"properties":{"average":{"type":"number","minimum":1,"maximum":5},"count":{"type":"integer","minimum":1}}},{"type":"null"}],"description":"User ratings from 1 (dirty) to 5 (spotless). Each person has one rating, and the latest counts. Null when nobody rated it."},"confidence":{"type":"string","enum":["high","medium","low"]},"source":{"type":"string"},"partner":{"anyOf":[{"type":"null"},{"type":"object","required":["name","tier","conditions","opening_hours","open_now","closes_at"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"Business name that the partner gave and a moderator approved."},"tier":{"type":"string","enum":["basic","plus"]},"conditions":{"anyOf":[{"type":"string","enum":["everyone","customers"]},{"type":"null"}],"description":"What the business says about who may use the toilet. everyone: free for everyone. customers: customers only. Null when the business said nothing. It is the word of the business, not a checked fact."},"opening_hours":{"type":["string","null"],"description":"Opening hours that the business entered, in OpenStreetMap opening_hours syntax. Null when it entered none."},"open_now":{"description":"Open state from the hours of the business and its live switch, at the toilet's local time. false: the business closed the toilet for now, or its hours say closed. unknown: the business gave no usable hours.","anyOf":[{"type":"boolean"},{"const":"unknown"}]},"closes_at":{"type":["string","null"],"description":"Local closing time with UTC offset, from the hours of the business. Null when unknown or when it never closes."}}}],"description":"Set when the business behind the toilet pays for a Verified Partner listing. It buys visibility and a label. It never changes the order of the results. Show it as a label only. Conditions, opening_hours and open_now are statements of the business. The toilet's own fields (access, opening_hours, open_now) stay as they are."},"provenance":{"type":"object","required":["source","checked","decided"],"additionalProperties":false,"description":"Where the facts come from. People can correct every fact. A value that most people vote for replaces the source value. Every fact comes from the source, unless decided lists it.","properties":{"source":{"type":"string","description":"Source of the toilet record, for example osm."},"checked":{"type":["string","null"],"description":"Month of the last check in the source, for example 2025-06."},"decided":{"type":"object","description":"Facts that people or a moderator decided. The keys are access, fee, fee_amount, wheelchair, changing_table, gender, opening_hours, name, operator and existence.","propertyNames":{"enum":["access","fee","fee_amount","wheelchair","changing_table","gender","opening_hours","name","operator","existence"]},"additionalProperties":{"type":"object","required":["by","votes_for","votes_total","as_of"],"additionalProperties":false,"properties":{"by":{"type":"string","enum":["votes","moderator"],"description":"votes: most people voted for the shown value. moderator: a moderator locked the value."},"votes_for":{"type":["integer","null"],"minimum":0,"description":"People who voted for the shown value. The source value counts as one vote. Null for a moderator."},"votes_total":{"type":["integer","null"],"minimum":0,"description":"All votes in the last 24 months, including the source value."},"as_of":{"type":"string","description":"Date of the last count."}}}}}},"url":{"type":"string","format":"uri"},"directions":{"type":"object","required":["google","apple"],"additionalProperties":false,"properties":{"google":{"type":"string","format":"uri"},"apple":{"type":"string","format":"uri"}}},"geo":{"type":"string","description":"geo: URI."},"image":{"anyOf":[{"type":"object","required":["url","width","height","credit","licence","licence_url","source_url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","description":"The picture, served from this site. A client needs no other host."},"width":{"type":"integer","minimum":1},"height":{"type":"integer","minimum":1},"credit":{"type":"string","description":"Name of the author. Show it next to the photo."},"licence":{"type":"string","description":"Short name of the licence, for example CC BY-SA 4.0. Show it next to the photo."},"licence_url":{"type":["string","null"],"format":"uri","description":"Text of the licence. Null for a work in the public domain."},"source_url":{"type":"string","format":"uri","description":"Page of the picture at Wikimedia Commons or Panoramax."}}},{"type":"null"}],"description":"A photo of the toilet. OpenStreetMap links it from Wikimedia Commons or Panoramax. It is set only when the licence is free (CC0, public domain, CC BY, CC BY-SA, Etalab Open Licence) and the author is known. Else it is null. Show credit and licence next to the photo."}}},"attribution":{"type":"object","required":["text","license","url","license_notice_url"],"additionalProperties":false,"properties":{"text":{"type":"string","examples":["© OpenStreetMap contributors"]},"license":{"type":"string","examples":["ODbL-1.0"]},"url":{"type":"string","format":"uri"},"license_notice_url":{"type":"string","format":"uri","description":"The page that lists every source with its licence and offers the data dumps."}}},"display_note":{"type":"string"}}},"Problem":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","format":"uri"},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string","description":"How to fix the request."},"invalid_params":{"type":"array","items":{"type":"object","required":["name","reason"],"properties":{"name":{"type":"string"},"reason":{"type":"string"}}}},"retry_after_seconds":{"type":"integer"}}}},"securitySchemes":{"session":{"type":"apiKey","in":"cookie","name":"__Host-loonaut_session","description":"Set after a passkey sign-in on the web page of this site."},"stripeSignature":{"type":"apiKey","in":"header","name":"Stripe-Signature","description":"Stripe signs the raw body with the endpoint secret."}},"headers":{"RateLimit-Limit":{"description":"Requests allowed in the window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests left in the window.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the window resets.","schema":{"type":"integer"}},"RateLimit":{"description":"Structured form: remaining and reset.","schema":{"type":"string"}},"RateLimit-Policy":{"description":"Quota and window in seconds.","schema":{"type":"string"}},"Retry-After":{"description":"Seconds to wait.","schema":{"type":"integer"}},"Deprecation":{"description":"Sent only for a deprecated operation. The date from which the operation counts as deprecated (RFC 9745).","schema":{"type":"string"}},"Sunset":{"description":"Sent only for a deprecated operation. The date when the operation stops working (RFC 8594).","schema":{"type":"string"}}}}}