WhenIsBinswhenisbins

UK bin collection dates for apps and AI agents

AI agent, or short on context? Read the one-page quickstart first: the whole happy path and recurring checks in one page. This guide is about 50 KB.

Find upcoming household rubbish, recycling, food waste and garden waste collection dates using a UK postcode and address.

WhenIsBins checks councils' own published information and returns JSON containing collection dates, waste types, council source links and information about address matching and date confidence. WhenIsBins is an independent service, not a council.

For personal use, start without an account or API key, within the free anonymous limits. Commercial users must contact hello@whenisbins.com before using the API, including through MCP. For regular use, request a free token: its allowance is yours alone, not shared with everyone on your network. Read where our information comes from and how to use it. Using the API or MCP server means accepting our terms and conditions.

Coverage and available dates vary by council and property. Some lookups finish immediately; others require an asynchronous council check. Results can include dates up to 92 days ahead, but a full calendar is not always available.

When to use this API

Use WhenIsBins when a user asks "When is my next bin collection?", "Which bin is collected this week?" or "When is my recycling collected?" It supports apps, home automation and AI assistants that can make HTTP GET and POST requests, and AI clients that support a remote MCP server.

Start with the user's full UK postcode. The API tells you which address details the collecting council needs. Ask for missing information or an exact selection when necessary; a postcode alone may not identify the home.

For bank-holiday questions, use the returned dates and qualifications. Do not calculate an assumed timetable change. Use council coverage to check supported authorities; coverage is not a guarantee for every property.

This API retrieves household collection information. It does not report missed bins, create reminders, arrange collections, serve commercial waste enquiries, or establish whether a household subscribes to garden waste. It supplies the council's own missed-bin reporting link when one has been verified.

WhenIsBins reads collection dates. It does not set reminders, check whether a bin was emptied or report a missed bin, and there is no WhenIsBins report-submission endpoint. Where we hold a verified link, a response names the council's own page for reporting a missed collection, which may be useful if you are helping someone whose bin was not emptied.

  • council.missed_bin_url appears on a Lookup's council and result.council, in address discovery, council discovery and saved Schedules. It is the council's operational missed-collection page, not lookup_url (the date checker), source_url, or a formal complaint route.
  • missed_bin_checked_at is the date that link was last verified in our registry, not a live availability check or the schedule's retrieval time.
  • Both fields are null when no approved verified link is held. Older responses may omit them; treat omission like null. Approved links remain visible until deliberately updated or withdrawn, even after their review date.

Reporting rules differ by council and waste stream: when reports open and close, known disruptions, exclusions, the evidence needed and whether the crew returns. The council's page is the authority on all of them. For example, checked on 2 October 2026:

  • Birmingham: general waste reports open after 1:30pm; known incomplete rounds are excluded.
  • Colchester: reports open after 5:30pm and must be made within 2 days of the missed collection.
  • Cambridge: reports open at 3:30pm and close at 3:30pm the next weekday (Monday to Friday). Cambridge does not return for missed outdoor food waste caddies.

A returned collection date is not proof that a collection was due for a particular household or that a report is eligible. Past dates drop out of a Schedule once the day has passed; this is not cancellation. Projected-date, representative-address, subscription and provisional qualifications apply to a date whatever it is used for.

Agent quickstart

The base URL is https://whenisbins.com/v1. Occasional queries need no token, registration or browser challenge. Limits and retries apply to anonymous and token clients, including requests that start new work.

  1. Discover: call GET /addresses?postcode=... with a URL-encoded postcode. Read required_input, candidates and input_options.
  2. Resolve: use the user's exact, unambiguous address or ask them to select a candidate. Follow required_input for other council journeys. Never guess a property or consent to a neighbouring-property answer on the user's behalf.
  3. Submit once: save the selected JSON and a new Idempotency-Key privately, then send them to POST /lookups. Reuse both unchanged after a lost response.
  4. Inspect: HTTP 201 means the Lookup was created, not necessarily completed. done and failed are terminal. queued, running and partial need continuation.
  5. Continue: poll the same lookup ID, respecting Retry-After and at least five seconds between snapshot polls. The client example waits for at most two minutes. If still pending, retain the ID and any qualified partial result for a later user-requested check. Never resubmit because a lookup is slow.
  6. Answer: present the returned dates, service names and council source with the relevant address, confidence, completeness and subscription qualifications. Save property_id only if the integration needs to retrieve the schedule later.

An assistant needs a permitted HTTP tool or installed integration to execute this journey. Reading these pages does not itself install a tool. A GET-only fetcher can read documentation and existing resources; creating a lookup needs POST support. Pending work is not a promise of eventual success.

Check that you can reach the API

This fixed-reference request creates no lookup job:

curl --fail-with-body \
  https://whenisbins.com/v1/waste-types

A shortened response looks like this:

{
  "waste_types": [
    { "id": "refuse", "display_name": "General waste" },
    { "id": "recycling", "display_name": "Recycling" },
    { "id": "garden", "display_name": "Garden waste" }
  ]
}

Dates use ISO 8601 YYYY-MM-DD format; timestamps use ISO 8601 UTC format. Interpret relative dates such as "today" in Europe/London.

The lookup journey

A lookup normally takes these steps:

  1. Ask /addresses what information the council needs for the postcode.
  2. Send that information to POST /lookups.
  3. If the lookup is still running, wait for it to change.
  4. Read the schedule from the completed lookup.
  5. Save property_id if you need to retrieve the address-free schedule later.

Some lookups finish immediately from stored information. Others take longer because the service must check the council's website. Always inspect the returned status; do not assume a 201 Created response already contains a finished schedule.

1. Find out what address information is needed

Send the postcode to GET /addresses:

curl --fail-with-body --get \
  --data-urlencode "postcode=CB4 2HX" \
  https://whenisbins.com/v1/addresses

The response identifies the collecting council and tells you what to send next. For a council with a property list it may look like this:

{
  "postcode": "CB4 2HX",
  "council": {
    "id": "E07000008",
    "name": "Cambridge City Council",
    "lookup_url": "https://www.cambridge.gov.uk/check-when-your-bin-will-be-emptied",
    "expected_wait_seconds": 8,
    "success_rate": 0.96
  },
  "required_input": "property_id",
  "postcode_representative": "opt_in",
  "candidates_source": "council",
  "candidates": [
    {
      "id": "p:4c5ee6c2f2c7c959",
      "label": "15 EXAMPLE COURT, EXAMPLE ROAD, CAMBRIDGE, CB4 2HX"
    }
  ]
}

