Overview
CouncilBins is the resident-facing progressive web app. The integration API is how your council (or a systems integrator) feeds operational data into that experience — and optionally receives issue reports back into your CRM or service desk.
Every council stores collection data differently. This guide describes the recommended capability surface WM Studios implements on client projects. Endpoints can be mapped to your property system, waste contractor feeds, GIS exports, or middleware.
Who this is for
- Council ICT / digital — evaluating integration effort and security posture
- Waste & cleansing operations — confirming what data must stay current
- Vendors & integrators — property, CRM, or waste platforms connecting outbound/inbound
The interactive demo at /Demo/ shows the resident experience. This page describes the recommended production integration, not the demo’s simplified endpoints.
Design principles
- Your identifiers win — use your property/assessment IDs as external keys; we store a mapping.
- Push or pull — you can POST changes to us, or we can pull from a feed you expose (by agreement).
- Idempotent writes — safe retries with the same payload do not create duplicates.
- Partial updates — update a zone, a holiday table, or a suburb batch without full reloads.
- Auditability — imports and API writes are logged with actor, timestamp, and summary counts.
Authentication
Production APIs are not public. Typical setup:
| Method | Use when |
|---|---|
Authorization: Bearer <api_key> |
Server-to-server from council middleware or iPaaS |
| IP allowlisting | Fixed egress from council data centre / cloud NAT |
| Optional mutual TLS | Higher assurance environments (by request) |
Keys are issued per environment (sandbox / production) and can be rotated without downtime. Resident devices never hold integration keys — they only talk to the public app APIs.
curl -sS \
-H "Authorization: Bearer cb_live_***" \
-H "Accept: application/json" \
https://{tenant}.councilbins.example/api/v1/health
Base URL & versioning
https://{tenant-host}/api/v1/
- Tenant host — dedicated hostname or path for your council instance
- Version — breaking changes ship under a new version prefix;
v1remains stable for the contract term - Format — JSON request/response bodies; UTF-8; dates as
YYYY-MM-DD; times as ISO-8601 where needed - Timezone — collection logic uses the council timezone configured at go-live (e.g.
Australia/Sydney)
Resource map
| Area | Capability | Direction |
|---|---|---|
| Addresses / properties | Upsert, search, deactivate | Council → CouncilBins |
| Zones & rules | Collection day, bin cadence, FOGO flags | Council → CouncilBins |
| Exceptions | Public holidays & alternate dates | Council → CouncilBins |
| Recycling guide | What-goes-where catalogue | Council → CouncilBins |
| News / tips | In-app content | Council → CouncilBins |
| Reports | Missed bins, dumping, damaged bins | CouncilBins → Council |
| Bulk jobs | File or JSON batch import status | Both |
Addresses
An address is what a resident searches for. It must resolve to a collection zone (or explicit day + rules) so the app can build a personalised schedule.
Upsert address
PUT /api/v1/addresses/{external_id}
{
"external_id": "PROP-1002847",
"address_line_1": "12 Riverbank Drive",
"suburb": "Riverside",
"postcode": "2150",
"state": "NSW",
"zone_code": "ZONE-A",
"collection_day": "Monday",
"active": true,
"meta": {
"assessment_number": "A-884201",
"latitude": -33.8688,
"longitude": 151.2093
}
}
Batch upsert
POST /api/v1/addresses:batchUpsert
{
"items": [ { "external_id": "PROP-1", "address_line_1": "…", "zone_code": "ZONE-A", "…" : "…" } ],
"on_missing_zone": "reject"
}
Recommended batch size: up to 1,000 items per request (or use bulk upload for full estate refreshes).
Deactivate
POST /api/v1/addresses/{external_id}/deactivate — hides from resident search without destroying history.
Schedules & zones
Zones encode the rules residents experience: which weekday, which bins alternate, whether FOGO is weekly, etc.
Addresses reference a zone_code so zone changes cascade without rewriting every property.
Upsert zone
PUT /api/v1/zones/{zone_code}
{
"zone_code": "ZONE-A",
"name": "Riverside north",
"collection_day": "Monday",
"bins": {
"garbage": { "frequency": "weekly" },
"recycling": { "frequency": "fortnightly", "parity": "even_iso_week" },
"green": { "frequency": "fortnightly", "parity": "odd_iso_week" },
"fogo": { "frequency": "weekly" }
},
"notes": "Optional operations note"
}
Preview schedule (optional)
GET /api/v1/addresses/{external_id}/schedule?weeks=8
Returns the same shape the resident app uses — useful for UAT and for operations to verify a property after a zone change.
{
"address": { "external_id": "PROP-1002847", "display": "12 Riverbank Drive, Riverside 2150" },
"schedule": [
{
"iso_date": "2026-07-20",
"bins": [
{ "type": "garbage", "label": "General Waste" },
{ "type": "recycling", "label": "Recycling" },
{ "type": "fogo", "label": "FOGO" }
],
"note": null
}
]
}
Collection exceptions
Public holidays and one-off shifts. When a normal collection day hits an exception date, the app shows the alternate date or skip reason.
PUT /api/v1/exceptions/{exception_date}
{
"exception_date": "2026-12-25",
"reason": "Christmas Day — delayed collection",
"alternate_date": "2026-12-27",
"applies_to": "all_zones"
}
applies_to may be all_zones or an array of zone codes.
Bulk replace for a year is supported via POST /api/v1/exceptions:replaceYear with a full list.
Recycling guide
Optional if you maintain a what-goes-where catalogue. Items power search and filters in the app.
POST /api/v1/recycling/items:batchUpsert
{
"items": [
{
"external_id": "REC-PLASTIC-PET",
"name": "Plastic bottles (PET/HDPE)",
"category": "Plastics",
"bin_type": "recycling",
"description": "Empty, rinse, lids loosely on.",
"keywords": ["plastic", "bottle", "pet"]
}
]
}
Allowed bin_type values (configurable per council): recycling, garbage, green, fogo, special.
Reports (outbound to council)
When a resident submits a missed bin, damaged bin, illegal dumping, or other issue, CouncilBins stores a reference and can deliver the payload to you.
| Mode | How it works |
|---|---|
| Webhook | HTTPS POST to your endpoint on submit (recommended) |
| Pull API | GET /api/v1/reports?since=… for polling integrations |
| Email / shared mailbox | Available for pilots; not preferred long-term |
{
"event": "report.created",
"id": "rpt_01J…",
"reference": "CB-A628-9166",
"type": "missed_bin",
"details": "Red bin not emptied; left out from 6am.",
"address": {
"external_id": "PROP-1002847",
"display": "12 Riverbank Drive, Riverside 2150"
},
"contact": {
"name": "Alex Resident",
"phone": "04xx xxx xxx",
"email": null
},
"created_at": "2026-07-18T21:15:00+10:00"
}
Bulk upload API
For estate-wide refreshes or when a nightly file is simpler than fine-grained REST calls. Same validation rules as the interactive API; results are tracked as a job.
POST /api/v1/imports (multipart)
- Formats — CSV or JSON (schema provided at onboarding)
- Types —
addresses,zones,exceptions,recycling - Mode —
merge(default) orreplace_scope(e.g. replace all addresses in a suburb)
curl -X POST https://{tenant-host}/api/v1/imports \
-H "Authorization: Bearer cb_live_***" \
-F "type=addresses" \
-F "mode=merge" \
-F "file=@addresses_2026-07-18.csv"
GET /api/v1/imports/{job_id}
{
"id": "imp_01J…",
"type": "addresses",
"status": "completed",
"received": 42810,
"upserted": 42792,
"rejected": 18,
"error_report_url": "https://…/imports/imp_01J…/errors.csv"
}
Webhooks
- Signed with an HMAC secret (
X-CouncilBins-Signature) - At-least-once delivery with exponential retry (e.g. 1m / 5m / 30m / 2h)
- Your endpoint should respond
2xxquickly and process asynchronously if needed - Idempotency key included so duplicate deliveries can be ignored
Configurable events include report.created, import.completed, and import.failed.
Errors, pagination & limits
Error shape
{
"status": "error",
"code": "validation_failed",
"message": "zone_code ZONE-Z does not exist",
"details": [
{ "field": "zone_code", "issue": "unknown_zone" }
],
"request_id": "req_01J…"
}
| HTTP | Meaning |
|---|---|
400 / 422 | Validation or business rule failure |
401 / 403 | Missing or invalid credentials |
404 | Unknown resource |
409 | Conflict (e.g. concurrent replace) |
429 | Rate limited — back off using Retry-After |
5xx | Transient platform error — safe to retry idempotent calls |
List endpoints support cursor pagination (?cursor= / next_cursor).
Default rate guidance: 120 requests/minute per key for interactive APIs;
bulk imports are separate and queued.
Integration checklist
- Confirm source of truth for addresses (property system, waste contractor, GIS).
- Define zone model: day + bin cadence (+ FOGO if applicable).
- Agree external ID strategy (stable property key).
- Map public holiday process for the next 12–24 months.
- Choose report delivery: webhook URL or pull API (+ CRM field mapping).
- Sandbox credentials, sample payloads, and UAT address set.
- Go-live: cutover plan, monitoring contacts, key rotation owner.
Get a tailored contract
Endpoint names and fields above are the recommended baseline. On engagement we publish your tenant’s OpenAPI (Swagger) document, sandbox keys, and sample files mapped to your systems.
Phone 0447 599 119 · WM Studios · or use the form on the product site