sleevecast API guide

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.

npx openapi-typescript https://api.sleevecast.app/guide/openapi.json

Every call to /v1 needs a key. The Quickstart shows your first three calls.

Quickstart

  1. 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.
  2. Find the city. Call nearest with a coordinate, and keep the slug of the city it returns. Round the coordinate to 2 decimals first (about 1 km). That is enough for nearest. 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).
  3. Ask for the answer. Call /now with the slug, then /days.
  4. Show the credits. Wherever you show our data, show the credits that /v1/attribution lists. See Attribution.
  5. 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

Cities

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 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

NameInRequiredTypeDescription
citypathyesstringThe city: its slug or its GeoNames id, as /v1/cities lists them.
metaquerynoboolean, default falsetrue adds meta to the answer: how it was made. Diagnostics, not for display.

Responses

StatusMeaningBody
200What people wear in the city in the current local hour.NowOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_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
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

NameInRequiredTypeDescription
citypathyesstringThe city: its slug or its GeoNames id, as /v1/cities lists them.
daysquerynointeger, 1 to 7, default 7How many local dates to answer, today first, from 1 to 7.
metaquerynoboolean, default falsetrue adds meta to the answer: how it was made. Diagnostics, not for display.

Responses

StatusMeaningBody
200What people wear in the city on each of the days asked for, today first.DaysOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_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
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

NameInRequiredTypeDescription
qquerynostringKeeps 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.
countryquerynostringKeeps the cities of one country: a two-letter ISO 3166-1 code, in any case.
limitquerynointeger, 1 to 200, default 50The size of a page, from 1 to 200.
cursorquerynostringThe 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

StatusMeaningBody
200A page of cities, and the cursor of the next page if there is one.CitiesOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_found: The path is not one of the API's. (Check the path.)ErrorOut
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

NameInRequiredTypeDescription
latqueryyesnumber, -90 to 90The latitude of the point, in degrees, from -90 to 90.
lonqueryyesnumber, -180 to 180The longitude of the point, in degrees, from -180 to 180.

Responses

StatusMeaningBody
200The nearest city and its distance from the point.NearestOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_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
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