The response is a guide for the next request:

  • required_input tells you which field or fields to ask the user for.
  • candidates contains council-provided properties when a pick-list is available. Display label and send the selected id as property_id. The list is the council's own and normally at most 14 days old; when nothing fresher can be had, a copy up to 45 days old is returned rather than nothing.
  • unavailable_candidates, when present, names properties the council lists but returned no collection record for on a recent lookup. Tell the user the truth about them instead of leaving them to conclude the address is missing, but never offer them as a choice: selecting or typing one is answered at once with 422 property_dates_unavailable.
  • source_notice, when present, says what the service already knows about the council's own checker, as a problem and a detail sentence to show beside the address step. It is a notice on a successful response, never a refusal. For council_site_maintenance and council_not_supported, a fresh lookup would be refused with that problem, but a schedule the service already holds is still returned, so submit the lookup as normal. For council_site_unavailable nothing is refused: the council's website is proving hard to read, so the lookup may be slow and may fail.
  • input_options, when present, contains council-provided roads or areas. Display each option's label and send its value in the field named by input_options.field. When input_options is absent, supply the field requested by required_input as free text. A street directory with no matches is omitted, so required_input: street still accepts a street name. The exception is a council whose whole road list is held (City of Edinburgh, and Nuneaton and Bedworth): a q matching nothing returns input_options with total: 0 and empty options, which means the council does not list that road. Free text still works there.
  • input_options.not_listed_help, when present, is the council's own help for a road that is not in its list (for Edinburgh, homes that use communal bins). Show its text and link its link_text to its url.
  • When input_options.needs_more_query is true, its options array is empty on purpose. Ask for more of the road or area name and repeat the request with a narrower q.
  • input_options.not_listed_value is the value for a final "none of these" choice. It returns the user to ordinary address entry and must never be sent to a council directly.
  • candidates_source is council when candidates came from the council's own checker. unavailable means the service does not currently hold a property list: ask for the first line of the address and send it as property.
  • postcode_representative tells you whether a labelled neighbouring-property answer is automatic, available after explicit opt_in, or unavailable.
  • expected_wait_seconds is an estimate, not a deadline. It can be null when there is not enough recent evidence.
  • success_rate is the recent success rate from 0 to 1. It can also be null.

required_input can have the following values:

ValueWhat the client should send to POST /lookups
noneOnly postcode is required.
property_idA selected candidates[].id as property_id.
propertyThe first line of the address as property.
streetA road or street name as street.
road_and_localityBoth street and locality.
street_and_propertyBoth street and property. This exists for compatibility with a small number of council journeys.
settlementA town, village, island or settlement as locality.
settlement_or_roadThe council-recognised settlement or road as locality.
normal_weekdayOne named weekday, such as Monday, as normal_weekday.
normal_weekday_and_localityBoth normal_weekday and locality.
property_typeOne of the returned council journey's supported residential property types as property_type.

The property_type values currently accepted are private_flat_without_bin_store, private_block_with_bin_store, housing_estate, and street_bag_collection. Only ask for one when required_input is property_type.

For road and area pickers, you can narrow input_options with the optional q parameter:

curl --fail-with-body --get \
  --data-urlencode "postcode=EH14 7AL" \
  --data-urlencode "q=Lanark Road" \
  https://whenisbins.com/v1/addresses

Do not guess or parse candidate IDs. Treat every property_id as an opaque string and send it back unchanged.

When an exact address is not listed

If postcode_representative is opt_in and the lookup returns 422 address_not_found, you may offer a clearly worded choice to use dates from a comparable neighbouring property. Only after the user agrees, repeat the same lookup with:

{
  "postcode": "CB4 2HX",
  "property": "15",
  "allow_postcode_representative": true
}

When the value is automatic, the service may return a representative result without this flag because recent evidence shows the postcode is homogeneous. When it is unavailable, do not offer the option. In every case, inspect the successful Schedule's address_match; representative answers remain labelled postcode_representative and include a caveat in notes.

2. Create the lookup

Send JSON to POST /lookups. The simplest direct lookup contains a postcode and the first line of the address:

curl --fail-with-body \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 33f146d8-7ff4-4d1c-979d-8a5cb0441dd1" \
  -d '{"postcode":"CB4 2HX","property":"15"}' \
  https://whenisbins.com/v1/lookups

If the user selected a candidate, send its ID instead:

{
  "postcode": "CB4 2HX",
  "property_id": "p:4c5ee6c2f2c7c959"
}

Use the field or fields requested by /addresses. Do not send unrelated fields. Unknown fields are rejected.

The optional Idempotency-Key header makes retries safe. Generate a new random value for one logical lookup and reuse that value only when retrying the same request. Reusing it with different lookup details returns 409 idempotency_conflict. A retry returns the same lookup id whether the first request was answered at once from stored information or is still running. A key is remembered for 30 days, or until its lookup is removed.

The API returns 201 Created after it creates the durable lookup. The Location header contains its status URL. The response body is the same Lookup resource returned by GET /lookups/{lookup_id}.

A lookup that finished immediately

{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "status": "done",
  "created_at": "2026-09-07T09:00:00Z",
  "completed_at": "2026-09-07T09:00:01Z",
  "council": {
    "id": "E07000008",
    "name": "Cambridge City Council",
    "lookup_url": "https://www.cambridge.gov.uk/check-when-your-bin-will-be-emptied"
  },
  "expected_wait_seconds": 8,
  "success_rate": 0.96,
  "result": {
    "property_id": "p:4c5ee6c2f2c7c959",
    "matched_address": "15 EXAMPLE COURT, EXAMPLE ROAD, CAMBRIDGE, CB4 2HX",
    "address_match": "exact",
    "council": {
      "id": "E07000008",
      "name": "Cambridge City Council",
      "lookup_url": "https://www.cambridge.gov.uk/check-when-your-bin-will-be-emptied"
    },
    "collections": [
      {
        "name": "Black bin",
        "waste_type": "refuse",
        "dates": ["2026-09-10", "2026-09-24"],
        "dates_complete": true,
        "bin_colour": "black",
        "lid_colour": "black",
        "colour_source": "council",
        "container": "wheelie_bin"
      },
      {
        "name": "Blue bin",
        "waste_type": "recycling",
        "dates": ["2026-09-17", "2026-10-01"],
        "dates_complete": true,
        "bin_colour": "blue",
        "lid_colour": "blue",
        "colour_source": "council",
        "container": "wheelie_bin"
      }
    ],
    "date_confidence": "published_calendar",
    "date_completeness": "limited_horizon",
    "evidence_granularity": "property",
    "retrieved_at": "2026-09-07T09:00:01Z",
    "source_url": "https://www.cambridge.gov.uk/check-when-your-bin-will-be-emptied"
  }
}

