Overview
This API tells you what people wear outside in a city. It answers for the current hour and for each of the next days. Use it to show a hint such as "light jacket, closed shoes", or to help a traveller pack.
- What you get. For every populated place GeoNames lists with more than 15,000 people, every capital, and every city where we have cameras, the share of people who wear each value of five things: the upper layer, the lower body, footwear, a scarf and an open umbrella.
- Where it comes from. A weather model. Where we have street cameras in a city, what they see can correct the model.
- Where it lives. The base URL is
https://api.sleevecast.app. This page is athttps://api.sleevecast.app/guide/. The machine-readable description (OpenAPI 3.1) is athttps://api.sleevecast.app/guide/openapi.json. It needs no key. - Interactive reference. Read the API in your browser and try a call with your own key: /guide/reference/. It is served by this API and loads nothing from other sites.
- A typed client. Generate one from that document:
npx openapi-typescript https://api.sleevecast.app/guide/openapi.json
- The version. The contract is versioned. The latest version, and the date of the first frozen contract, are in the Changelog.
GET /v1/versionreturns the same number ascontract_version.
Every call to /v1 needs a key. The Quickstart shows your first three calls.
Quickstart
- Get a key. Keys are issued by hand, to one organisation. Write to the address under Support. Read the conditions of use before you ask. A key expires after the number of days in the table under Limits and caching, unless we agree otherwise.
- Find the city. Call
nearestwith a coordinate, and keep theslugof the city it returns. Round the coordinate to 2 decimals first (about 1 km). That is enough fornearest. You then never send your user's exact position, and the same area gives the same URL, so the day-long cache of the answer is used (see Limits and caching). - Ask for the answer. Call
/nowwith the slug, then/days. - Show the credits. Wherever you show our data, show the credits that
/v1/attributionlists. See Attribution. - Call from a server. A key in a web page or a mobile app is public to everyone who opens it. Keep it on your server and call us from there.
Set your key in the shell, then run the calls below. Each call shows the answer we recorded for it.
export SLEEVECAST_API_KEY=swk1_...your key...
Example: The city nearest to a point
curl -sS 'https://api.sleevecast.app/v1/cities/nearest?lat=-6.16&lon=39.19' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "zanzibar",
"geonames_id": 148730,
"name": "Zanzibar",
"country": "TZ",
"lat": -6.16394,
"lon": 39.19793,
"tz": "Africa/Dar_es_Salaam",
"admin1": "25",
"population": 403658,
"cameras": false
},
"distance_km": 1.0
}
Example: A city with camera evidence
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/now' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"at": "2026-10-26T14:00:00+01:00",
"summary": "Mixed: short sleeves 36%, light jackets 26%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.36,
"long_sleeve": 0.26,
"knit_hoodie": 0.12,
"light_jacket": 0.26,
"coat": 0.0,
"puffer": 0.0
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.01,
"closed_low": 0.98,
"boots": 0.01
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 38,
"basis": "model+cameras",
"confidence": "high",
"data_through": "2026-10-26T14:10:00+01:00",
"weather": {
"t": 17.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"cloud": 50,
"code": 1,
"issued_at": "2026-10-26T13:20:00+01:00",
"units": {
"t": "°C",
"feels": "°C",
"precip": "mm",
"wind": "km/h",
"cloud": "%"
}
},
"sources": [
"madrid-informo",
"met-norway",
"geonames"
]
}
Example: Three days in Madrid
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/days?days=3' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"days": [
{
"date": "2026-10-26",
"summary": "Mixed: short sleeves 36%, light jackets 26%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.36,
"long_sleeve": 0.26,
"knit_hoodie": 0.12,
"light_jacket": 0.26,
"coat": 0.0,
"puffer": 0.0
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.01,
"closed_low": 0.98,
"boots": 0.01
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 38,
"basis": "model+cameras",
"confidence": "high",
"weather": {
"t_min": 9.0,
"t_max": 21.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
},
{
"date": "2026-10-27",
"summary": "Mixed: light jackets 32%, sweaters and hoodies 24%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.08,
"long_sleeve": 0.23,
"knit_hoodie": 0.24,
"light_jacket": 0.32,
"coat": 0.09,
"puffer": 0.04
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.02,
"closed_low": 0.93,
"boots": 0.05
},
"scarf": {
"yes": 0.01,
"no": 0.99
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 54,
"basis": "model",
"confidence": "medium",
"weather": {
"t_min": 17.0,
"t_max": 17.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
},
{
"date": "2026-10-28",
"summary": "Mixed: light jackets 28%, coats 27%, closed shoes; umbrellas 30%",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.02,
"long_sleeve": 0.09,
"knit_hoodie": 0.16,
"light_jacket": 0.28,
"coat": 0.27,
"puffer": 0.18
},
"lower": {
"shorts": 0.02,
"skirt_short": 0.01,
"skirt_long": 0.02,
"trousers": 0.95
},
"footwear": {
"open": 0.01,
"closed_low": 0.87,
"boots": 0.12
},
"scarf": {
"yes": 0.08,
"no": 0.92
},
"umbrella_open": {
"yes": 0.3,
"no": 0.7
}
},
"index": 71,
"basis": "model",
"confidence": "medium",
"weather": {
"t_min": 11.0,
"t_max": 11.0,
"feels": 8.0,
"precip": 12.0,
"wind": 22.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
}
],
"sources": [
"madrid-informo",
"met-norway",
"geonames"
]
}
Keys
- Send the key in one way only: the header
Authorization: Bearer swk1_.... A key in a URL, in another header or in a cookie counts as no key. Never put it in a URL. - If a key ever went into a URL, we remove keys we recognise from URLs before logging, but treat a key that was ever sent in a URL as leaked: it stays in your logs and in your users' browsers. Ask us for a new key.
- One key per app or web page. A key that leaks can then be revoked without touching your other apps.
- A key expires. From its expiry date (UTC), calls fail with
api_key_expired. Ask for a new key before that day. - The four 401 codes tell you what is wrong with a key:
api_key_missing(no key sent),api_key_invalid(malformed, or not one we issued),api_key_expiredandapi_key_revoked. A 401 carries noRateLimit-*headers: with no accepted key there is no budget. - A rate limit is per key. See Limits and caching.
Cities
- A city has a slug and a GeoNames id. Use either in the path:
/v1/cities/madrid/nowor/v1/cities/3117735/now. The list shows both. - Store the slug. A slug does not change, and we never give it to another city. A city that leaves the catalogue is still found by its slug or id, and comes last in the list.
- Search the list with
q.qmatches the start of a name, ignoring case, accents and apostrophes.Sãofinds Sao Paulo,KolnandKoelnfind Köln, andXianfinds Xi'an. It also matches the start of the name's plain-ASCII spelling and of the slug. It does not search other names:MoskvaandMünchenfind nothing. - Page through the list with
limitandcursor. Send back thenext_cursorof a page as thecursorof the next one. It isnullon the last page. The most populous cities come first. Filter bycountrywith a two-letter code. nearestfinds the nearest city within 50 km of a point, or answers404with the codeno_city_nearby. It answers with a city, not with one of its districts. A district is still in the list and answers like any city. Round the coordinates to 2 decimals before you send them: it protects your users, and it makes the day-long cache of the answer useful.cameras: truemarks a city with our street cameras: what they see can correct its answers, field by field, once their labels pass our accuracy check.basissays whether an answer was corrected. Where it isfalse, the weather model answers alone.
Endpoints
All seven operations are GET. HEAD works too. Each is described below with its parameters, its answers and a recorded example.
GET /v1/cities/{city}/now: what people wear in the city this hour. Add?meta=truefor diagnostics.GET /v1/cities/{city}/days: today and the days after it, up to 7 days in all. Use it for packing.GET /v1/cities: list or search the cities.GET /v1/cities/nearest: the city nearest to a point.GET /v1/cities/{city}/history: what our cameras saw, day by day. A city without cameras has no items. It shows the shares of a field only for a finished day, only for a field whose camera labels have passed our accuracy check, and only from 10 people. The default range is the last 30 days. The longest range is 366 days; a longer one is a422.GET /v1/attribution: the credits to show.GET /v1/version: the versions behind the numbers.
GET /v1/cities/{city}/now
What people wear now
What people wear in the city now: the shares of each value of each field for the current local hour, from the weather model, which the city's licence-clean cameras can correct (basis says when).
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
city | path | yes | string | The city: its slug or its GeoNames id, as /v1/cities lists them. |
meta | query | no | boolean, default false | true adds meta to the answer: how it was made. Diagnostics, not for display. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | What people wear in the city in the current local hour. | NowOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's, or the city in it is unknown. (Check the path and, if it has a city, the city's slug or id.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.)
weather_unavailable: No stored weather forecast for this city covers the hour or the days asked for. (Wait as long as Retry-After says, which is 600 s, then retry.) | ErrorOut |
Example: No key sent
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/now'
Answer: 401
{
"error": {
"code": "api_key_missing",
"message": "Send the API key as 'Authorization: Bearer <key>'",
"request_id": "5f0c3e9a2b7d4e18a6c1d0f2b3e4a5c6"
}
}
Example: The key's budget is spent
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/now' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 429
{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key: 60 a minute; retry after 60 s",
"request_id": "5f0c3e9a2b7d4e18a6c1d0f2b3e4a5c6"
}
}
Example: No weather to answer with
curl -sS 'https://api.sleevecast.app/v1/cities/tokyo/now' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 503
{
"error": {
"code": "weather_unavailable",
"message": "no stored forecast covers this hour",
"request_id": "5f0c3e9a2b7d4e18a6c1d0f2b3e4a5c6"
}
}
Example: A city with camera evidence
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/now' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"at": "2026-10-26T14:00:00+01:00",
"summary": "Mixed: short sleeves 36%, light jackets 26%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.36,
"long_sleeve": 0.26,
"knit_hoodie": 0.12,
"light_jacket": 0.26,
"coat": 0.0,
"puffer": 0.0
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.01,
"closed_low": 0.98,
"boots": 0.01
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 38,
"basis": "model+cameras",
"confidence": "high",
"data_through": "2026-10-26T14:10:00+01:00",
"weather": {
"t": 17.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"cloud": 50,
"code": 1,
"issued_at": "2026-10-26T13:20:00+01:00",
"units": {
"t": "°C",
"feels": "°C",
"precip": "mm",
"wind": "km/h",
"cloud": "%"
}
},
"sources": [
"madrid-informo",
"met-norway",
"geonames"
]
}
Example: The same, with meta=true
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/now?meta=true' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"at": "2026-10-26T14:00:00+01:00",
"summary": "Mixed: short sleeves 36%, light jackets 26%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.36,
"long_sleeve": 0.26,
"knit_hoodie": 0.12,
"light_jacket": 0.26,
"coat": 0.0,
"puffer": 0.0
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.01,
"closed_low": 0.98,
"boots": 0.01
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 38,
"basis": "model+cameras",
"confidence": "high",
"data_through": "2026-10-26T14:10:00+01:00",
"weather": {
"t": 17.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"cloud": 50,
"code": 1,
"issued_at": "2026-10-26T13:20:00+01:00",
"units": {
"t": "°C",
"feels": "°C",
"precip": "mm",
"wind": "km/h",
"cloud": "%"
}
},
"sources": [
"madrid-informo",
"met-norway",
"geonames"
],
"meta": {
"model_version": "m1.b804897b",
"rules_version": "a1.3c5f046b",
"catalog_version": "gn15k.8f3e7ea7",
"labeler": "openrouter:google/gemini-3.1-flash-lite",
"taxonomy_version": "v1",
"unit": {
"key": "iw:catholic_europe",
"calibrated": true,
"shift_c": -1.5
},
"prior_strength": 20.0,
"window": {
"from": "2026-10-26T12:00:00+01:00",
"hours": 3,
"n_cameras": 1,
"n": {
"upper": 23,
"lower": 9,
"footwear": 23,
"scarf": 23,
"umbrella_open": 23
},
"camera_weight": {
"upper": 0.53,
"lower": 0.0,
"footwear": 0.53,
"scarf": 0.53,
"umbrella_open": 0.53
}
},
"weather_point": {
"slug": "madrid",
"distance_km": 0.0
}
}
}
Example: A city the model answers alone
curl -sS 'https://api.sleevecast.app/v1/cities/sydney/now' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "sydney",
"geonames_id": 2147714,
"name": "Sydney",
"country": "AU",
"lat": -33.86785,
"lon": 151.20732,
"tz": "Australia/Sydney"
},
"at": "2026-10-26T15:00:00+11:00",
"summary": "Mixed: long sleeves 33%, light jackets 22%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.01,
"short_sleeve": 0.18,
"long_sleeve": 0.33,
"knit_hoodie": 0.21,
"light_jacket": 0.22,
"coat": 0.04,
"puffer": 0.01
},
"lower": {
"shorts": 0.07,
"skirt_short": 0.02,
"skirt_long": 0.03,
"trousers": 0.88
},
"footwear": {
"open": 0.02,
"closed_low": 0.94,
"boots": 0.04
},
"scarf": {
"yes": 0.01,
"no": 0.99
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 44,
"basis": "model",
"confidence": "low",
"data_through": null,
"weather": {
"t": 19.4,
"feels": 18.3,
"precip": 0.0,
"wind": 17.1,
"cloud": 11,
"code": 0,
"issued_at": "2026-10-26T14:05:00+11:00",
"units": {
"t": "°C",
"feels": "°C",
"precip": "mm",
"wind": "km/h",
"cloud": "%"
}
},
"sources": [
"met-norway",
"geonames"
]
}
GET /v1/cities/{city}/days
What people wear today and on the next days
What people wear in the city today and on the days after it, for packing: one item per local date, today first, over the day's outdoor hours. Today's item can carry the cameras' correction where the city has licence-clean cameras; later days are the weather model's.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
city | path | yes | string | The city: its slug or its GeoNames id, as /v1/cities lists them. |
days | query | no | integer, 1 to 7, default 7 | How many local dates to answer, today first, from 1 to 7. |
meta | query | no | boolean, default false | true adds meta to the answer: how it was made. Diagnostics, not for display. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | What people wear in the city on each of the days asked for, today first. | DaysOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's, or the city in it is unknown. (Check the path and, if it has a city, the city's slug or id.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.)
weather_unavailable: No stored weather forecast for this city covers the hour or the days asked for. (Wait as long as Retry-After says, which is 600 s, then retry.) | ErrorOut |
Example: Three days in Madrid
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/days?days=3' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"days": [
{
"date": "2026-10-26",
"summary": "Mixed: short sleeves 36%, light jackets 26%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.36,
"long_sleeve": 0.26,
"knit_hoodie": 0.12,
"light_jacket": 0.26,
"coat": 0.0,
"puffer": 0.0
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.01,
"closed_low": 0.98,
"boots": 0.01
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 38,
"basis": "model+cameras",
"confidence": "high",
"weather": {
"t_min": 9.0,
"t_max": 21.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
},
{
"date": "2026-10-27",
"summary": "Mixed: light jackets 32%, sweaters and hoodies 24%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.08,
"long_sleeve": 0.23,
"knit_hoodie": 0.24,
"light_jacket": 0.32,
"coat": 0.09,
"puffer": 0.04
},
"lower": {
"shorts": 0.04,
"skirt_short": 0.02,
"skirt_long": 0.02,
"trousers": 0.92
},
"footwear": {
"open": 0.02,
"closed_low": 0.93,
"boots": 0.05
},
"scarf": {
"yes": 0.01,
"no": 0.99
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 54,
"basis": "model",
"confidence": "medium",
"weather": {
"t_min": 17.0,
"t_max": 17.0,
"feels": 15.0,
"precip": 0.0,
"wind": 10.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
},
{
"date": "2026-10-28",
"summary": "Mixed: light jackets 28%, coats 27%, closed shoes; umbrellas 30%",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.02,
"long_sleeve": 0.09,
"knit_hoodie": 0.16,
"light_jacket": 0.28,
"coat": 0.27,
"puffer": 0.18
},
"lower": {
"shorts": 0.02,
"skirt_short": 0.01,
"skirt_long": 0.02,
"trousers": 0.95
},
"footwear": {
"open": 0.01,
"closed_low": 0.87,
"boots": 0.12
},
"scarf": {
"yes": 0.08,
"no": 0.92
},
"umbrella_open": {
"yes": 0.3,
"no": 0.7
}
},
"index": 71,
"basis": "model",
"confidence": "medium",
"weather": {
"t_min": 11.0,
"t_max": 11.0,
"feels": 8.0,
"precip": 12.0,
"wind": 22.0,
"issued_at": "2026-10-26T13:20:00+01:00"
}
}
],
"sources": [
"madrid-informo",
"met-norway",
"geonames"
]
}
Example: One day in Sydney, with meta=true
curl -sS 'https://api.sleevecast.app/v1/cities/sydney/days?days=1&meta=true' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "sydney",
"geonames_id": 2147714,
"name": "Sydney",
"country": "AU",
"lat": -33.86785,
"lon": 151.20732,
"tz": "Australia/Sydney"
},
"days": [
{
"date": "2026-10-26",
"summary": "Mixed: long sleeves 33%, light jackets 22%, closed shoes",
"fields": {
"upper": {
"sleeveless": 0.01,
"short_sleeve": 0.18,
"long_sleeve": 0.33,
"knit_hoodie": 0.21,
"light_jacket": 0.22,
"coat": 0.04,
"puffer": 0.01
},
"lower": {
"shorts": 0.07,
"skirt_short": 0.02,
"skirt_long": 0.03,
"trousers": 0.88
},
"footwear": {
"open": 0.02,
"closed_low": 0.94,
"boots": 0.04
},
"scarf": {
"yes": 0.01,
"no": 0.99
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"index": 44,
"basis": "model",
"confidence": "low",
"weather": {
"t_min": 19.4,
"t_max": 19.4,
"feels": 18.3,
"precip": 0.0,
"wind": 17.1,
"issued_at": "2026-10-26T14:05:00+11:00"
}
}
],
"sources": [
"met-norway",
"geonames"
],
"meta": {
"model_version": "m1.b804897b",
"rules_version": "a1.3c5f046b",
"catalog_version": "gn15k.8f3e7ea7",
"labeler": "openrouter:google/gemini-3.1-flash-lite",
"taxonomy_version": "v1",
"unit": {
"key": null,
"calibrated": false,
"shift_c": -1.5
},
"prior_strength": 20.0,
"window": null,
"weather_point": {
"slug": "sydney",
"distance_km": 0.0
}
}
}
Example: A parameter out of range
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/days?days=8' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 422
{
"error": {
"code": "validation_error",
"message": "query.days: Input should be less than or equal to 7",
"request_id": "5f0c3e9a2b7d4e18a6c1d0f2b3e4a5c6"
}
}
GET /v1/cities
List or search cities
The cities of the catalogue, a page at a time, the most populous first; cities that have left the catalogue come last. Without q it lists them all, with it it searches by name. The next_cursor of a page goes back as the cursor of the next one, and is null on the last.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
q | query | no | string | Keeps the cities whose name or slug starts with this text, ignoring case, accents and apostrophes: São finds Sao Paulo, Koln and Koeln find Köln, Xian finds Xi'an. Names are searched by their Latin spelling, so a text with no Latin letter, accented or not, and no digit from 0 to 9 (Cyrillic or Chinese, say) finds nothing. An empty or blank q filters nothing. |
country | query | no | string | Keeps the cities of one country: a two-letter ISO 3166-1 code, in any case. |
limit | query | no | integer, 1 to 200, default 50 | The size of a page, from 1 to 200. |
cursor | query | no | string | The next_cursor of the previous page, for the page after it. Leave it out, or leave it empty, for the first page. Only a value we handed out works; any other is a 422. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | A page of cities, and the cursor of the next page if there is one. | CitiesOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's. (Check the path.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.) | ErrorOut |
Example: The first page of a country
curl -sS 'https://api.sleevecast.app/v1/cities?country=ES&limit=1' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"items": [
{
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid",
"admin1": "29",
"population": 3255944,
"cameras": true
}
],
"next_cursor": "eyJyIjowLCJwIjozMjU1OTQ0LCJzIjoibWFkcmlkIn0"
}
Example: Search by name
curl -sS 'https://api.sleevecast.app/v1/cities?q=sao' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"items": [
{
"slug": "sao-paulo",
"geonames_id": 3448439,
"name": "São Paulo",
"country": "BR",
"lat": -23.5475,
"lon": -46.63611,
"tz": "America/Sao_Paulo",
"admin1": "27",
"population": 10021295,
"cameras": false
}
],
"next_cursor": null
}
GET /v1/cities/nearest
The city nearest to a point
The city nearest to a point, with its distance from it in km, for a client that knows where it is and needs a city to ask now and days about. A point far from every city (the open sea, a desert) is a 404 no_city_nearby, whose message says how far counts as near. The answer changes only when the catalogue is reloaded, so the client may keep it for a day.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
lat | query | yes | number, -90 to 90 | The latitude of the point, in degrees, from -90 to 90. |
lon | query | yes | number, -180 to 180 | The longitude of the point, in degrees, from -180 to 180. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The nearest city and its distance from the point. | NearestOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's. (Check the path.)
no_city_nearby: No city of the catalogue lies within 50 km of the point. (Offer a search by name instead.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.) | ErrorOut |
Example: A point with no city nearby
curl -sS 'https://api.sleevecast.app/v1/cities/nearest?lat=0&lon=-140' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 404
{
"error": {
"code": "no_city_nearby",
"message": "no city within 50 km",
"request_id": "5f0c3e9a2b7d4e18a6c1d0f2b3e4a5c6"
}
}
Example: The city nearest to a point
curl -sS 'https://api.sleevecast.app/v1/cities/nearest?lat=-6.16&lon=39.19' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "zanzibar",
"geonames_id": 148730,
"name": "Zanzibar",
"country": "TZ",
"lat": -6.16394,
"lon": 39.19793,
"tz": "Africa/Dar_es_Salaam",
"admin1": "25",
"population": 403658,
"cameras": false
},
"distance_km": 1.0
}
GET /v1/cities/{city}/history
What our cameras saw, day by day
What the cameras of a city saw, one item per local day that has evidence: the people judged on each field, the day's weather and, for the fields whose camera labels have been verified, once the day is over and only when enough people were judged (the privacy floor), the shares of the field and the warmth index. A day in progress shows its counts and its weather and no shares. A city without cameras has no history: 200 with no items.
Parameters
| Name | In | Required | Type | Description |
|---|---|---|---|---|
city | path | yes | string | The city: its slug or its GeoNames id, as /v1/cities lists them. |
from | query | no | string (date) | The first local date of the range, in the city's time zone, included. By default the range is the most recent days up to to. |
to | query | no | string (date) | The last local date of the range, in the city's time zone, included. By default today. A range has a longest allowed; a longer one is a 422 that names it. |
Responses
| Status | Meaning | Body |
|---|---|---|
200 | What the cameras saw in the city, one item per local day that has evidence. | HistoryOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's, or the city in it is unknown. (Check the path and, if it has a city, the city's slug or id.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.) | ErrorOut |
Example: Three days of history
curl -sS 'https://api.sleevecast.app/v1/cities/madrid/history?from=2026-10-24&to=2026-10-26' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"city": {
"slug": "madrid",
"geonames_id": 3117735,
"name": "Madrid",
"country": "ES",
"lat": 40.4165,
"lon": -3.70256,
"tz": "Europe/Madrid"
},
"rules_version": "a1.3c5f046b",
"sources": [
"madrid-informo"
],
"items": [
{
"date": "2026-10-24",
"complete": true,
"data_through": "2026-10-24T15:10:00+02:00",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.0,
"long_sleeve": 0.0,
"knit_hoodie": 0.0,
"light_jacket": 0.53,
"coat": 0.47,
"puffer": 0.0
},
"lower": {
"shorts": 0.0,
"skirt_short": 0.0,
"skirt_long": 0.0,
"trousers": 1.0
},
"footwear": {
"open": 0.0,
"closed_low": 1.0,
"boots": 0.0
},
"scarf": {
"yes": 0.0,
"no": 1.0
},
"umbrella_open": {
"yes": 0.0,
"no": 1.0
}
},
"n": {
"upper": 17,
"lower": 17,
"footwear": 17,
"scarf": 17,
"umbrella_open": 17
},
"index": 75,
"weather": {
"t_min": 9.0,
"t_max": 17.0,
"feels": 15.2
}
},
{
"date": "2026-10-25",
"complete": true,
"data_through": "2026-10-25T14:10:00+01:00",
"fields": {
"upper": {
"sleeveless": 0.0,
"short_sleeve": 0.0,
"long_sleeve": 0.0,
"knit_hoodie": 0.24,
"light_jacket": 0.0,
"coat": 0.53,
"puffer": 0.23
},
"lower": {
"shorts": 0.0,
"skirt_short": 0.0,
"skirt_long": 0.24,
"trousers": 0.76
},
"footwear": {
"open": 0.0,
"closed_low": 0.82,
"boots": 0.18
},
"umbrella_open": {
"yes": 0.18,
"no": 0.82
}
},
"n": {
"upper": 17,
"lower": 17,
"footwear": 17,
"scarf": 9,
"umbrella_open": 17
},
"index": 79,
"weather": {
"t_min": 6.0,
"t_max": 14.0,
"feels": 11.7
}
},
{
"date": "2026-10-26",
"complete": false,
"data_through": "2026-10-26T14:10:00+01:00",
"fields": {},
"n": {
"upper": 17,
"lower": 17,
"footwear": 17,
"scarf": 9,
"umbrella_open": 17
},
"index": null,
"weather": {
"t_min": 9.0,
"t_max": 17.0,
"feels": null
}
}
]
}
GET /v1/attribution
The credits to show
The credits of the service, for a client to show once, on a credits page or behind a link, and not in every answer: the camera sources whose cameras or stored days the answers rest on (ordered by operator, then id; cities are the cities they serve) and the datasets the service is built on (the city names, the weather, the regions).
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The camera sources and the datasets to credit. | AttributionOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's. (Check the path.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.) | ErrorOut |
Example: The credits to show
curl -sS 'https://api.sleevecast.app/v1/attribution' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"sources": [
{
"id": "madrid-informo",
"operator": "Ayuntamiento de Madrid",
"licence": "CC BY 4.0",
"attribution": "Origen de los datos: Ayuntamiento de Madrid",
"terms_url": "https://datos.madrid.es/dataset/202088-0-trafico-camaras",
"cities": [
"madrid"
]
},
{
"id": "austin-tpw",
"operator": "City of Austin Transportation and Public Works",
"licence": "Public Domain",
"attribution": null,
"terms_url": "https://data.austintexas.gov/d/b4k4-adkb",
"cities": [
"austin"
]
}
],
"datasets": [
{
"id": "geonames",
"name": "GeoNames",
"use": "city names, coordinates, population",
"licence": "CC BY 4.0",
"attribution": "GeoNames (geonames.org)",
"url": "https://www.geonames.org/"
},
{
"id": "open-meteo",
"name": "Open-Meteo",
"use": "weather forecasts and observations",
"licence": "CC BY 4.0",
"attribution": "Weather data by Open-Meteo.com",
"url": "https://open-meteo.com/"
},
{
"id": "met-norway",
"name": "MET Norway Locationforecast 2.0",
"use": "weather forecasts (hourly values interpolated and feels-like temperature computed by sleevecast)",
"licence": "CC BY 4.0",
"attribution": "Weather data from MET Norway",
"url": "https://api.met.no/weatherapi/locationforecast/2.0/"
},
{
"id": "natural-earth",
"name": "Natural Earth",
"use": "regions: cultural units and climate classes",
"licence": "public domain",
"attribution": "Made with Natural Earth",
"url": "https://www.naturalearthdata.com/"
},
{
"id": "un-m49",
"name": "UN M49",
"use": "regions: cultural units and climate classes",
"licence": "UN site terms",
"attribution": "United Nations Statistics Division, Standard country or area codes for statistical use (M49)",
"url": "https://unstats.un.org/unsd/methodology/m49/overview/"
}
]
}
GET /v1/version
The versions behind the numbers
The versions behind the numbers of the other routes: the contract, the build, the taxonomy and the labeler, the model, the rules and the city catalog. It is always revalidated (Cache-Control: no-cache), so after a release it names the new build at once.
Responses
| Status | Meaning | Body |
|---|---|---|
200 | The versions behind the numbers. | VersionOut |
304 | The If-None-Match sent matches the current ETag; no body. | |
400 | bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.) | ErrorOut |
401 | api_key_missing: The request has no API key in an Authorization: Bearer header. (Send a valid key.)
api_key_invalid: The API key is malformed, or is not one we issued. (Send a valid key.)
api_key_expired: The API key has passed its expiry date. (Ask for a new key.)
api_key_revoked: The API key has been revoked. (Ask for a new key.) | ErrorOut |
404 | not_found: The path is not one of the API's. (Check the path.) | ErrorOut |
422 | validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.) | ErrorOut |
429 | rate_limited: The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). (Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key.) | ErrorOut |
500 | internal_error: The server failed in a way we did not expect. (Retry once, with backoff; if it fails again, quote request_id in your report.) | ErrorOut |
503 | unavailable: The service cannot answer for the moment, for example while its database is away. (Wait as long as Retry-After says, which is 5 s, then retry.) | ErrorOut |
Example: The versions behind the numbers
curl -sS 'https://api.sleevecast.app/v1/version' -H "Authorization: Bearer $SLEEVECAST_API_KEY"
Answer: 200
{
"service": "sleevecast",
"api": "v1",
"contract_version": "1.0",
"version": "0.1.0",
"build": "ec0854f08f933c0395b40594561948a3725e7551",
"taxonomy_version": "v1",
"labeler": "openrouter:google/gemini-3.1-flash-lite",
"model_version": "m1.b804897b",
"rules_version": "a1.3c5f046b",
"catalog_version": "gn15k.8f3e7ea7"
}
Reading an answer
Shares
- Each field is a set of shares.
fields.upperhas one number for each upper-layer value. Each number is the expected share of the people outside in this city who wear that value. Shares have two decimals and add up to 1 as decimals; a floating-point sum can be off by about 1e-15. - To rank values, sort the numbers yourself. Do not rely on the order of the keys.
summaryis a short English phrase over the shares. Show it if you like. Do not parse it: its wording may change.indexis the warmth index, from 0 (everyone in sleeveless tops) to 100 (everyone in a puffer). It is side information.- The values of each field are listed under Field values.
Where an answer comes from
basisismodelwhen the weather model made the answer alone, andmodel+cameraswhen what our cameras saw corrected it. Cameras can enter field by field, once their labels have passed our accuracy check.confidencesays how far to trust the answer:high,mediumorlow. The schemas say what each level means. Showhighandmediumplainly. Forlow, show the answer with a softer wording, such as "a rough guess".data_throughis the time of the newest camera observation behind the answer, to the minute. It is in/nowand in every/historyitem; the items of/dayshave none. It isnullwhenbasisismodel.- The privacy floor. Camera evidence enters a field only when at least 10 people were judged on it in the camera window. The
ncounts inmetaand in/historyare whole people./historyshows a field's shares only from 10 people. sourceslists the ids of what the answer rests on. Each id is listed in/v1/attribution, with the credit to show.metais there only when you ask for it with?meta=true. It holds the versions, the region's calibration and the camera window. It is for diagnostics. Do not show it to people.
Weather, times and zones
weatherin/nowholds the temperature (t), the apparent temperature (feels),precip,wind,cloudand the WMO weathercode, with aunitsobject. Temperatures are in °C, precipitation in mm, wind in km/h. A value the forecast lacks isnull. In/days,t_minandt_maxcover the whole date, andfeels,precipandwindcover its daytime hours.issued_atis when we last refreshed the stored forecast for this city.- Times carry an offset, for example
2026-10-26T14:00:00+01:00.atis the start of the local hour the answer describes. For anything local, use the zone incity.tzand not the offset. - Dates in
/daysand/historyare local dates of the city. - Numbers change. The shares change as the model, its calibrations and our cameras change. That is not a change of the contract, and it has no changelog entry.
Field values
These are all the values of each field, with their meaning. A value never changes its meaning. A new value would come with a new taxonomy and a new version.
upper
| Value | Meaning |
|---|---|
sleeveless | sleeveless top, tank top, strapless or sleeveless dress |
short_sleeve | T-shirt, polo, short-sleeved shirt or dress |
long_sleeve | long-sleeved shirt, blouse or thin long-sleeved top (not knit, not a jacket) |
knit_hoodie | sweater, hoodie, fleece or cardigan as the outermost layer |
light_jacket | windbreaker, denim/leather jacket, trench coat, rain jacket, blazer |
coat | wool coat or parka (not quilted) |
puffer | down jacket or any quilted/padded jacket |
lower
| Value | Meaning |
|---|---|
shorts | shorts |
skirt_short | skirt or dress ending above the knee |
skirt_long | skirt or dress at or below the knee |
trousers | trousers, jeans or leggings |
footwear
| Value | Meaning |
|---|---|
open | sandals, flip-flops or other open shoes |
closed_low | sneakers, shoes or loafers not covering the ankle |
boots | boots covering the ankle |
scarf
| Value | Meaning |
|---|---|
yes | scarf or snood around the neck |
no | no scarf |
umbrella_open
| Value | Meaning |
|---|---|
yes | holding an open umbrella |
no | no open umbrella |
Errors
An error that the API itself answers has the body ErrorOut: one property, error, with a code, a message and a request_id (see the example "No key sent" under GET /v1/cities/{city}/now). The code is stable and meant for your program. The message is for people, and its wording may change. The same request_id is in the X-Request-ID header. Errors are never cached.
| Code | Status | Operations | Meaning | What to do |
|---|---|---|---|---|
bad_request | 400 | every operation | The request is malformed, for example by a NUL byte in the path or the query. | Fix the request. |
api_key_missing | 401 | every operation | The request has no API key in an Authorization: Bearer header. | Send a valid key. |
api_key_invalid | 401 | every operation | The API key is malformed, or is not one we issued. | Send a valid key. |
api_key_expired | 401 | every operation | The API key has passed its expiry date. | Ask for a new key. |
api_key_revoked | 401 | every operation | The API key has been revoked. | Ask for a new key. |
not_found | 404 | every operation | The path is not one of the API's, or the city in it is unknown. | Check the path and, if it has a city, the city's slug or id. |
no_city_nearby | 404 | getNearestCity | No city of the catalogue lies within 50 km of the point. | Offer a search by name instead. |
method_not_allowed | 405 | every operation | The path does not answer that HTTP method. | Use GET or HEAD. |
validation_error | 422 | every operation | A parameter is missing, malformed or out of range. | Fix the parameter that message names. |
rate_limited | 429 | every operation | The key is over its budget of requests per minute, or too many key checks have failed, from your address or from all addresses together (a valid key is never refused for that). | Wait as long as Retry-After says, then retry; after failed key checks, retry with a valid key. |
internal_error | 500 | every operation | The server failed in a way we did not expect. | Retry once, with backoff; if it fails again, quote request_id in your report. |
unavailable | 503 | every operation | The service cannot answer for the moment, for example while its database is away. | Wait as long as Retry-After says, which is 5 s, then retry. |
weather_unavailable | 503 | getDays, getNow | No stored weather forecast for this city covers the hour or the days asked for. | Wait as long as Retry-After says, which is 600 s, then retry. |
-
When to retry. After a
429or a503, wait as long asRetry-Aftersays, then retry. After a500, retry once, with backoff. Do not retry other errors: they fail again until you change the request. -
weather_unavailablemeans the forecast we hold does not cover the hours you asked for. It is rare. Retry after the time inRetry-After; asking for fewer days may work sooner. -
Not every error answer is ours. Some answers come from the proxy in front of the API, and they are not in the table above:
- a
404for a path outside/v1,/guideand/healthz; - a
429when requests arrive too fast; - a
502,503or504when no API server is ready or one does not answer in time.
These answers carry no
X-Request-IDand noErrorOutbody, and a502,503or504carries noRetry-After. Do not parse their body, and treat a502,503or504without our JSON as "try again later" with your own backoff. - a
-
A page of another origin cannot call
/v1until its origin is allowed. While none is (the default), its preflight gets our405method_not_allowed. Once origins are allowed, a preflight from another origin, or one asking for another method or header, gets a400with a short plain-text body (from another origin, withoutAccess-Control-Allow-Origin). Either way the browser reports a CORS error. Origins are allowed one by one, on request: see Support.
Limits and caching
The numbers below are the settings of the server that sent you this page.
| Limit | Value |
|---|---|
| Requests a minute for a key, unless agreed otherwise | 60 |
| The most a key can be given, requests a minute | 600 |
| Failed key checks a minute from one address | 20 |
Seconds an answer may be reused (max-age) | 300 |
| Days a new key is valid, unless agreed otherwise | 90 |
- Per key. A key may make the number of requests in the table in any 60 seconds. Ask for more if you need it.
RateLimit-Limit,RateLimit-RemainingandRateLimit-Resetcome on every answer to an accepted key (the number of seconds until the oldest counted request leaves the window). The429for a spent budget carriesRetry-After. A refused request does not count against the budget. A304and a404do. When we run more than one server the counts are approximate.- Failed key checks are limited per client address and overall; a valid key is never refused for them.
- At the proxy. A proxy in front of the API also limits how fast requests may arrive, to keep floods out. Its
429is not anErrorOutand has noRateLimit-*headers. It may carryRetry-After: honour it, or else wait a few seconds. - Cache what the headers allow. An answer carries
Cache-Control: max-age=with the seconds in the table, and a weakETag. Within that time, ask again only if you must. Afterwards sendIf-None-Matchwith theETag: you get a304with no body when the answer has not changed. A304still counts against your budget. nearestis kept for a day. It carriesCache-Control: private, max-age=86400.privatemeans that your client alone keeps it, because its URL holds a position. A cache stores an answer by URL, so round the coordinates to 2 decimals: with 12.3456, the next call from the same phone a few metres away is a new URL, and the cache does not help.- Poll gently. A city's answer changes at most every 30 minutes (cameras) and every hour (weather): poll
/nowat most every 5 minutes per city. /v1/versionrevalidates. It carriesCache-Control: no-cache, so you always see a new release at once; theETagmakes it a304when nothing changed.- Send
Accept-Encoding: gzip. Answers of 1,000 bytes or more are compressed. - Browser origins are allowed one by one, on request. By default none is allowed, and a page of another origin cannot read
/v1. Write to the address under Support with the origin.
Attribution
- The duty. Wherever you show data from this API, show the credits that
/v1/attributionlists, or link to a page of yours that shows them. The licences of the data (CC BY 4.0 for MET Norway, GeoNames and Madrid's cameras) ask for credit, and CC BY 4.0 accepts a link to a page that shows it. This is also condition 3 of the conditions of use. - The credits are two lists.
sourceslists the camera sources the answers can rest on: who operates the cameras, the licence, the credit line to show and the page of the terms.datasetslists the data the service is built on, such as GeoNames and the weather from MET Norway. Each entry names its licence and has a credit line to show. - Show what an entry has. A camera source may list no city, and may have
nullfor its licence, credit line or terms page. Show what it has. - Keep the list current. New sources appear. Refresh it at least once a week. The
sourcesof an answer name what that answer rests on, and each id is in the list. - You need not show them in every answer. A credits page linked from where you show the data (for example behind an "i" button) is enough.
Conditions of use
- Your key. A key is issued to one organisation for the use agreed when it was issued. Keep it out of public code, and use one key per app or web page. We may suspend or revoke a key that is abused or leaked, and we tell you why.
- Fair use. Stay within your key's budget and cache what the headers allow. Do not spread one app's traffic over several keys to get around a limit; ask if you need more.
- Credits. Show the credits that
/v1/attributionlists wherever you show data from this API, or on a page linked from there. They are the conditions of the data's licences (CC BY 4.0 for MET Norway, GeoNames and Madrid's cameras), not ours. - The data. Weather values, place names and coordinates come under the licences in
/v1/attribution; these conditions add nothing to them. The clothing estimates are ours: you may show them in your app and store them for that. Ask us before you redistribute them as a dataset. - Estimates. Answers are statistical estimates, and
confidencesays how far to trust each one. Do not rely on them where a wrong answer could cause harm. - No warranty. The service is offered as it is, without a guaranteed uptime, during a pilot. We change it only as the versioning rules allow.
- Personal data. Do not send us personal data: name cities by their slugs, and round coordinates to two decimals.
- Changes. We announce changes to these conditions in the changelog at least 30 days before they apply.
Operator: sleevecast.app.
Contact: api@sleevecast.app
Privacy
Requests this API answers are logged for abuse handling, with the IP address they came from, the
method, the path and query string and the response status, and for /v1 also the name of the key
(never the key itself) and the time taken; our log store keeps these lines for 7 days, and the
servers that run the API keep a rolling copy, which new lines overwrite and which is deleted when
the API is redeployed. A /v1 line also holds the host name the request was sent to and a request
id, and quotes coordinates rounded to two decimals; no line holds another request header or a
cookie. The guide pages set no cookie and load nothing from other sites. Requests to /v1 are also
counted per key name, without addresses. Questions about these logs: write to the contact under
Support.
Versioning
- The contract version is
MAJOR.MINOR. The major is the path,/v1. The minor rises with each release that adds something. It is in the document (info.version), inGET /v1/versionand in the Changelog. - What we promise. Within
/v1, additions only: a new property, a new code, a new operation, a new optional parameter. We do not remove or rename anything, and a value keeps its meaning. - What you must do.
- Ignore properties you do not know.
- Accept error codes, source and dataset ids, and WMO weather codes you do not know.
- Do not rely on the order of keys.
- Do not parse
summaryormessage. - Follow
Retry-After.
- What is not a change of the contract. The numbers in the answers, the catalogue of cities, the camera sources, and the wording of
summary,message, descriptions and examples. They change without a changelog entry. - A change that would break a client goes to
/v2. The one exception is a correction: the document was wrong about what the server always did. A correction ships with a changelog entry that names each change it makes. - A deprecation within
/v1marks the property or parameterdeprecatedin the document and adds a changelog entry. It stays until/v2. /v2. If we ever ship it,/v1stays served for at least 90 days after that. From the announcement on, every/v1answer carries the headersDeprecationandSunset. We also write to the contact of every key.
Changelog
This is the changelog of the public API under /v1. Within /v1 the contract only grows: a new
property, a new code or a new operation is an addition. A change that would break a client goes
to /v2. The one exception is a correction: the document was wrong about what the server always
did, and its entry names each change it makes.
Each release has an entry below, newest first, with its date, the digest of the contract it
describes, and what changed. Numbers, the catalogue, sources and the wording of summary,
message, descriptions and examples change without an entry: they are not part of the contract.
Changes to the conditions of use are announced here at least 30 days before they apply, as a dated
paragraph above the entries.
1.0 — 2026-11-04
Contract 7676523d6b50734e, first.
- The seven operations:
GET /v1/cities,/v1/cities/nearest,/v1/cities/{city}/now,/v1/cities/{city}/days,/v1/cities/{city}/history,/v1/attributionand/v1/version. - The first frozen contract; before it,
/v1changed in place while no client held a key.
Semantics: as served since 2026-11-03, except that times carry their seconds (RFC 3339) and end in
Z at a zero offset, /v1/version adds contract_version, and some properties are listed in a new
order (a field's shares in taxonomy order, admin1 after tz).
Support
Contact: api@sleevecast.app
- Questions, a new key, a new origin, more budget: write to the address above.
- When you report a problem, include the
request_idof the error body or theX-Request-IDheader, the time of the call (with its zone), and the name of your key. Never send the key itself. - The same address is for the operators of the services we use. MET Norway's requests name this page.
Reference: schemas
These are the bodies of the API, with every property and its meaning. They come from the same document as https://api.sleevecast.app/guide/openapi.json.
AttributionOut
The credits of the service, for a client to show once, on a credits page or behind a link, and not in every answer.
| Property | Type | Presence | Description |
|---|---|---|---|
sources | array of CameraSource | The camera sources the answers rest on, ordered by operator, then id. | |
datasets | array of Dataset | The datasets the service is built on: the city names, the weather and the regions. |
Basis
How an answer was made: from the weather model alone (model), or from the model corrected by what the cameras saw (model+cameras). A new value would be a breaking change.
Type: string: model, model+cameras
CameraSource
A source of camera images the answers rest on, to credit: who operates the cameras, the licence they publish under, the credit line to show and the page of their terms.
| Property | Type | Presence | Description |
|---|---|---|---|
id | string | The source's id, as the sources of an answer list it. New ids may appear. | |
operator | string | Who operates the cameras. | |
licence | string | may be null | The licence the images are published under; null where the source gives none. |
attribution | string | may be null | The credit line to show; null where the source gives none. |
terms_url | string | may be null | The page of the source's terms; null where the source gives none. |
cities | array of string | The slugs of the cities, as /v1/cities/{city}/now takes them, whose answers rest on this source. Empty where none of the source's cities is in the catalogue. |
CameraWindow
The window the cameras' evidence came from: its first local hour, its length in hours, the number of cameras seen, and per field the people judged and the cameras' weight. No camera is named, and no share of the cameras alone is shown.
| Property | Type | Presence | Description |
|---|---|---|---|
from | string (date-time) | The start of the window's first local hour. | |
hours | integer, 1 or more | The length of the window in local hours, up to the current one. | |
n_cameras | integer, 0 or more | The number of cameras the window saw. A count: no camera is named. | |
n | FieldCounts | How many people our cameras judged on that field: whole people, counts, not shares. A person counts for a field only when the image shows it clearly. | |
camera_weight | FieldWeights | How much of each field the cameras carry in the answer, from 0 to 1. It is 0 where fewer than 10 people were judged on the field, or where its camera labels have not passed our accuracy check. |
CitiesOut
A page of the catalogue: the most populous city first, and the cities that have left the catalogue last.
| Property | Type | Presence | Description |
|---|---|---|---|
items | array of City | The cities of this page, in the order of the list. | |
next_cursor | string | may be null | Send it back as cursor to get the next page; null on the last page. A client does not read it or make one. |
City
A city of the catalogue: what CityRef names and locates, with its first-level division, its population, and whether our cameras can correct its answers.
| Property | Type | Presence | Description |
|---|---|---|---|
slug | string | The city's key in the path, as /v1/cities/{city}/now takes it. | |
geonames_id | integer, 1 or more | The city's GeoNames id, which the path takes in place of the slug. | |
name | string | The city's name. | |
country | string | The city's country, as an ISO 3166-1 alpha-2 code. | |
lat | number, -90 to 90 | The latitude of the city, in degrees. | |
lon | number, -180 to 180 | The longitude of the city, in degrees. | |
tz | string | The city's IANA time zone, the zone the times of the answer are written in. | |
admin1 | string | The GeoNames code of the city's first-level administrative division (a state, a province, a region), for example 29 for Madrid's: a code, not a name, unique only within country, and GeoNames may change it. An empty string where the city has none. | |
population | integer, 0 or more | may be null | The city's population, as GeoNames gives it; null where it has none. |
cameras | boolean | true for a city whose answers can be corrected by what our licence-clean street cameras there saw; false where the weather model answers alone. |
CityRef
The city an answer is for: what a client needs to name and locate it.
| Property | Type | Presence | Description |
|---|---|---|---|
slug | string | The city's key in the path, as /v1/cities/{city}/now takes it. | |
geonames_id | integer, 1 or more | The city's GeoNames id, which the path takes in place of the slug. | |
name | string | The city's name. | |
country | string | The city's country, as an ISO 3166-1 alpha-2 code. | |
lat | number, -90 to 90 | The latitude of the city, in degrees. | |
lon | number, -180 to 180 | The longitude of the city, in degrees. | |
tz | string | The city's IANA time zone, the zone the times of the answer are written in. |
Confidence
How far to trust an answer, from low to high. A new value would be a breaking change.
Type: string: low, medium, high
Dataset
A dataset the service is built on, to credit.
| Property | Type | Presence | Description |
|---|---|---|---|
id | string | The dataset's id, as the sources of an answer list it; the key a client matches on. New ids may appear. | |
name | string | The dataset's name. | |
use | string | What the service uses the dataset for. | |
licence | string | The licence the dataset is published under. | |
attribution | string | The credit line to show. | |
url | string | may be null | Where to read about the dataset; null where it has no page. |
Day
The answer for one local date: the shares of each field over the day's outdoor hours, the warmth index of upper, how the answer was made and the day's weather. Only the first item, today, can have basis model+cameras.
| Property | Type | Presence | Description |
|---|---|---|---|
date | string (date) | The local date, in the city's time zone. | |
summary | string | A short English phrase over the shares. Its wording may change: show it, do not parse it. | |
fields | Fields | For each clothing field, the expected share of the people outside in this city over the day's outdoor hours who wear each value. Two decimals; the values of a field sum to 1. | |
index | integer, 0 to 100 | The warmth index: 0 when everyone wears sleeveless tops, 100 when everyone wears a puffer; the mean warmth of fields.upper. Side information. | |
basis | Basis | model: the answer comes from the weather model alone. model+cameras: the model corrected by what our licence-clean street cameras in this city saw in the last hours, for the clothing fields whose camera labels have passed our accuracy check. | |
confidence | Confidence | How far to trust the answer. high: the cameras saw enough people judged on their upper layer to carry at least half of it, at least meta.prior_strength (20 today). medium: they saw 10 to 19 such people, or the model is calibrated for this region. low: the model alone in a region without a calibration, a forecast more than 24 hours old, or a day 4 to 6 days ahead. | |
weather | DayWeather | The weather of the date. |
DayWeather
The weather of a local date, to one decimal. t_min and t_max are the lowest and highest hourly temperature of the whole date, feels and wind the means and precip the total over its daytime hours. Only the hours the forecast covers count, and a value that none of the hours it is taken over has is null.
| Property | Type | Presence | Description |
|---|---|---|---|
t_min | number | may be null | The lowest hourly temperature of the date in °C; null when none of the date's hours has a value. |
t_max | number | may be null | The highest hourly temperature of the date in °C; null when none of the date's hours has a value. |
feels | number | may be null | The mean apparent temperature of the daytime hours in °C; null when none of them has a value. |
precip | number, 0 or more | may be null | The total precipitation of the daytime hours in mm; null when none of them has a value. |
wind | number, 0 or more | may be null | The mean wind speed of the daytime hours in km/h; null when none of them has a value. |
issued_at | string (date-time) | When our stored forecast for this city was last refreshed. |
DaysOut
The answers for today and the days after it, today first. meta is there only when ?meta=true was asked, once for the whole response.
| Property | Type | Presence | Description |
|---|---|---|---|
city | CityRef | The city the answers are for. | |
days | array of Day | One item per local date, today first. | |
sources | array of string | The ids of what the answer rests on; each one is listed in /v1/attribution. New ids may appear. | |
meta | Meta | may be absent | Diagnostics, on request: versions, the region's calibration and the camera window. Not for display. |
ErrorBody
What went wrong: a stable code, a message for people and the id of the request.
| Property | Type | Presence | Description |
|---|---|---|---|
code | string | Stable, machine-readable code in snake_case, for example not_found. New codes may appear. | |
message | string | Explanation for humans; the wording may change. | |
request_id | string | Identifier of this request, also sent as the X-Request-ID response header; quote it when reporting a problem. |
ErrorOut
The body of an error that the API itself answers under /v1, whatever its status. The proxy in front of the API has answers of its own (a 404 for another path, a 429, a 502, 503 or 504), and those have no such body.
| Property | Type | Presence | Description |
|---|---|---|---|
error | ErrorBody | The error. |
FieldCounts
The people the cameras judged on each field: whole people, counts and never shares.
| Property | Type | Presence | Description |
|---|---|---|---|
upper | integer, 0 or more | The people judged on upper. | |
lower | integer, 0 or more | The people judged on lower. | |
footwear | integer, 0 or more | The people judged on footwear. | |
scarf | integer, 0 or more | The people judged on scarf. | |
umbrella_open | integer, 0 or more | The people judged on umbrella_open. |
FieldWeights
The weight of the cameras' evidence in the answer for each field, from 0 (the model alone) to 1.
| Property | Type | Presence | Description |
|---|---|---|---|
upper | number, 0 to 1 | The cameras' weight in fields.upper. | |
lower | number, 0 to 1 | The cameras' weight in fields.lower. | |
footwear | number, 0 to 1 | The cameras' weight in fields.footwear. | |
scarf | number, 0 to 1 | The cameras' weight in fields.scarf. | |
umbrella_open | number, 0 to 1 | The cameras' weight in fields.umbrella_open. |
Fields
The shares of each clothing field the service answers for: every value of every field is a named property, always there.
| Property | Type | Presence | Description |
|---|---|---|---|
upper | UpperShares | The outermost layer on the upper body: the share of each value. | |
lower | LowerShares | What is worn on the legs: the share of each value. | |
footwear | FootwearShares | What is worn on the feet: the share of each value. | |
scarf | YesNoShares | The shares of the people with and without a scarf. yes: scarf or snood around the neck. no: no scarf. | |
umbrella_open | YesNoShares | The shares of the people with and without an open umbrella. yes: holding an open umbrella. no: no open umbrella. |
FootwearShares
The shares of footwear, what is worn on the feet: one property per value, from 0 to 1 with two decimals, in the taxonomy's order; they sum to 1. Closed: a new value would be a new taxonomy.
| Property | Type | Presence | Description |
|---|---|---|---|
open | number, 0 to 1 | sandals, flip-flops or other open shoes | |
closed_low | number, 0 to 1 | sneakers, shoes or loafers not covering the ankle | |
boots | number, 0 to 1 | boots covering the ankle |
HistoryDay
What the cameras observed on one local day. n, data_through and weather are always shown. fields and index are shown only when all of these hold: the day is over; the field's camera labels have passed our accuracy check; and enough people were judged on the field that day. A day in progress has fields empty and index null.
A day can still change for a while after it is over, as late labels arrive, so a client must not treat it as final.
| Property | Type | Presence | Description |
|---|---|---|---|
date | string (date) | The local date, in the city's time zone. | |
complete | boolean | True once the local day has ended. The day can still change for a while after that, as late labels arrive. | |
data_through | string (date-time) | When the newest camera observation behind this day was taken, to the minute. | |
fields | HistoryFields | The shares the cameras observed, for each field that is shown. A field is shown only once the day is over (complete), only if its camera labels have passed our accuracy check, and only when at least 10 people were judged on it that day; otherwise it is absent. A day in progress has none. | |
n | FieldCounts | How many people our cameras judged on that field: whole people, counts, not shares. A person counts for a field only when the image shows it clearly. | |
index | integer, 0 to 100 | may be null | The warmth index: 0 when everyone wears sleeveless tops, 100 when everyone wears a puffer; the mean warmth of fields.upper. Side information. null when fields.upper is absent. |
weather | HistoryWeather | The weather of the day. |
HistoryFields
The shares the cameras observed on one day, for the fields that are shown. A field is there only for a finished day, only if its camera labels have passed our accuracy check, and only when enough people were judged on it that day (the privacy floor); otherwise it is absent, never null. Today's item has none.
| Property | Type | Presence | Description |
|---|---|---|---|
upper | UpperShares | may be absent | The outermost layer on the upper body: the share of each value. |
lower | LowerShares | may be absent | What is worn on the legs: the share of each value. |
footwear | FootwearShares | may be absent | What is worn on the feet: the share of each value. |
scarf | YesNoShares | may be absent | The shares of the people with and without a scarf. yes: scarf or snood around the neck. no: no scarf. |
umbrella_open | YesNoShares | may be absent | The shares of the people with and without an open umbrella. yes: holding an open umbrella. no: no open umbrella. |
HistoryOut
The history of a city: one item per local day with camera evidence in the range asked, oldest first. A city without cameras has none.
| Property | Type | Presence | Description |
|---|---|---|---|
city | CityRef | The city the history is for. | |
rules_version | string | The version of the rules every item was made under. | |
sources | array of string | The ids of the camera sources behind the items; each one is listed in /v1/attribution. New ids may appear. | |
items | array of HistoryDay | One item per local day that has evidence, oldest first. |
HistoryWeather
The weather of a day in the history, to one decimal. A value the day has no data for is null.
| Property | Type | Presence | Description |
|---|---|---|---|
t_min | number | may be null | The lowest temperature the day's observations saw, in °C. |
t_max | number | may be null | The highest temperature the day's observations saw, in °C. |
feels | number | may be null | The mean observed apparent temperature of the day's daytime hours, in °C. |
LowerShares
The shares of lower, what is worn on the legs: one property per value, from 0 to 1 with two decimals, in the taxonomy's order; they sum to 1. Closed: a new value would be a new taxonomy.
| Property | Type | Presence | Description |
|---|---|---|---|
shorts | number, 0 to 1 | shorts | |
skirt_short | number, 0 to 1 | skirt or dress ending above the knee | |
skirt_long | number, 0 to 1 | skirt or dress at or below the knee | |
trousers | number, 0 to 1 | trousers, jeans or leggings |
Meta
What ?meta=true adds to an answer: the versions it was made with, the city's unit and shift, the cameras' window and the weather point.
| Property | Type | Presence | Description |
|---|---|---|---|
model_version | string | The version of the model that made the shares. | |
rules_version | string | The version of the rules the camera evidence was aggregated under. | |
catalog_version | string | The version of the city catalogue. | |
labeler | string | The labeler that judged the people in the camera evidence. | |
taxonomy_version | string | The version of the taxonomy that names the fields and their values. | |
unit | ModelUnit | The cultural unit of the city and its shift. | |
prior_strength | number | How many judged people make the cameras weigh as much as the model: the cameras' weight is n / (n + prior_strength). | |
window | CameraWindow | may be null | The window of the cameras' evidence; null when no field took any. |
weather_point | WeatherPoint | The forecast the answer read. |
ModelUnit
The cultural unit of the city, whether a calibration names it, and the shift in °C its feels-like temperature is read at.
| Property | Type | Presence | Description |
|---|---|---|---|
key | string | may be null | The unit's key; null for a city outside every region. New keys may appear. |
calibrated | boolean | Whether a calibration names the unit. | |
shift_c | number | The shift in °C added to the apparent temperature before the model reads it. |
NearestOut
The city nearest to a point and its distance from it.
| Property | Type | Presence | Description |
|---|---|---|---|
city | City | The nearest city of the catalogue. | |
distance_km | number, 0 or more | Its great-circle distance from the point, in km, to one decimal. |
NowOut
The answer for a city's current local hour. meta is there only when ?meta=true was asked.
| Property | Type | Presence | Description |
|---|---|---|---|
city | CityRef | The city the answer is for. | |
at | string (date-time) | The start of the local hour the answer describes. | |
summary | string | A short English phrase over the shares. Its wording may change: show it, do not parse it. | |
fields | Fields | For each clothing field, the expected share of the people outside in this city at this hour who wear each value. Two decimals; the values of a field sum to 1. | |
index | integer, 0 to 100 | The warmth index: 0 when everyone wears sleeveless tops, 100 when everyone wears a puffer; the mean warmth of fields.upper. Side information. | |
basis | Basis | model: the answer comes from the weather model alone. model+cameras: the model corrected by what our licence-clean street cameras in this city saw in the last hours, for the clothing fields whose camera labels have passed our accuracy check. | |
confidence | Confidence | How far to trust the answer. high: the cameras saw enough people judged on their upper layer to carry at least half of it, at least meta.prior_strength (20 today). medium: they saw 10 to 19 such people, or the model is calibrated for this region. low: the model alone in a region without a calibration, a forecast more than 24 hours old, or a day 4 to 6 days ahead. | |
data_through | string (date-time) | may be null | When the newest camera observation behind this answer was taken, to the minute; null when basis is model. |
weather | NowWeather | The hour's weather and the forecast it comes from. | |
sources | array of string | The ids of what the answer rests on; each one is listed in /v1/attribution. New ids may appear. | |
meta | Meta | may be absent | Diagnostics, on request: versions, the region's calibration and the camera window. Not for display. |
NowWeather
The weather of the hour and the forecast it comes from. t, feels, precip and wind are to one decimal. A value the forecast lacks for the hour is null.
| Property | Type | Presence | Description |
|---|---|---|---|
t | number | may be null | Air temperature in °C; null when the forecast has no value for the hour. |
feels | number | Apparent temperature in °C, which we compute from temperature, humidity and wind (Steadman's formula as the Australian Bureau of Meteorology publishes it). | |
precip | number, 0 or more | may be null | Precipitation in mm in the hour; null when the forecast has no value for the hour. |
wind | number, 0 or more | may be null | Wind speed in km/h at 10 m; null when the forecast has no value for the hour. |
cloud | integer, 0 to 100 | may be null | Cloud cover in percent; null when the forecast has no value for the hour. |
code | integer | may be null | The WMO weather code of the hour (the table Open-Meteo documents); null when the forecast has no value for the hour. New codes may appear. |
issued_at | string (date-time) | When our stored forecast for this city was last refreshed. | |
units | WeatherUnits | The unit of each value above. |
UpperShares
The shares of upper, the outermost layer on the upper body: one property per value, from 0 to 1 with two decimals, in the taxonomy's order; they sum to 1. Closed: a new value would be a new taxonomy.
| Property | Type | Presence | Description |
|---|---|---|---|
sleeveless | number, 0 to 1 | sleeveless top, tank top, strapless or sleeveless dress | |
short_sleeve | number, 0 to 1 | T-shirt, polo, short-sleeved shirt or dress | |
long_sleeve | number, 0 to 1 | long-sleeved shirt, blouse or thin long-sleeved top (not knit, not a jacket) | |
knit_hoodie | number, 0 to 1 | sweater, hoodie, fleece or cardigan as the outermost layer | |
light_jacket | number, 0 to 1 | windbreaker, denim/leather jacket, trench coat, rain jacket, blazer | |
coat | number, 0 to 1 | wool coat or parka (not quilted) | |
puffer | number, 0 to 1 | down jacket or any quilted/padded jacket |
VersionOut
The contract, the build, the labeler and the rules behind the numbers of the other routes.
| Property | Type | Presence | Description |
|---|---|---|---|
service | string | The name of the service. | |
api | string | The major version of the API: the v1 of the paths. | |
contract_version | string | The version of this API's contract, MAJOR.MINOR. The minor rises with a release that only adds; the guide's changelog lists the releases. | |
version | string | The version of the service. | |
build | string | may be null | The git commit the running image was built from; null for a build without one. |
taxonomy_version | string | The version of the taxonomy that names the fields and their values. | |
labeler | string | The labeler that judges the people in the camera evidence the answers use. | |
model_version | string | The version of the model that makes the shares. | |
rules_version | string | The version of the rules the camera evidence is aggregated under. | |
catalog_version | string | may be null | The version of the city catalog the service was loaded from. null when no catalog has been loaded, or when it is not known at this moment: the service could not read it and has not read it before. |
WeatherPoint
The city whose stored forecast the answer read.
| Property | Type | Presence | Description |
|---|---|---|---|
slug | string | The slug of the city whose forecast was read: the asked city itself if it has one. | |
distance_km | number, 0 or more | Its distance from the asked city in km, to one decimal. |
WeatherUnits
The units of NowWeather's values.
| Property | Type | Presence | Description |
|---|---|---|---|
t | string | The unit of t: degrees Celsius. | |
feels | string | The unit of feels: degrees Celsius. | |
precip | string | The unit of precip: millimetres. | |
wind | string | The unit of wind: kilometres per hour. | |
cloud | string | The unit of cloud: percent. |
YesNoShares
The shares of a yes-or-no field, scarf or umbrella_open: yes and no, from 0 to 1 with two decimals; they sum to 1. Closed: a new value would be a new taxonomy.
| Property | Type | Presence | Description |
|---|---|---|---|
yes | number, 0 to 1 | The share of the people for whom the answer is yes. | |
no | number, 0 to 1 | The share of the people for whom the answer is no. |