NameInRequiredTypeDescription
citypathyesstringThe city: its slug or its GeoNames id, as /v1/cities lists them.
fromquerynostring (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.
toquerynostring (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

StatusMeaningBody
200What the cameras saw in the city, one item per local day that has evidence.HistoryOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_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
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

StatusMeaningBody
200The camera sources and the datasets to credit.AttributionOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_found: The path is not one of the API's. (Check the path.)ErrorOut
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

StatusMeaningBody
200The versions behind the numbers.VersionOut
304The If-None-Match sent matches the current ETag; no body.
400bad_request: The request is malformed, for example by a NUL byte in the path or the query. (Fix the request.)ErrorOut
401api_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
404not_found: The path is not one of the API's. (Check the path.)ErrorOut
422validation_error: A parameter is missing, malformed or out of range. (Fix the parameter that message names.)ErrorOut
429rate_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
500internal_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
503unavailable: 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

Where an answer comes from

Weather, times and zones

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

ValueMeaning
sleevelesssleeveless top, tank top, strapless or sleeveless dress
short_sleeveT-shirt, polo, short-sleeved shirt or dress
long_sleevelong-sleeved shirt, blouse or thin long-sleeved top (not knit, not a jacket)
knit_hoodiesweater, hoodie, fleece or cardigan as the outermost layer
light_jacketwindbreaker, denim/leather jacket, trench coat, rain jacket, blazer
coatwool coat or parka (not quilted)
pufferdown jacket or any quilted/padded jacket

lower

ValueMeaning
shortsshorts
skirt_shortskirt or dress ending above the knee
skirt_longskirt or dress at or below the knee
trouserstrousers, jeans or leggings

footwear

ValueMeaning
opensandals, flip-flops or other open shoes
closed_lowsneakers, shoes or loafers not covering the ankle
bootsboots covering the ankle

scarf

ValueMeaning
yesscarf or snood around the neck
nono scarf

umbrella_open

ValueMeaning
yesholding an open umbrella
nono 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.

CodeStatusOperationsMeaningWhat to do
bad_request400every operationThe request is malformed, for example by a NUL byte in the path or the query.Fix the request.
api_key_missing401every operationThe request has no API key in an Authorization: Bearer header.Send a valid key.
api_key_invalid401every operationThe API key is malformed, or is not one we issued.Send a valid key.
api_key_expired401every operationThe API key has passed its expiry date.Ask for a new key.
api_key_revoked401every operationThe API key has been revoked.Ask for a new key.
not_found404every operationThe 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_nearby404getNearestCityNo city of the catalogue lies within 50 km of the point.Offer a search by name instead.
method_not_allowed405every operationThe path does not answer that HTTP method.Use GET or HEAD.
validation_error422every operationA parameter is missing, malformed or out of range.Fix the parameter that message names.
rate_limited429every operationThe 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_error500every operationThe server failed in a way we did not expect.Retry once, with backoff; if it fails again, quote request_id in your report.
unavailable503every operationThe 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_unavailable503getDays, getNowNo 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.

Limits and caching

The numbers below are the settings of the server that sent you this page.

LimitValue
Requests a minute for a key, unless agreed otherwise60
The most a key can be given, requests a minute600
Failed key checks a minute from one address20
Seconds an answer may be reused (max-age)300
Days a new key is valid, unless agreed otherwise90

Attribution

Conditions of use

  1. 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.
  2. 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.
  3. Credits. Show the credits that /v1/attribution lists 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.
  4. 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.
  5. Estimates. Answers are statistical estimates, and confidence says how far to trust each one. Do not rely on them where a wrong answer could cause harm.
  6. 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.
  7. Personal data. Do not send us personal data: name cities by their slugs, and round coordinates to two decimals.
  8. 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

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.

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

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.

PropertyTypePresenceDescription
sourcesarray of CameraSourceThe camera sources the answers rest on, ordered by operator, then id.
datasetsarray of DatasetThe 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.

PropertyTypePresenceDescription
idstringThe source's id, as the sources of an answer list it. New ids may appear.
operatorstringWho operates the cameras.
licencestringmay be nullThe licence the images are published under; null where the source gives none.
attributionstringmay be nullThe credit line to show; null where the source gives none.
terms_urlstringmay be nullThe page of the source's terms; null where the source gives none.
citiesarray of stringThe 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.

PropertyTypePresenceDescription
fromstring (date-time)The start of the window's first local hour.
hoursinteger, 1 or moreThe length of the window in local hours, up to the current one.
n_camerasinteger, 0 or moreThe number of cameras the window saw. A count: no camera is named.
nFieldCountsHow 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_weightFieldWeightsHow 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.

PropertyTypePresenceDescription
itemsarray of CityThe cities of this page, in the order of the list.
next_cursorstringmay be nullSend 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.

PropertyTypePresenceDescription
slugstringThe city's key in the path, as /v1/cities/{city}/now takes it.
geonames_idinteger, 1 or moreThe city's GeoNames id, which the path takes in place of the slug.
namestringThe city's name.
countrystringThe city's country, as an ISO 3166-1 alpha-2 code.
latnumber, -90 to 90The latitude of the city, in degrees.
lonnumber, -180 to 180The longitude of the city, in degrees.
tzstringThe city's IANA time zone, the zone the times of the answer are written in.
admin1stringThe 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.
populationinteger, 0 or moremay be nullThe city's population, as GeoNames gives it; null where it has none.
camerasbooleantrue 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.

PropertyTypePresenceDescription
slugstringThe city's key in the path, as /v1/cities/{city}/now takes it.
geonames_idinteger, 1 or moreThe city's GeoNames id, which the path takes in place of the slug.
namestringThe city's name.
countrystringThe city's country, as an ISO 3166-1 alpha-2 code.
latnumber, -90 to 90The latitude of the city, in degrees.
lonnumber, -180 to 180The longitude of the city, in degrees.
tzstringThe 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.

PropertyTypePresenceDescription
idstringThe dataset's id, as the sources of an answer list it; the key a client matches on. New ids may appear.
namestringThe dataset's name.
usestringWhat the service uses the dataset for.
licencestringThe licence the dataset is published under.
attributionstringThe credit line to show.
urlstringmay be nullWhere 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.

PropertyTypePresenceDescription
datestring (date)The local date, in the city's time zone.
summarystringA short English phrase over the shares. Its wording may change: show it, do not parse it.
fieldsFieldsFor 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.
indexinteger, 0 to 100The warmth index: 0 when everyone wears sleeveless tops, 100 when everyone wears a puffer; the mean warmth of fields.upper. Side information.
basisBasismodel: 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.
confidenceConfidenceHow 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.
weatherDayWeatherThe 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.

PropertyTypePresenceDescription
t_minnumbermay be nullThe lowest hourly temperature of the date in °C; null when none of the date's hours has a value.
t_maxnumbermay be nullThe highest hourly temperature of the date in °C; null when none of the date's hours has a value.
feelsnumbermay be nullThe mean apparent temperature of the daytime hours in °C; null when none of them has a value.
precipnumber, 0 or moremay be nullThe total precipitation of the daytime hours in mm; null when none of them has a value.
windnumber, 0 or moremay be nullThe mean wind speed of the daytime hours in km/h; null when none of them has a value.
issued_atstring (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.

PropertyTypePresenceDescription
cityCityRefThe city the answers are for.
daysarray of DayOne item per local date, today first.
sourcesarray of stringThe ids of what the answer rests on; each one is listed in /v1/attribution. New ids may appear.
metaMetamay be absentDiagnostics, 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.

PropertyTypePresenceDescription
codestringStable, machine-readable code in snake_case, for example not_found. New codes may appear.
messagestringExplanation for humans; the wording may change.
request_idstringIdentifier 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.

PropertyTypePresenceDescription
errorErrorBodyThe error.

FieldCounts

The people the cameras judged on each field: whole people, counts and never shares.

PropertyTypePresenceDescription
upperinteger, 0 or moreThe people judged on upper.
lowerinteger, 0 or moreThe people judged on lower.
footwearinteger, 0 or moreThe people judged on footwear.
scarfinteger, 0 or moreThe people judged on scarf.
umbrella_openinteger, 0 or moreThe 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.

PropertyTypePresenceDescription
uppernumber, 0 to 1The cameras' weight in fields.upper.
lowernumber, 0 to 1The cameras' weight in fields.lower.
footwearnumber, 0 to 1The cameras' weight in fields.footwear.
scarfnumber, 0 to 1The cameras' weight in fields.scarf.
umbrella_opennumber, 0 to 1The 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.

PropertyTypePresenceDescription
upperUpperSharesThe outermost layer on the upper body: the share of each value.
lowerLowerSharesWhat is worn on the legs: the share of each value.
footwearFootwearSharesWhat is worn on the feet: the share of each value.
scarfYesNoSharesThe shares of the people with and without a scarf. yes: scarf or snood around the neck. no: no scarf.
umbrella_openYesNoSharesThe 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.

PropertyTypePresenceDescription
opennumber, 0 to 1sandals, flip-flops or other open shoes
closed_lownumber, 0 to 1sneakers, shoes or loafers not covering the ankle
bootsnumber, 0 to 1boots 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.

PropertyTypePresenceDescription
datestring (date)The local date, in the city's time zone.
completebooleanTrue once the local day has ended. The day can still change for a while after that, as late labels arrive.
data_throughstring (date-time)When the newest camera observation behind this day was taken, to the minute.
fieldsHistoryFieldsThe 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.
nFieldCountsHow 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.
indexinteger, 0 to 100may be nullThe 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.
weatherHistoryWeatherThe 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.

PropertyTypePresenceDescription
upperUpperSharesmay be absentThe outermost layer on the upper body: the share of each value.
lowerLowerSharesmay be absentWhat is worn on the legs: the share of each value.
footwearFootwearSharesmay be absentWhat is worn on the feet: the share of each value.
scarfYesNoSharesmay be absentThe shares of the people with and without a scarf. yes: scarf or snood around the neck. no: no scarf.
umbrella_openYesNoSharesmay be absentThe 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.

PropertyTypePresenceDescription
cityCityRefThe city the history is for.
rules_versionstringThe version of the rules every item was made under.
sourcesarray of stringThe ids of the camera sources behind the items; each one is listed in /v1/attribution. New ids may appear.
itemsarray of HistoryDayOne 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.

PropertyTypePresenceDescription
t_minnumbermay be nullThe lowest temperature the day's observations saw, in °C.
t_maxnumbermay be nullThe highest temperature the day's observations saw, in °C.
feelsnumbermay be nullThe 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.

PropertyTypePresenceDescription
shortsnumber, 0 to 1shorts
skirt_shortnumber, 0 to 1skirt or dress ending above the knee
skirt_longnumber, 0 to 1skirt or dress at or below the knee
trousersnumber, 0 to 1trousers, 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.

PropertyTypePresenceDescription
model_versionstringThe version of the model that made the shares.
rules_versionstringThe version of the rules the camera evidence was aggregated under.
catalog_versionstringThe version of the city catalogue.
labelerstringThe labeler that judged the people in the camera evidence.
taxonomy_versionstringThe version of the taxonomy that names the fields and their values.
unitModelUnitThe cultural unit of the city and its shift.
prior_strengthnumberHow many judged people make the cameras weigh as much as the model: the cameras' weight is n / (n + prior_strength).
windowCameraWindowmay be nullThe window of the cameras' evidence; null when no field took any.
weather_pointWeatherPointThe 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.

PropertyTypePresenceDescription
keystringmay be nullThe unit's key; null for a city outside every region. New keys may appear.
calibratedbooleanWhether a calibration names the unit.
shift_cnumberThe 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.

PropertyTypePresenceDescription
cityCityThe nearest city of the catalogue.
distance_kmnumber, 0 or moreIts 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.

PropertyTypePresenceDescription
cityCityRefThe city the answer is for.
atstring (date-time)The start of the local hour the answer describes.
summarystringA short English phrase over the shares. Its wording may change: show it, do not parse it.
fieldsFieldsFor 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.
indexinteger, 0 to 100The warmth index: 0 when everyone wears sleeveless tops, 100 when everyone wears a puffer; the mean warmth of fields.upper. Side information.
basisBasismodel: 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.
confidenceConfidenceHow 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_throughstring (date-time)may be nullWhen the newest camera observation behind this answer was taken, to the minute; null when basis is model.
weatherNowWeatherThe hour's weather and the forecast it comes from.
sourcesarray of stringThe ids of what the answer rests on; each one is listed in /v1/attribution. New ids may appear.
metaMetamay be absentDiagnostics, 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.

PropertyTypePresenceDescription
tnumbermay be nullAir temperature in °C; null when the forecast has no value for the hour.
feelsnumberApparent temperature in °C, which we compute from temperature, humidity and wind (Steadman's formula as the Australian Bureau of Meteorology publishes it).
precipnumber, 0 or moremay be nullPrecipitation in mm in the hour; null when the forecast has no value for the hour.
windnumber, 0 or moremay be nullWind speed in km/h at 10 m; null when the forecast has no value for the hour.
cloudinteger, 0 to 100may be nullCloud cover in percent; null when the forecast has no value for the hour.
codeintegermay be nullThe 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_atstring (date-time)When our stored forecast for this city was last refreshed.
unitsWeatherUnitsThe 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.

PropertyTypePresenceDescription
sleevelessnumber, 0 to 1sleeveless top, tank top, strapless or sleeveless dress
short_sleevenumber, 0 to 1T-shirt, polo, short-sleeved shirt or dress
long_sleevenumber, 0 to 1long-sleeved shirt, blouse or thin long-sleeved top (not knit, not a jacket)
knit_hoodienumber, 0 to 1sweater, hoodie, fleece or cardigan as the outermost layer
light_jacketnumber, 0 to 1windbreaker, denim/leather jacket, trench coat, rain jacket, blazer
coatnumber, 0 to 1wool coat or parka (not quilted)
puffernumber, 0 to 1down jacket or any quilted/padded jacket

VersionOut

The contract, the build, the labeler and the rules behind the numbers of the other routes.

PropertyTypePresenceDescription
servicestringThe name of the service.
apistringThe major version of the API: the v1 of the paths.
contract_versionstringThe version of this API's contract, MAJOR.MINOR. The minor rises with a release that only adds; the guide's changelog lists the releases.
versionstringThe version of the service.
buildstringmay be nullThe git commit the running image was built from; null for a build without one.
taxonomy_versionstringThe version of the taxonomy that names the fields and their values.
labelerstringThe labeler that judges the people in the camera evidence the answers use.
model_versionstringThe version of the model that makes the shares.
rules_versionstringThe version of the rules the camera evidence is aggregated under.
catalog_versionstringmay be nullThe 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.

PropertyTypePresenceDescription
slugstringThe slug of the city whose forecast was read: the asked city itself if it has one.
distance_kmnumber, 0 or moreIts distance from the asked city in km, to one decimal.

WeatherUnits

The units of NowWeather's values.

PropertyTypePresenceDescription
tstringThe unit of t: degrees Celsius.
feelsstringThe unit of feels: degrees Celsius.
precipstringThe unit of precip: millimetres.
windstringThe unit of wind: kilometres per hour.
cloudstringThe 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.

PropertyTypePresenceDescription
yesnumber, 0 to 1The share of the people for whom the answer is yes.
nonumber, 0 to 1The share of the people for whom the answer is no.