The example values are illustrative and the response is shortened. Collection names, dates, colours and source details always depend on what the relevant council publishes.

A lookup that is still running

{
  "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "status": "queued",
  "created_at": "2026-09-07T09:00:00Z",
  "council": {
    "id": "E07000008",
    "name": "Cambridge City Council"
  },
  "expected_wait_seconds": 120,
  "success_rate": 0.81,
  "progress": {
    "stage": "deterministic",
    "message": "Checking the council's fastest sources",
    "steps": [
      { "stage": "deterministic", "state": "active" }
    ]
  }
}

Keep the lookup id and continue with the wait endpoint.

3. Wait for a running lookup

The preferred browser flow is long polling:

curl --fail-with-body -i \
  "https://whenisbins.com/v1/lookups/f47ac10b-58cc-4372-a567-0e02b2c3d479/wait"

The request waits until the lookup changes or for about 25 seconds. Read the X-Lookup-Cursor response header and send it as after when reconnecting:

curl --fail-with-body -i --get \
  --data-urlencode "after=CURSOR_FROM_THE_PREVIOUS_RESPONSE" \
  "https://whenisbins.com/v1/lookups/f47ac10b-58cc-4372-a567-0e02b2c3d479/wait"

If a response supplies Retry-After, respect it before reconnecting. Only keep one active wait request for the same lookup and IP address.

If long polling is inconvenient, fetch a single snapshot from GET /lookups/{lookup_id}. Send the response's ETag in If-None-Match on the next request; an unchanged lookup returns 304 Not Modified with no JSON body.

4. Handle every lookup status

StatusMeaningWhat the client should do
queuedThe lookup is waiting to run.Show the wait estimate and keep waiting.
runningThe council is being checked.Show progress when present and keep waiting.
partialA usable result is present, but more complete dates may still arrive.Display it honestly and keep waiting. Check every collection's dates_complete.
doneThe lookup has settled successfully.Display result; no further polling is needed.
failedThe lookup has settled without a successful result.Display detail, offer council.lookup_url when present, and stop polling.

A partial result can be provisional. When result.provisional is true, it is a clearly labelled schedule from a neighbouring property while the exact property is still being checked. It always has address_match: "postcode_representative". Do not cache it, send reminders from it, or treat it as final. Continue waiting for the exact result.

progress.steps records only the lookup methods already reached. A step can be active, waiting, or missed. A missed step did not produce an answer; it is not a completed stage in a pipeline. queue_ahead, when present, is the number of citizen lookups waiting ahead of this one.

5. Retrieve the stable schedule later

The result of a successful lookup contains property_id. Save that opaque value if your service needs to re-check the schedule later. In the URL only, drop its leading p: namespace marker; the remaining value is the opaque property_token:

curl --fail-with-body -i \
  "https://whenisbins.com/v1/schedules/4c5ee6c2f2c7c959"

This Schedule resource is deliberately address-free. It does not contain matched_address, because possession of a property ID is not authentication.

Save its ETag and use a conditional request next time:

curl --fail-with-body -i \
  -H 'If-None-Match: "ETAG_FROM_THE_PREVIOUS_RESPONSE"' \
  "https://whenisbins.com/v1/schedules/4c5ee6c2f2c7c959"
  • 200 OK contains the latest stored Schedule.
  • A 304 means stored content is unchanged, not that the council re-verified the date. 304 Not Modified has no JSON body.
  • 404 no_schedule means the service has never answered for that property. Create a lookup; do not interpret it as an empty collection calendar. The body's next_step says so, with create_lookup and website links: creating a lookup needs a POST, so a client that can only GET should use the website.

An older schedule can still be returned while the service refreshes it. Read retrieved_at to see when the council information was fetched and refresh_due_at for when the service considers it due for refresh: before that instant it is an acceptable cache, after it the read you just made has queued the refresh. An older schedule is not converted into a 404 merely because it is due for refresh.

Checking again on a schedule

A weekly reminder, a daily "what goes out tomorrow?" check and a home automation all follow the same pattern. Create one lookup, then read the stored Schedule. Do not create a new lookup on every run.

  1. Once: complete the lookup journey and save result.property_id with the user's agreement. Save the request body you sent as well, in case step 3 is ever needed.
  2. Every run: send GET /schedules/{property_token} with the last ETag in If-None-Match. A 304 means stored content is unchanged, not that the council re-verified the date. A 200 updates the upcoming schedule. A missing past date is not cancellation. This read creates no lookup and does not use the new-work allowance unless it starts a background refresh.
  3. Only when needed: repeat the lookup journey with the saved request body and a new Idempotency-Key. That is needed after 404 no_schedule, when a service the user relies on has no dates left, or when 503 persists after its Retry-After. Save the property_id from the new result.

A property_id is derived from the council and the address as the council lists it. It stays the same between lookups of the same property and does not expire, so it is safe to store. It can change if the council changes how it lists the address or is reorganised into a new authority. Handle that through step 3 rather than assuming the identifier is permanent.

The service refreshes a stored Schedule in the background when a read finds it due, so an integration that reads weekly does not need its own refresh routine. The rule: a stored answer is fresh for 28 days after retrieved_at, or 7 days when today is within a fortnight of a UK bank holiday; some councils' own projected calendars are rechecked sooner. Once it is due, a read returns the stored answer as it stands and queues the refresh; a later read returns the refreshed answer with a new ETag and retrieved_at. An answer still unrefreshed 45 days after retrieved_at (28 near a bank holiday) is refused with 503 and Retry-After rather than served. Compare retrieved_at with today before relying on an old answer near a bank holiday.

Presenting the answer to the user

Name each collection service and give its next returned date, or the returned dates within the period the user asked about. Include the council source link and preserve the response's notes and qualifications. When useful, state when the council information was retrieved; retrieved_at: null means unknown, not the time of this request.

For a question about the next collection, a useful answer has this shape: "Your next [service name] collection is [returned date]. Source: [council link]." Add the applicable caveat beside that date, such as "This next date is projected from the council's published weekday" or "These are provisional dates from a neighbouring property; your address is still being checked." These are templates, not real collection information.

  • Interpret "today", "tomorrow" and "this week" using Europe/London and the user's intended period. A collection date is a local calendar date, not a UTC collection-time timestamp.
  • Keep date_confidence, date_completeness, dates_complete, address_match, evidence_granularity and relevant notes meaningful in the answer. An exact address match does not necessarily mean a complete, property-level calendar.
  • Keep a provisional neighbouring-property answer labelled as provisional. Continue the exact lookup; never promote the provisional answer after failure or use it for reminders, feeds or a persistent schedule cache.
  • Mention subscription_required when present. Garden collection dates do not prove that the household subscribes.
  • Do not infer bin contents from colour, invent a collection time or put-out deadline, expand weekday wording into dates, or assume a bank-holiday shift.
  • The response does not currently mark a date the council has moved. A moved collection appears as a returned date on a different weekday from that service's other dates. You may tell the user that the date falls on a different day from usual. Give a reason only when notes states one, and never move or add a date yourself.
  • Do not work out weekdays by reasoning. by_date gives each returned date its weekday and the services due. To answer "what goes out tomorrow?", look for tomorrow's Europe/London date there.
  • Missing dates mean information is unavailable, not that no collection exists. On failure, explain detail and offer the council's lookup link when present.

