---
title: "Start or immediately satisfy a household bin collection lookup"
method: POST
path: "/lookups"
---

# Start or immediately satisfy a household bin collection lookup

`POST /lookups`

## Request

- Base URL: `https://whenisbins.com/v1`
- URL: `https://whenisbins.com/v1/lookups`
- Auth: one of:
  - none
  - HTTP bearer

The journey's single write. Send `postcode` plus either
free-text `property` (house number or name — may resolve
straight to a result) or a `property_id` picked from a
candidate list. If the address cannot be resolved to exactly
one property, the response is 422 with the candidate list
embedded and **no lookup is created**.

Free-text `property` must name the building: a house number or a
house/building name, optionally with its flat/unit before it and
its street after it (`Flat 2, 60, Kings Road`). A flat, floor or
unit descriptor on its own — `Ground Floor Flat`, `Flat 2`, with
or without a street — can never match a council's list, so it is
refused with 422 `property_identifier_required` before any lookup
exists (the held candidates ride on that response too).

`allow_postcode_representative` (Q10) must only be sent after
the citizen explicitly consents to neighbour-based dates; it is
honoured only when the exact property is not listed.

The lookup row is created before any council work begins. Cache hits
return immediately. When recent cold timing predicts a result below
ten seconds, when no timing history exists yet, or when the route
maintainer's control plane holds the council's active deterministic
route healthy (the estimate is then that route's own recent p90, or
unknown, whatever the blended history says), the server briefly
awaits deterministic work. For predicted longer waits it returns the
durable running lookup immediately. Crossing the brief response budget
also returns that durable lookup; in either case the same work
continues in the background instead of restarting. For journeys that
are already in, or historically expected to need, the fallback worker,
the estimate also includes current queue depth and recent stage
execution time. Queue telemetry can increase but never reduce the
historical estimate, and the healthy-route rule never applies once a
lookup has reached the browser or Opus stage or a residential handoff.

Previously verified knowledge is looked up by `property_id`, not by
address spelling. A stale result that still contains future dates is
returned immediately with its original `retrieved_at` while a
low-priority refresh runs in the background.

## Headers

- `Idempotency-Key` string

## Request body

- LookupRequest: Send only the field or fields `GET /addresses` asked for. Any field not listed here is rejected with 400 `invalid_request` naming it — the body is not silently ignored.
  - `postcode` string, required
  - `property` string: The first line of the address (optional — free text, including the manual-entry flow): a house number or name, optionally preceded by its flat/unit and followed by its street, comma-separated — `15`, `Rose Cottage`, `Flat 2, 60, Kings Road`. It must carry a house number or a house/building name; a flat, floor or unit descriptor alone is refused with `property_identifier_required`. Ignored when `property_id` is given.
  - `property_id` string: A candidate id previously returned; selects the service's stored candidate mapping and is not itself council-address evidence.
  - `street` string: Street/road name, accepted only when GET /addresses requests a street-based input.
  - `locality` string: Council-recognised town, locality, settlement, island, or legacy collection area when requested by GET /addresses.
  - `normal_weekday` 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday': Citizen-confirmed normal collection weekday. It is used only inside dates/weeks the council explicitly publishes and is never inferred from a neighbour.
  - `property_type` 'private_flat_without_bin_store' | 'private_block_with_bin_store' | 'housing_estate' | 'street_bag_collection': City of London residential service model when requested; rule-level results are not represented as a dated property calendar.
  - `allow_postcode_representative` boolean: Q10 consent flag. Send ONLY after the citizen explicitly opts in to labelled neighbour-based dates; honoured only when the exact property is not listed. Never the default.

## Response `201`

Lookup created. `status` may already be `done` (cache hit)
or `queued`/`running`/`partial` — the body is the same Lookup
resource `GET /lookups/{lookup_id}` serves.