Understanding a schedule

Address and evidence

FieldMeaning
property_idThe service's opaque identifier for the property. Do not parse it.
matched_addressThe council-rendered address. Present only inside a private Lookup result, not the stable Schedule.
address_matchHow the address was matched: exact, street, postcode, or the labelled neighbour result postcode_representative.
councilThe collecting council. council.id is its official GSS code.
evidence_granularityThe level at which the council publishes the evidence: property, street, postcode, area, or round.
source_urlThe council page used for the lookup, or its known bin-checker page.
retrieved_atWhen the information was fetched from the council. null for historical cached lookups whose original retrieval time was not retained.
refresh_due_atWhen this answer stops being fresh: retrieved_at plus 28 days, or plus 7 days within a fortnight of a UK bank holiday. Before it, no refresh is owed; after it, a read of the Schedule resource queues one and still returns the stored answer. Policy, not a promise of when the refresh runs. null when retrieved_at is.

address_match and evidence_granularity answer different questions. For example, the service may match a property exactly and then use the council's verified round calendar. That result has address_match: "exact" and evidence_granularity: "round".

Collection entries

Each entry in collections represents one council waste service. Do not merge entries just because they share a date; some councils genuinely provide many separate recycling services.

FieldMeaning
nameThe service name exactly as the council presents it.
waste_typeA standard category such as refuse, recycling, garden or food. Use /waste-types for the complete vocabulary.
datesSorted upcoming collection dates. Clients may derive the weekday for display. Services in one Schedule can carry different numbers of dates: a council may publish a calendar for refuse and recycling but only the next date for garden or food waste.
scheduleCouncil-published weekday or time wording, when relevant. Clients must not expand it into further dates.
dates_completeFalse during a partial lookup, or whenever this service has no dates (including weekday_only answers). If the lookup is done, request a fresh lookup instead of polling the old one.
containerwheelie_bin, caddy, box, bag, sack, or communal. A legacy field: wheelie_bin is also the default when no shape is known.
bin_colour and lid_colourCouncil-stated colours, or a neutral default. Legacy fields; prefer appearance_occurrences.
colour_sourcecouncil when the council stated at least one of the two colours; otherwise default.
bin_colour_source and lid_colour_sourceThe same flag for each colour separately. A lid colour inherited from the body is default. Check these before describing a body or lid colour as the council's.
appearanceOne conservative description of the container across every returned date: its kind, whether it has a lid, and separate overall, body and lid colour facts with their sources. Anything not evidenced is unknown or absent, never guessed.
appearance_occurrencesThe preferred container description, one row per returned date. Use the row whose date matches the collection you are describing and never borrow another date's row. Each row's variants are alternatives ("or"), and the items inside one variant go out together. A status of unknown means no description is held, not that there is no container.
contentsWhat goes in the containers, summarised from council-published material labels. accepted lists those labels once; source is council. If null, say the council has not published contents in the evidence we hold; never guess from colour or name. varies_by_date: true means the materials, their container assignments, conditions or evidence coverage differ across dates or variants.
subscription_requiredPresent and true when the service is a paid opt-in, or when a representative garden-waste result cannot prove this property's subscription.

contents.accepted combines labels across a collection's containers and alternatives. It is not an exhaustive list and does not mean every container takes every listed material. For which box or bag takes what, read the matching appearance_occurrences row and its variants[].items[].material, keeping the variant's condition visible. Those items remain authoritative, especially when varies_by_date is true. A false value does not establish completeness. Labels retain the council's wording and any qualifications, with HTML removed and whitespace tidied. The evidence currently has no separate exclusions list, so there is no not_accepted field.

Collections by date

by_date holds the same dates the other way round: one entry per day on which anything is collected, soonest first, with the weekday already worked out.

{
  "by_date": [
    {
      "date": "2026-09-22",
      "weekday": "Tuesday",
      "collections": [
        { "name": "Black bin", "waste_type": "refuse" },
        { "name": "Garden waste", "waste_type": "garden", "subscription_required": true }
      ]
    }
  ]
}

To answer "what goes out tomorrow?", find tomorrow's Europe/London date in by_date; no entry means nothing is due that day in the returned dates. It is a view over collections, not a second source: it adds no date, moves none and projects none, and the same date_confidence, date_completeness and dates_complete apply. weekday is only the weekday of that date. It does not say which day is usual, and a moved collection is not labelled. by_date is empty when no service has a concrete date.

Compact view

Add ?view=compact to any GET /schedules/... request for the same answer without the container descriptions. Each entry in collections keeps only name, waste_type, dates, dates_complete and, when present, schedule and subscription_required; by_date, the evidence and date-trust fields, notes and source_url are unchanged, and view: "compact" names the view. A full Schedule is mostly appearance evidence (about 9 KB against about 1 KB), so use the compact view for a reminder or a "what goes out tomorrow?" check. It carries no colour or container information at all, so nothing in it can be read as the council's description of a bin; request the full Schedule when you need to say which bin to put out. The compact body has its own ETag: send back the one you received for the same view. Any other view value is 400 invalid_request.

Date trust labels

The API deliberately separates confidence from completeness:

Field and valueMeaning
date_confidence: published_calendarThe council publishes concrete calendar dates.
date_confidence: council_projectionThe council, or a bounded service rule based on the council's published weekday, produced one next date. Holiday exceptions may be missing.
date_confidence: next_collection_onlyThe source provides only the next collection date or dates. Do not imply a longer calendar exists.
date_completeness: next_onlyThe answer contains concrete next dates only.
date_completeness: limited_horizonThe answer is valid but covers less than the full serving window.
date_completeness: full_horizonThe source was verified across the full serving window.
date_completeness: weekday_onlyThe council published weekday wording that could not safely be converted into dates. Read schedule.

Never generate additional dates from a pattern yourself. The API returns concrete dates for up to 92 days ahead and labels the narrow cases where one next date was projected from a council-published weekday.

Public schedule identifiers

When public calendar feeds are enabled and the service has verified the identifier, a Schedule can also contain:

  • uprn: the property's verified Unique Property Reference Number.
  • council_id: the council's own identifier for that property. This is not the council's GSS code; the GSS code is council.id.
  • calendar_url: the public calendar feed those identifiers key; see "Subscribable calendar links" below.

These identifiers support two additional address-free Schedule endpoints:

GET /schedules/uprn/{uprn}
GET /schedules/council/{gss}/{id}

They resolve only schedules the service already holds. A uniquely mapped stale schedule is returned immediately and may be refreshed in the bounded background queue. Unknown, invalid, unverified or ambiguous identifiers never create work. The routes return 404 no_schedule when public feeds are not enabled, the identifier is invalid or unverified, no schedule exists, or the identifier does not select exactly one property.

When a Schedule has either identifier it also has calendar_url: the property's public iCalendar feed on the website.

{
  "uprn": "100023336956",
  "calendar_url": "https://whenisbins.com/<UPRN>.ics"
}

Use the value as given; do not assemble a link yourself. It is the uprn form whenever the Schedule has a uprn, and otherwise https://whenisbins.com/{council.id}/{council_id}.ics. If the service later verifies that property's UPRN, the council form answers 301 to the UPRN form. A Schedule without calendar_url has no public calendar: never construct one from the opaque property_id, a postcode or an address. A completed Lookup's result carries the same field.

The feed is for a person to subscribe to in a calendar application, so give the user the link instead of fetching it on their behalf. It is not a /v1 route. It carries collection dates, service names and, where held, the council's description of each container: no address, postcode or personal details. By default it has one event per collection date naming every service due; add ?per=service for one event per service per date, and ?download=1 for a one-off file instead of a subscription. It sends an ETag and Cache-Control: public, max-age=3600, and honours If-None-Match.

It follows the same rules as the two endpoints above: it serves only a schedule the service already holds, never creates a lookup, and shares the ordinary data-read allowance. It answers 404, which is not an empty calendar, when the held schedule has no concrete dates (a provisional or weekday_only result). The dates are the Schedule's dates; read date_confidence and date_completeness from the API before telling a user how far ahead the calendar reaches.

Get only a calendar URL

Use the standard postcode lookup and address selection, then return only the subscription link:

  1. Call GET /addresses?postcode=… and display the returned candidates[].label.
  2. Let the user select their address. Send that candidate's id unchanged as property_id to GET /calendar-url.
curl --fail --silent --show-error --get \
  --data-urlencode "postcode=CB4 2HX" \
  "https://whenisbins.com/v1/addresses"

After the user selects an address, substitute its returned candidate ID:

curl --fail --silent --show-error --get \
  --data-urlencode "property_id=p:0123456789abcdef" \
  "https://whenisbins.com/v1/calendar-url"

No intervening POST /lookups, schedule download, UPRN or council identifier is needed. The selected address must already have a usable saved schedule. Never automatically select the first candidate. If the postcode response requires typed or council-specific input instead of offering property_id choices, use the ordinary lookup flow described above.

Callers that already know a confirmed identifier may skip the postcode step. The URL endpoint accepts exactly one of property_id (or its URL token), uprn (verified canonical digits), or gss and council_id together.

A successful response is 200, with Content-Type: text/plain and only https://whenisbins.com/100023336956.ics in the body. Save that URL and give it to the user to subscribe. The endpoint does not return a schedule or fetch the calendar file. It never starts a lookup or refresh, even on a miss.

The property must map uniquely to a public calendar and have a usable saved schedule with current dates. The URL is the same calendar_url a completed lookup or saved Schedule names for that property, and it is refused when it would not resolve uniquely. Unavailable, ambiguous, provisional, undated, expired or postcode-representative answers return 404 no_schedule. A store outage returns 503 service_unavailable. Invalid, repeated or extra query parameters return 400 invalid_request; do not send a written address. Errors use the ordinary JSON Problem format. Public feeds must be enabled. The link alone makes no promise about how far ahead its dates extend.

This endpoint has a separate 100 requests per rolling 24 hours allowance per anonymous network or per bearer token. The preceding /addresses call uses the standard address-discovery allowance, not this URL allowance. Successful and unsuccessful reads, including HEAD, count; they do not consume ordinary read allowance. A quota refusal returns 429, limit_scope: calendar_url and Retry-After. Outer traffic protections still apply. These request counters are process-local and reset on restart; the boundary minute is conservatively retained. Responses use Cache-Control: no-store for this initial small allowance. The subscription URL can be retained; fetching its .ics content continues to use the existing calendar read limits and bounded refresh behaviour.

Free allowances

ActivityAnonymous HTTPTokenless MCPFree token (HTTP or MCP)Elevated token
Calendar URL only (HTTP)100/rolling 24 hours per networkNot exposed as an MCP tool100/rolling 24 hours per token100/rolling 24 hours per token
Ordinary data reads300/minute and 10,000/day per network300/minute and 10,000/day per network300/minute and 10,000/day per token600/minute and 50,000/day per token
Address discovery requests60/minute and 1,200/hour60/minute and 1,200/hour60/minute and 1,200/hour300/minute and 5,000/hour
Lookup submissions60/minute and 1,200/hour60/minute and 1,200/hour60/minute and 1,200/hour300/minute and 5,000/hour
New lookup or refresh work1000/hour and 5000/rolling 24 hours1000/hour and 5000/rolling 24 hours1000/hour and 5000/rolling 24 hours1000/hour and 5000/rolling 24 hours
Status reads6,000/hour, separate from ordinary reads6,000/hour per network6,000/hour per token6,000/hour per token

Anonymous HTTP, tokenless MCP and free tokens have the same figures, set high so that AI agents are not refused by their network allowance. The difference is whose allowance it is: anonymous and tokenless MCP callers share theirs with everyone else reaching us from the same network, which for a cloud AI client or a proxy exit can be many users, while a token has its own. Sending a token selects the token allowance on either surface. An elevated token is an ordinary token the operator has raised for a named integration, with higher request and address-discovery figures; ask at hello@whenisbins.com if the token allowance is not enough for your regular use. Every allowance still sits under the shared capacity, provider and spend gates.

Status long-polling defaults to 4 simultaneous status waits per source IP, one wait per lookup per IP, and 50 across the web process. This is not a fixed per-token limit on active lookups. A second wait above these bounds receives 429 with Retry-After; close or finish an existing wait before opening another. MCP polls ordinary status reads and returns still_working when its own bounded wait ends. The MCP process holds at most 20 calls for these longer waits; further calls use a short wait and may return still_working so the client can check later. Queue health, queue depth and age, provider capacity and shared spend can separately refuse new work with 503 service_unavailable. Serialise or reduce parallel submissions, honour Retry-After, and continue checking an existing lookup instead of repeatedly creating one. A token does not reserve four or five worker slots.