- Lookup
  - `id` string, uuid, required
  - `status` 'queued' | 'running' | 'partial' | 'done' | 'failed', required: `partial` = a usable result is attached while fuller dates (ICS/PDF completion) are still being fetched — check each collection's `dates_complete`.
  - `created_at` string, date-time, required
  - `council` CouncilRef, required
    - `id` string, required: GSS code — the canonical, rename-proof council identity.
    - `name` string, required
    - `lookup_url` string, uri: The council's own bin-day checker page, when known (https only). The honest fallback for the citizen on every wait and failure — the same page a served Schedule reports as `source_url`.
    - `missed_bin_url` string, uri, nullable: Council-specific HTTPS operational missed-collection reporting page, from the approved verified reporting registry. Null when unavailable; never substituted with the checker, broader problem or formal complaint route. Returned on council discovery, lookup and schedule council objects. Read the council's current reporting window, disruptions, exclusions and evidence requirements. Too early? Keep the details and, with permission, follow up when the window opens and before it closes. If you cannot, give the owner the council link and saved details. Check-in consent is not permission to submit. Get separate permission to report and share personal details with the council. Check for an existing report in the council account or by reference if you have access; otherwise ask the owner. After a timeout or no confirmation, reconcile before retrying. Deduplicate by property + due date + waste stream (bin type). Only claim success when the council confirms it; keep its reference and next steps. Do not promise a return collection: some streams have none. WhenIsBins supplies dates and a link; it does not schedule check-ins or submit reports. See the guide for the full check-in and reporting rules.
    - `missed_bin_checked_at` string, date, nullable: Registry verification date (YYYY-MM-DD) for the published reporting route, not the schedule retrieval time or a live availability check. Null when missed_bin_url is null. Approved links remain published until reviewed and withdrawn or updated, even when their review date has passed.
  - `expected_wait_seconds` integer, nullable, required: Route-aware recent cold-visit p90, rounded up to seconds. Fast deterministic routes supply the initial estimate when they serve at least half of recent cold visits; a deterministic miss promotes the lookup to at least 120 for the browser handoff. Null until five timed cold observations exist in the rolling 84-day window. While the route maintainer's control plane holds the council's active deterministic route healthy, the deterministic-stage estimate is instead the p90 of recent cold successes on the fast routes alone (null below five such successes), whatever the blended history or success rate says. A lookup parked for the residential worker after a council bot-blocked the web host is estimated from that worker's own deterministic stage timing (pickup plus execution, p90) from its first recent sample; with none it shows the handoff.
  - `success_rate` number, double, nullable, required: Share of cold observations with outcome `success` in the same rolling window. Null until five eligible cold observations exist. Cache hits are never included.
  - `completed_at` string, date-time, nullable
  - `result` LookupSchedule: The stable address-free answer (dictionary section 2). Says how the property was matched and how much to trust the dates without publishing the exact address.
    - `property_id` string, required: Echo of the requested property id (mockup amendment 5) — clients should assert it matches what they asked for.
    - `address_match` 'exact' | 'street' | 'postcode' | 'postcode_representative', required: `exact` includes a citizen's own pick. `street`/`postcode` = council publishes at that granularity (notes explain). `postcode_representative` = a labelled same-postcode neighbour answer. It may follow explicit consent, measured evidence that permits automatic serving, or the narrower `provisional` interim gate. It is never presented as an exact-property result.
    - `council` CouncilRef, required
      - `id` string, required: GSS code — the canonical, rename-proof council identity.
      - `name` string, required
      - `lookup_url` string, uri: The council's own bin-day checker page, when known (https only). The honest fallback for the citizen on every wait and failure — the same page a served Schedule reports as `source_url`.
      - `missed_bin_url` string, uri, nullable: Council-specific HTTPS operational missed-collection reporting page, from the approved verified reporting registry. Null when unavailable; never substituted with the checker, broader problem or formal complaint route. Returned on council discovery, lookup and schedule council objects. Read the council's current reporting window, disruptions, exclusions and evidence requirements. Too early? Keep the details and, with permission, follow up when the window opens and before it closes. If you cannot, give the owner the council link and saved details. Check-in consent is not permission to submit. Get separate permission to report and share personal details with the council. Check for an existing report in the council account or by reference if you have access; otherwise ask the owner. After a timeout or no confirmation, reconcile before retrying. Deduplicate by property + due date + waste stream (bin type). Only claim success when the council confirms it; keep its reference and next steps. Do not promise a return collection: some streams have none. WhenIsBins supplies dates and a link; it does not schedule check-ins or submit reports. See the guide for the full check-in and reporting rules.
      - `missed_bin_checked_at` string, date, nullable: Registry verification date (YYYY-MM-DD) for the published reporting route, not the schedule retrieval time or a live availability check. Null when missed_bin_url is null. Approved links remain published until reviewed and withdrawn or updated, even when their review date has passed.
    - `collections` Collection[], required
      - `name` string, required: The service exactly as the council words it — never rewritten.
      - `waste_type` 'refuse' | 'recycling' | 'garden' | 'food' | 'glass' | 'textiles' | 'electricals' | 'paper_card' | 'plastics_cans' | 'other', required
      - `dates` string[], required: Upcoming dates from today through 92 days ahead, sorted. Normally council-published; a council-published normal weekday may be translated into its single next occurrence by the bounded deterministic projection rule and is labelled with schedule-level `date_confidence: council_projection`. Clients render weekday + date ("Wednesday 12th August") — derived, not shipped.
      - `schedule` string: Council wording such as a weekday/time. Required when `date_completeness` is `weekday_only` and retained when it is the basis of a labelled weekday projection. Consumers must not expand it themselves.
      - `dates_complete` boolean, required: False while a fuller calendar is being fetched, or whenever this service has no dates (including weekday_only answers). For a done lookup with missing dates, request a fresh lookup; polling the old lookup does not refresh it.
      - `bin_colour` string, required: Legacy lookup colour field, else neutral `light_grey`. Historically includes overall wording such as blue bin. Prefer appearance for authoritative component distinctions and reviewed council profiles.
      - `lid_colour` string, required: Defaults to the bin's colour when unstated.
      - `colour_source` 'council' | 'default', required: Legacy combined flag; council means at least one component was stated. Use the component flags before describing or colouring a body or lid.
      - `bin_colour_source` 'council' | 'default', required: Legacy provenance for bin_colour, which can describe an overall container rather than an explicit body. A default value is presentation only. Prefer appearance_occurrences for dated instructions.
      - `lid_colour_source` 'council' | 'default', required: Whether the lid colour was independently council-stated. A lid inherited from the body colour is default.
      - `container` 'wheelie_bin' | 'caddy' | 'box' | 'bag' | 'sack' | 'communal', required: Shape derived from the service label or an explicit container noun phrase from a gated deterministic browser route, otherwise wheelie_bin. Untrusted container_kind fields and contradictory descriptions are ignored. Colour provenance alone does not establish shape evidence.
      - `appearance` ContainerAppearance: Compatible v1 appearance resolved conservatively across all dates from verified lookup wording or an applicable reviewed council profile. Overall colour is not proof of either body or lid colour. No unknown component is filled from a different source. The legacy collection colour/container fields remain compatibility fields and do not contain profile enrichment. Profile evidence must apply to every emitted date in the collection. Source dates are preserved; public content currently has no age expiry.
        - `schema_version` 1, required
        - `origin` 'lookup' | 'profile' | 'unknown', required
        - `container_kind` 'wheelie_bin' | 'bin' | 'caddy' | 'box' | 'bag' | 'sack' | 'communal' | 'unknown', required
        - `description_kind` string: Optional profile wording category; container when the profile does not establish a single shape.
        - `lid_presence` 'present' | 'absent' | 'unknown', required
        - `attributes` object[], required
          - `component` 'overall' | 'body' | 'lid', required
          - `attribute` 'colour' | 'transparency', required
          - `value` string, required
          - `qualifier` string, nullable, required: Qualified descriptions must not become definitive colour imagery.
        - `profile_id` string, nullable, required
        - `checked_at` string, nullable, required: Original profile evidence check timestamp; lookup observations instead use the schedule retrieval provenance.
        - `sources` object[], required: Public council guidance sources for profile evidence; lookup evidence remains attributed to the schedule source_url.
          - `url` string, uri, required
          - `title` string, required
          - `publisher` string, nullable, required
      - `contents` object, nullable, required: Compact union of the council material labels in appearance_occurrences, with markup removed and whitespace trimmed. Null means no contents evidence is held, not that the service accepts nothing. Never inferred from colour, service name or waste type. The list is not exhaustive and does not say every container takes every material: occurrence items and their conditions remain authoritative. No separate not_accepted list is available in the source material type.
        - `accepted` string[], required: Council labels kept whole, not split or interpreted; may include qualifications or exclusions in the council wording.
        - `source` 'council', required
        - `varies_by_date` boolean, required: True if material assignments, conditions or evidence coverage differ across dates or variants, including unknown occurrences. Consult the per-occurrence items before giving sorting instructions. False does not establish completeness.
      - `appearance_occurrences` ContainerOccurrence[]: Preferred dated container projection. Each row belongs to an existing collection date or the existing single-weekday projection; null date means this service has no usable date. Never apply one row to another date. Missing rows are unknown. The legacy fields and appearance v1 retain their previous meanings. This field creates no dates or services.
        - `schema_version` 1, required
        - `date` string, date, nullable, required
        - `status` 'resolved' | 'conditional' | 'partial' | 'unknown', required: Resolved describes supported objects, not proof that all household containers are known.
        - `complete` boolean, required: True only for an unconditional, reviewed exhaustive group. A directly observed object alone does not establish completeness.
        - `sources` Sources, required (unresolved $ref)
        - `variants` object[], required: Objects within a variant go together. Different variants are alternatives, never additions. Keep their conditions visible. Unknown has no variants.
          - `id` string, required
          - `condition` string, nullable, required
          - `complete` boolean, required
          - `sources` Sources, required (unresolved $ref)
          - `items` object[], required
            - `id` string, required: Reviewed object identity; do not deduplicate by colour or shape.
            - `material` string, nullable, required
            - `quantity` integer, nullable, required: Only an explicitly evidenced quantity; null does not imply one.
            - `appearance` OccurrenceContainerAppearance, required: Same supported component semantics as appearance v1, with explicit shape evidence and the extended kind vocabulary. An overall colour may fill an identifying silhouette without becoming body/lid facts. Unknown shape is never a wheelie-bin fallback. Bare bag does not establish reusable handles.
              - …
      - `subscription_required` boolean: Present and true when the council's own text says this service is paid or opt-in. Also true for garden waste on a representative answer, because dates may transfer while the exact property's subscription entitlement remains unproven.
    - `by_date` CollectionDay[], required: The same dates as `collections[].dates`, regrouped: one entry per day on which anything is collected, soonest first, each naming its weekday and every service due. It adds no date, moves none and projects none, so it carries exactly the evidence `dates` carries and the same `date_confidence` and `date_completeness` apply. Empty when no service has a concrete date (`weekday_only`). Use it to answer "what is collected on this day?" without calendar arithmetic.
      - `date` string, date, required: A local calendar date in Europe/London, exactly as it appears in the `dates` of each service listed here.
      - `weekday` 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday', required: The weekday of `date`, worked out by the service so that a client need not. It states nothing about the council's usual collection day.
      - `collections` object[], required: Every service due on `date`, in the order of `Schedule.collections`.
        - `name` string, required: The service's `name` in `Schedule.collections`.
        - `waste_type` string, required: The service's `waste_type` in `Schedule.collections`.
        - `subscription_required` boolean: Present and `true` when the service carries `subscription_required` in `Schedule.collections`, so an answer built from this entry alone still names the subscription.
    - `date_confidence` 'published_calendar' | 'council_projection' | 'next_collection_only', required: `published_calendar` = real dates the council publishes. `council_projection` = dates published by the council's own projected calendar, or one next date calculated by the service's bounded normal-weekday rule (holiday changes may be missing). Council-published dates are retained through the 92-day horizon; only the service's own calculation is limited to one occurrence. `next_collection_only` = nothing beyond the next date(s) exists — do not imply more is coming.
    - `date_completeness` 'next_only' | 'limited_horizon' | 'full_horizon' | 'weekday_only', required: Separate from validity: `next_only` is concrete next date(s), `limited_horizon` is a valid incomplete slice, `full_horizon` was verified across the serving horizon, and `weekday_only` contains council-published weekday wording that could not safely be translated into dates. Weekday projections are `limited_horizon` and contain at most the next date for each collection type. A nearby UK bank holiday does not suppress that date; any explicit council exception the run saw takes precedence.
    - `evidence_granularity` 'property' | 'street' | 'postcode' | 'area' | 'round', required: The level at which the council publishes the schedule evidence; separate from how the requested address was matched.
    - `retrieved_at` string, date-time, nullable, required: When we fetched this from the council (answers are cached). Null for historical cached lookups whose original retrieval time was not retained; never substitute the time the cache was read.
    - `refresh_due_at` string, date-time, nullable, required: The instant this answer stops being fresh under the service's refresh policy — `retrieved_at` plus 28 days, or plus 7 days while today is within a fortnight of a UK bank holiday. Before it, the answer is an acceptable cache and no refresh is owed. After it, a read of the schedule resource queues a background refresh and still returns the answer as it stands; a later read returns the refreshed answer with a new `retrieved_at`, `refresh_due_at` and `ETag`. It is policy, not a promise of when the refresh runs. Null when `retrieved_at` is null.
    - `source_revalidated_at` string, date-time: When a shared source artifact was most recently conditionally revalidated.
    - `artifact_version` string: Opaque content-hash and parser-version identity for a deterministic shared calendar.
    - `provisional` boolean: Present and `true` only on an INTERIM answer: a same-postcode neighbour's schedule, served in under a second while the lookup for this exact property is still running. It is always accompanied by `address_match: postcode_representative`, `dates_complete: false` on every collection, and a `notes` caveat naming the neighbour. The lookup carrying it is `partial`, never `done`. An interim is never cached: it does not appear at `GET /v1/schedules/{property_token}`, in a calendar feed, in a reminder or in an email, and it is replaced in place by the exact answer as soon as that arrives. A client that treats it as final is misreading it — keep polling the lookup. It is fail-closed: the council must pass the measured zero-mismatch gate and the postcode's held comparable neighbours must also pass the serve-time verification gate. See docs/interim-then-upgrade-serving-2026-08-27.md for the internal evidence thresholds; clients must rely on `provisional`, not reproduce those policies.
    - `notes` string: Plain-English caveats written for the citizen (assisted collections, communal bins, granularity, …).
    - `source_url` string, uri: The council's own checker page ("check on your council's site"): the page the serving run actually read, else the council's known checker page from our council intel. https only.
    - `uprn` string: The property's Unique Property Reference Number, present only when the service has verified it against OS Open UPRN AND public feeds are on (`PUBLIC_FEEDS=1`). It keys the shareable public calendar `https://whenisbins.com/{uprn}.ics` and the `/schedules/uprn/{uprn}` form of this resource. Canonical digits, no zero padding.
    - `council_id` string: The council's own identifier for the property (the value its address list uses), canonical form, present only when the service captured it from an exact match AND public feeds are on. Keys `https://whenisbins.com/{council.id}/{council_id}.ics` and the `/schedules/council/{gss}/{id}` form of this resource — the public calendar for councils whose identifiers are not UPRNs.
    - `calendar_url` string, uri: The property's public, subscribable iCalendar feed, present only when public feeds are on AND the Schedule carries a `uprn` or a `council_id`. Always the `uprn` form when there is a `uprn`, otherwise `https://whenisbins.com/{council.id}/{council_id}.ics`. Use this value as given rather than assembling a link. It is a website resource, not a `/v1` route: collection dates only, no address or personal details, one event per collection date (`?per=service` for one per service, `?download=1` for a one-off file). Reading it never creates a lookup. Give it to a person to subscribe to; its dates are this Schedule's dates, so read `date_confidence` and `date_completeness` here before describing how far ahead it reaches. Absent means there is no public calendar for this property; never build one from `property_id`.
    - `matched_address` string, required: The property as the council's own system lists it, verbatim.
  - `problem` string: Present when status is `failed` (same codes as Problem).
  - `detail` string: Citizen-actionable explanation when `failed`.
  - `candidates` Candidate[]: Present only on a failed `address_not_found` lookup, in two cases: the lookup discovered the council's candidate list while it was running and the citizen must now pick from it, or the lookup ended because the council's own list for the postcode does not include the typed property. Either way it is the council's list for that postcode; clients can resubmit the original postcode with one selected `property_id`. The Lookup resource does not echo the postcode or typed address.
    - `id` string, required: Opaque service token — echo back as `property_id`; never parse it or assume it is a council UPRN.
    - `label` string, required: The property exactly as the council's checker words it.
  - `progress` object: Safe, optional progress information for a non-final lookup.
    - `stage` 'deterministic' | 'browser' | 'opus'
    - `message` string
    - `steps` object[]: The rungs this lookup has reached, oldest first, ending with the one it is on. The ladder is a fallback chain, not a pipeline — every rung does the same job — so a rung the lookup has moved past was attempted and did not produce an answer (`missed`), never "completed". Rungs not yet reached are not listed: a lookup served by the first rung reports exactly one step. A refresh or legacy lookup is off the ladder and reports an empty list.
      - `stage` 'deterministic' | 'browser' | 'opus', required
      - `state` 'missed' | 'active' | 'waiting', required: `missed` = attempted, produced no answer, escalated; `active` = running now; `waiting` = queued for the fallback worker and not yet started.
    - `queue_ahead` integer: Citizen lookups queued ahead of this one for the fallback workers — the same work-ahead set that informs `expected_wait_seconds`, counted rather than timed. Workers drain several lookups at once, so it is a count of work ahead, not a position in a single file. Present only while a rung is `waiting` on a worker; absent for the web pool's own deterministic path, and absent when no queue snapshot could be read. A count of queued rows is a fact rather than a projection, so it needs no sample threshold.