Request windows conservatively include the boundary minute. An IPv4 address or IPv6 /64 network shares the anonymous allowance. Shared networks can request a token; tokens have their own allowance across networks. An outer network traffic limit, a process-wide external traffic ceiling and global capacity controls always apply.

A cache hit or joining an existing job does not consume new-work allowance. A new refresh does, even when triggered by a GET. Failed work still counts if it was admitted. Address discovery has separate provider budgets and concurrency limits; cached candidates remain useful when a live provider is unavailable. Documentation has no application data quota.

On 429, wait for Retry-After seconds; the response explains which allowance was reached and gives token_request_url. On 503, capacity or a dependency is unavailable: wait for Retry-After. A token does not guarantee capacity or bypass the shared spending ceiling. Do not rotate IPs, tokens or submissions to avoid limits. Bulk use needs a discussion with the operator.

Authentication and free tokens

Commercial users must contact hello@whenisbins.com before using the API, including through MCP.

Occasional queries need no token. For regular use, request a free token by emailing hello@whenisbins.com with your intended use and approximate volume. You will receive a one-time delivery link, valid for 24 hours. Open it and press Reveal my API token when ready to save the token privately. Opening the link alone does not consume it. Treat the link like a temporary password. Store the token in WHENISBINS_API_TOKEN and send Authorization: Bearer on data requests. Do not put a token in a URL or public browser JavaScript. A supplied invalid or revoked token receives 401; it is never silently treated as an anonymous request.

Errors

An error response has a stable machine-readable code and a plain-English explanation:

{
  "problem": "address_not_found",
  "detail": "The council's system doesn't list that exact address."
}

Depending on the error, it can also contain council, council_note, candidates, unavailable_candidates or postcode_representative, and a 429 can contain limit_scope and token_request_url. Display detail to the user and branch on problem in code. Do not branch on the wording of detail.

HTTP statusProblemWhat it means or what to do
400invalid_postcodeSend a complete UK postcode.
400invalid_requestCorrect the malformed value, unsupported field or invalid idempotency key described in detail.
401unauthorizedA supplied token is invalid or revoked, or the service is in token-only mode. Ask for a replacement; do not silently discard a rejected credential.
403invalid_requestThe browser origin is not allowed to create lookups. Server-to-server clients are not subject to the browser origin check.
404postcode_outside_coverageThe postcode is valid but does not resolve to a collecting council the service covers.
404lookup_not_foundThe lookup ID is unknown or its 30-day retention period has passed.
404no_scheduleNo stored Schedule exists for that identifier.
404not_foundThe /v1 route does not exist.
405method_not_allowedUse the method named by the Allow response header.
409idempotency_conflictThe idempotency key was already used for different lookup details.
413invalid_requestThe JSON body is too large. Send only the fields /addresses asked for.
415invalid_requestSend a JSON body with Content-Type: application/json.
422council_not_supportedUse council.lookup_url when present. The API cannot automate this council safely: it has no online checker the service can read, or its checker is behind a CAPTCHA, which the service never bypasses. detail says which.
422postcode_outside_coveragePOST /lookups could not resolve the postcode to a collecting council the service covers.
422ambiguous_addressAsk for the missing council-specific input or let the user select a returned candidate.
422address_not_foundCheck the first line of the address or let the user select a returned candidate.
422property_identifier_requiredAdd a house number or building name; a flat or floor description alone is insufficient.
422property_dates_unavailableThe council lists the property but currently has no collection record for it. Returned at once when the user selects or types a property named in unavailable_candidates. Re-entering the address will not help; offer council.lookup_url.
422council_site_maintenanceThe council's checker is showing its own maintenance page, so no fresh lookup is started. A schedule the service already holds is still returned. Try again later.
429rate_limitedWait for Retry-After; limit_scope and token_request_url may explain how to request more access.
500server_errorAn unexpected service error occurred. It is safe to retry with the same idempotency key where applicable.
503service_unavailableA required service is temporarily unavailable. Wait for Retry-After when supplied. This includes a postcode that could not be resolved because the postcode service did not answer: that is a 503 to retry, never postcode_outside_coverage.

A lookup can also finish with status: "failed". In that case its body uses the same problem and detail fields. Additional final lookup problems include:

  • council_site_unavailable: the council's checker could not be read, or the service had to stop trying. A lookup always ends: one that cannot be answered is failed with this problem instead of waiting indefinitely.
  • council_site_maintenance: the council is showing a temporary maintenance page. It is also returned immediately as the 422 above when a recent observation of that maintenance page is still current.
  • property_dates_unavailable: the council lists the property but currently publishes no collection dates for it. Re-entering the same address will not fix that gap. It is also returned immediately as the 422 above for a property already named in unavailable_candidates.

A lookup that failed with address_not_found can carry candidates: the council's own list for that postcode. Let the user pick from it and submit a new lookup with the selected property_id.

Caching, privacy and browser access

  • GET /addresses, /schedules, /councils and /waste-types are cacheable resources and allow cross-origin browser reads. An anonymous response is Cache-Control: public; a response to a request that supplied a token is private, so it is never stored in a shared cache.
  • Lookup creation and lookup-status responses can contain an exact address. They use Cache-Control: private, no-store and browser access is restricted to approved WhenIsBins origins. Server-to-server callers can use them without an Origin header.
  • Never place a full address in a URL. Send property details only in the JSON body of POST /lookups.
  • A postcode and address together are personal data. Keep lookup IDs, property IDs and response bodies out of long-lived logs unless you have a documented need and retention policy.
  • Lookup resources are retained for 30 days. Stable address-free Schedule resources are separate from that lookup history.

Complete JavaScript example

This example uses Node 22+ and no npm packages. It separates discovery from the user's selection and saves the request and retry key before submitting. It uses the published JavaScript client.

Work in a new private local directory for one lookup. Download the client as client.mjs and inspect it before running it:

curl --fail-with-body --output client.mjs https://whenisbins.com/v1/client.mjs

Save the following as lookup.mjs in that directory. Tokens, if needed, come from WHENISBINS_API_TOKEN in the environment. JSON input goes through stdin; address details never go in command arguments. The state files and output are private: keep them out of source control, shared folders and long-lived logs, and delete them when the lookup and any requested continuation are finished. Run one command at a time in this directory.