## Other responses

- `400`: Malformed input (e.g. `invalid_postcode`).
- `401`: Supplied bearer token is malformed, unknown or revoked, or a token is missing during a token-only hold (`unauthorized`).
- `403`: Browser origin not allowed to create lookups (`invalid_request`). Server-to-server callers are unaffected.
- `405`: The path exists but not with this method. Every resource below answers exactly one method; the `Allow` header names it.
- `409`: The Idempotency-Key was previously used for a different lookup request (`idempotency_conflict`).
- `413`: The JSON body exceeds the 8 KiB parser limit. Returned as `invalid_request`; no lookup is created.
- `415`: Body was not `application/json` (`invalid_request`).
- `422`: Address unresolved — `ambiguous_address`, `address_not_found`, `property_identifier_required` (the first line names no house number or building name), or `property_dates_unavailable` (the council lists the property but has no dates for it), with candidates embedded when a useful list is held. Also `postcode_outside_coverage` / `council_not_supported` (without candidates; the latter covers both a council with no readable checker and one whose checker is behind a CAPTCHA, which is never bypassed), `council_site_maintenance` while a recent observation of the council's own maintenance page is current, and `council_site_unavailable` while a confirmed outage is current (two recent independent lookups met the council's checker erroring before any address was chosen; nobody said maintenance). The maintenance, outage and CAPTCHA holds apply only to a FRESH lookup: a schedule already held is still served as a 201, and an open lookup for the same property is still joined. A lookup whose own work ends in an address refusal (`address_not_found`, `property_dates_unavailable`) or a CAPTCHA refusal inside the brief response hold is answered with this same 422 rather than a `failed` 201; there is nothing to poll. A maintenance page first seen during that work is instead a 201 whose Lookup is `failed` with `council_site_maintenance`.
- `429`: Request, provider or new-work allowance exceeded; optional limit_scope and token_request_url explain the limit.
- `500`: An unexpected fault on our side (`server_error`). Nothing is implied about whether the work started; retry with the same `Idempotency-Key` where one applies.
- `503`: Required service configuration, a dependency, or shared lookup admission is temporarily unavailable (`service_unavailable`).

---

[API](https://skmtc.dev/whenisbins/apis/whenisbins-uk-bin-collection-api-for-apps-and-ai-agents.md) · [All operations](https://skmtc.dev/whenisbins/apis/whenisbins-uk-bin-collection-api-for-apps-and-ai-agents/llms.txt) · [OpenAPI document](https://skmtc.dev/whenisbins/apis/whenisbins-uk-bin-collection-api-for-apps-and-ai-agents/revisions/6e4a960474a4?raw)