import { readFile, writeFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { createWhenIsBinsClient } from "./client.mjs";

const api = createWhenIsBinsClient({ token: process.env.WHENISBINS_API_TOKEN });
const command = process.argv[2];
const readJson = async path => JSON.parse(await readFile(path, "utf8"));
async function readInput() {
  process.stdin.setEncoding("utf8");
  let text = "";
  for await (const chunk of process.stdin) {
    text += chunk;
    if (Buffer.byteLength(text) > 16384) throw new Error("Input exceeds 16 KiB.");
  }
  return JSON.parse(text);
}
const saveNew = (path, value) => writeFile(path, JSON.stringify(value), {
  mode: 0o600, flag: "wx", // Refuse to replace another lookup's saved state.
});
const pending = lookup => ["queued", "running", "partial"].includes(lookup.status);
const print = value => console.log(JSON.stringify(value, null, 2));

try {
  if (command === "addresses") {
    const { postcode, q } = await readInput();
    print(await api.addresses(postcode, q)); // No selection or submission here.
  } else if (command === "prepare") {
    const input = await readInput(); // Only fields requested by /addresses.
    await saveNew("request.json", { input, idempotencyKey: randomUUID() });
    print({ prepared: true }); // The exact body and key now exist before POST.
  } else if (command === "submit") {
    // A saved receipt means the lookup already exists: use wait instead.
    let receipt;
    try { receipt = await readJson("submission.json"); }
    catch (error) { if (error.code !== "ENOENT") throw error; }
    if (receipt) {
      print(receipt);
    } else {
      const { input, idempotencyKey } = await readJson("request.json");
      const response = await api.submit(input, { idempotencyKey });
      receipt = { lookup: response.data,
        retryAt: Date.now() + response.retryAfterSeconds * 1000 };
      await saveNew("submission.json", receipt);
      print(receipt);
    }
  } else if (command === "wait") {
    const receipt = await readJson("submission.json");
    let previous = receipt.lookup;
    let retryAt = receipt.retryAt;
    try {
      const saved = await readJson("answer.json");
      previous = saved.lookup;
      retryAt = saved.retryAt;
    }
    catch (error) { if (error.code !== "ENOENT") throw error; }
    if (!pending(previous)) {
      print({ lookup: previous, timedOut: false });
    } else {
      const answer = await api.wait(previous.id, {
        maxWaitMs: 120000,
        retryAfterSeconds: Math.max(0, Math.ceil((retryAt - Date.now()) / 1000)),
      });
      // A timeout placeholder must not erase an earlier qualified partial answer.
      if (answer.timedOut && pending(answer.lookup) && !answer.lookup.result &&
          previous.status === "partial" && previous.result) answer.lookup = previous;
      answer.retryAt = Date.now() + (answer.retryAfterSeconds ?? 0) * 1000;
      await writeFile("answer.json", JSON.stringify(answer), { mode: 0o600 });
      print(answer); // Includes all evidence fields; a failed lookup stays failed.
    }
  } else {
    throw new Error("Use addresses, prepare, submit or wait.");
  }
} catch (error) {
  print({ error: "command_failed", status: error.status ?? null,
    problem: error.problem ?? null, retryAfterSeconds: error.retryAfterSeconds ?? null,
    detail: "No automatic retry was made. Check input and saved state. An uncertain submission must reuse request.json unchanged." });
  process.exitCode = 1;
}

First save the user's postcode in postcode.json, for example {"postcode":"USER_POSTCODE"}, replacing the placeholder. Run:

node lookup.mjs addresses < postcode.json

Read the returned required_input. Show candidate labels to the user when a choice is needed. Save only the confirmed request fields in selected.json: for a property list, use the postcode and the exact selected candidates[].id as property_id; for other journeys follow the required input table. Do not copy an illustrative ID from this guide or submit if the selection is ambiguous. For road/area searches, add q to postcode.json and repeat discovery until the user can choose a returned option. Discovery alone never submits work.

After the user has supplied or confirmed the necessary details:

node lookup.mjs prepare < selected.json
node lookup.mjs submit

Inspect lookup.status. If it is queued, running or partial, run:

node lookup.mjs wait

A timeout means this command stopped waiting, not that the lookup failed. Retain the qualified partial answer if present and offer a later check of the same ID. Run wait again only for a user-requested continuation. Terminal done and failed states cause no further polling. The example prints the full response so answer qualifications remain available; it creates no reminders or subscriptions.

The client's timed-out wait includes retryAfterSeconds when a server delay remains. The example saves its expiry as retryAt, so a later continuation respects the remaining delay instead of either polling early or restarting the whole delay each time.

If submission loses its response, keep request.json unchanged and explicitly rerun submit after addressing the error and any Retry-After. It reuses the same key and body. If a receipt was already saved, it returns that receipt without another POST. Do not run prepare again to recover an uncertain request. A corrected or different lookup needs a separate private directory and a new key.

These commands call the production API when you run them. There is no public sandbox. Repository tests execute this exact JavaScript example against offline fixtures, including a lost submission response, ambiguous address choices, partial results, failures and rate limits; those tests create no live jobs.

MCP server

The four tools carry the same council.missed_bin_url and missed_bin_checked_at fields as HTTP (inside result.council for lookup results); see missed-bin reporting links.

If your AI client supports remote MCP servers, add this address instead of calling the HTTP API yourself (the one-page MCP quickstart is the short form of this section):

https://whenisbins.com/mcp

It uses the Streamable HTTP transport. Personal use needs no account, key or sign-in within the free allowances. Commercial users must contact hello@whenisbins.com before using the API, including through MCP. For claude.ai, follow Claude's custom connector setup instructions. For Claude Code, its documented HTTP configuration is claude mcp add --transport http whenisbins https://whenisbins.com/mcp. A client that can send request headers may add Authorization: Bearer <token> to use a free token's allowances. There is no OAuth sign-in.

The service owner completed a lookup through a claude.ai custom connector on 20 September 2026; the client version was not recorded. Claude Code and other MCP clients have not been verified with WhenIsBins. Automated tests use MCP TypeScript SDK v2 against a fake API, over Streamable HTTP at protocol 2026-07-28 and with the 2025-11-25 handshake. They do not establish compatibility with every client. Tell us what you find at hello@whenisbins.com.

ToolPurpose
find_addressesA postcode's council, the input the council needs, and candidate addresses.
get_collectionsCollection dates for the one property the user chose. Submits once and waits up to about 45 seconds.
check_lookupContinue a lookup that was still_working or partial, by its lookup_id.
get_scheduleRead a stored Schedule again by property_id. Starts no new lookup.

get_collections and check_lookup return status done, partial, still_working or failed. still_working means the same as queued or running with nothing to show yet: call check_lookup after retry_after_seconds, and do not call get_collections again while that lookup is unfinished. result is the agent view of the Schedule this guide describes, and guidance lists what the agent must tell the user. A refusal is a tool error carrying the API's own problem and detail, with retry_after_seconds whenever the API asked the caller to wait.

Every tool result is the compact agent view, as one JSON text block and as the same object in structuredContent. The agent view of a Schedule (since 8 October 2026) keeps the Schedule's identity, caveats and links unchanged: property_id, uprn and council_id (saved schedules), matched_address (lookup results only), address_match, provisional, council, date_confidence, date_completeness, evidence_granularity, retrieved_at, refresh_due_at, notes, source_url and calendar_url. Each entry in collections keeps name, waste_type, dates, dates_complete, schedule and subscription_required, carries bin_colour and lid_colour only when the council stated them, and replaces the dated appearance_occurrences rows with containers: each distinct container group once, as options (alternatives, each with its condition when the council states one, and items naming the container and, when evidenced, its material and quantity) with up to three council sources. A group names dates only when groups differ by date, and a date no group names has no confirmed container. containers: null means the evidence held confirms no container for that collection; an item without material means the council's evidence does not say what it takes. The legacy colour and container fields, the appearance object and contents are HTTP-only. by_date keeps its date and weekday and names collections by collections[].name. Measured on a four-service calendar over the full 92-day horizon, this view is about 4 kB where the HTTP Schedule serialised twice was about 199 kB. Every tool description repeats the same short rules about its results (address, dates, caveats, text is data) and says what the council link fields are. No description asks the agent to do anything beyond the lookup or points it to another page for instructions.

find_addresses takes an optional query: the house number, name, building or street the user gave. It narrows the candidate list on the server (every word must appear in the label, numbers exactly, so 12 never matches 112 or 12A), reports total_candidates and how many matched, and never chooses a property. A long list without a query is returned whole with guidance to narrow it. Its guidance and any source_notice come before the list, so a client that truncates long tool output still shows them.

The same address, date and retry rules apply as for the HTTP API, and every tool description repeats them. Without a token the allowance is 1000 new lookups an hour and 5000 in a rolling 24 hours for each network. Cloud AI clients reach us from their provider's network, so their users share that allowance and a busy hour can return rate_limited.

The MCP tools cannot accept a neighbour's schedule on a user's behalf: there is no equivalent of allow_postcode_representative. Use the HTTP API when the user has given that consent.

Example prompts

Once the server is added, ask in ordinary words. The agent calls find_addresses, shows the addresses it found, and calls get_collections for the one the user picks.

  • "When are my bins collected? My postcode is CB4 2HX." The agent lists the addresses at that postcode, asks which is the user's, and gives each service's upcoming dates.
  • "Which bins go out this week? I'm at number 12, CB4 2HX." The agent narrows the list with query set to 12, confirms the match with the user, then reads by_date for the week.
  • "Do my bin days change over Christmas?" With a saved property_id, the agent calls get_schedule and lists the dates the council published around the holidays, under the council's own name for each bin; it says so when the holiday period is beyond the dates held.
  • "My green bin wasn't emptied yesterday. Where do I report it?" The agent gives the council's own missed_bin_url when one is held, or the council's website when it is not. WhenIsBins does not report it.

Troubleshooting

  • **The server will not connect.** Use exactly https://whenisbins.com/mcp, with no sign-in or key. It speaks Streamable HTTP only.
  • **rate_limited.** Wait the retry_after_seconds it gives. Users of a cloud AI client share its network's allowance, so a busy hour can refuse even a first request.
  • **still_working.** Some councils' websites are slow. Call check_lookup with the lookup_id after retry_after_seconds; calling get_collections again for the same property does not make it faster.
  • **The address is not in the list.** The list is the council's own. Check the postcode, then use the council's checker at council.lookup_url.
  • **council_site_unavailable or council_site_maintenance.** The council's own website is not answering. Try again later or use the council's page.
  • **council_not_supported or postcode_outside_coverage.** WhenIsBins cannot read that council's checker, or the postcode does not resolve to a council it covers. Council coverage lists every council.
  • **Anything else.** Email hello@whenisbins.com with the time, the tool and the problem code. Do not send your address unless we ask.

OpenClaw and other agent clients

For an agent with permitted HTTP tools, start with the agent quickstart and Markdown guide. No package installation is required to call the HTTP API. A fresh lookup needs POST support; OpenClaw's GET-only web_fetch tool cannot create one.

The OpenClaw skill is a standalone instruction file, not a complete helper package. Review its contents before adding it to your agent's skills. With only that file, use permitted HTTP tools. The JavaScript example above is independently usable without a ClawHub account or unpublished package path.

If you already have the complete reviewed WhenIsBins package, it also contains the client, guide and Node 22+ helper. Install that local package with openclaw skills install /absolute/path/to/whenisbins, substituting its actual location, then start a new OpenClaw conversation. From the package directory, run node scripts/whenisbins.mjs check for a fixed-reference request or node scripts/whenisbins.mjs --help for command inputs. The helper journey is addresses, user selection, key, submit, then wait while pending. Save the key and input before submitting, and pass the submission's retryAfterSeconds to wait. Preserve a partial result if waiting times out.

For other agent clients, use the MCP server if the client supports one, or the HTTP quickstart or JavaScript client with a permitted tool. The same address, date, consent and retry rules apply whichever client makes the requests.

Endpoint summary

Method and pathPurpose
GET /addressesResolve a postcode to its council and required address input.
POST /lookupsStart or immediately satisfy a property lookup.
GET /lookups/{lookup_id}Fetch one lookup snapshot.
GET /lookups/{lookup_id}/waitWait for a lookup to change.
GET /calendar-urlReturn only a known property's subscription URL, without starting work.
GET /schedules/{property_token}Retrieve the stable address-free Schedule.
GET /schedules/uprn/{uprn}Retrieve an existing Schedule by verified UPRN.
GET /schedules/council/{gss}/{id}Retrieve an existing Schedule by a council property identifier.
GET /councilsList collecting councils and coverage status.
GET /waste-typesList the fixed waste-type vocabulary.

Documentation is available from the API itself:

  • GET /openapi.yaml returns the machine-readable OpenAPI 3.0 contract.
  • GET /docs renders that contract as browsable reference documentation.
  • GET /guide returns this guide as a web page.

There are no bulk endpoints, webhooks or calendar files under /v1. The website's public calendar feed is named by a Schedule's calendar_url; see "Subscribable calendar links". Email reminders are a website feature with no API.

Further reference

For integration questions or a raised allowance, email hello@whenisbins.com with your intended use and approximate volume.


For exact request and response schemas, use the browsable API reference, or fetch the OpenAPI contract directly.