API Documentation
TrashAlert exposes two API surfaces: a free, unauthenticated resident lookup endpoint that backs the site's own address search, and a preview business API (/api/v1) for account-bound, pilot-approved integrations.
Resident API — Free, No API Key
https://trashalert.ioRate limit: 30 requests per minute per IP. This limit is enforced process-locally (an in-memory counter per running server instance) — it is not a durable, shared, or cross-instance quota, and it resets on every cold start or redeploy.
This endpoint remains free for residents looking up their own schedule. No account, API key, or billing is required.
/api/scheduleLook up trash pickup schedule by address or ZIP code. Free and unauthenticated — this is the same endpoint the TrashAlert site itself uses.
Parameters
address(string)* — Street address (e.g., "4567 Park Blvd"). Not required when using zip.city(string) — City name (default: "san diego")zip(string) — ZIP code — alternative to an address lookupResponse
{
"found": true,
"match": "exact" | "fuzzy" | "city_api" | "db_zone" | "hauler_api" | "zip_fallback",
"matchedAddress": "4567 park blvd, san diego, ca 92103",
"schedule": {
"collection_day": "wednesday",
"recycling_week": "A",
"verified": true,
"verification_count": 3,
"neighborhood": "university-heights"
}
}Look up by address (curl)
curl "https://trashalert.io/api/schedule?address=4567+Park+Blvd"Business API v1 — Preview, Approved Pilot Accounts
Auth: Send
Authorization: Bearer ta_test_… or ta_live_… on the schedules, coverage, and usage endpoints below — except GET /api/v1/openapi, which is public. The credential-management endpoints (/api/v1/credentials) are different: they require an authenticated portfolio-account session (the same one used to sign in to the dashboard), not an API key — a leaked API key can never use these endpoints to mint or revoke other credentials. Requests use a monthly, account-level request-unit quota (not the resident endpoint's per-IP rate limit); GET /api/v1/usage reports the current quota and does not itself consume a unit.The full machine-readable contract is published at /api/v1/openapi (OpenAPI 3.1).
/api/v1/schedules/lookupscope: schedule:readResolve one address against TrashAlert's coverage data. Consumes one request unit.
Request Body
{
"external_id": "unit-1",
"address": {
"line1": "4567 Park Blvd",
"city": "San Diego",
"state": "CA",
"postal_code": "92103"
}
}- Optional Idempotency-Key header (1–200 characters).
- Response status field is one of resolved, unsupported, not_found, or ambiguous — never a fabricated schedule.
Response
{
"request_id": "uuid",
"replay": false,
"data": {
"external_id": "unit-1",
"status": "resolved",
"normalized_address": { "line1": "...", "city": "...", "state": "CA", "postal_code": "92103" },
"coverage": { "status": "live", "city_slug": "san-diego", "source": "...", "detail": null },
"schedule": { "services": [ { "service": "trash", "days": ["wednesday"], "cadence": "weekly", "cycle": null } ], "verified": true, "verification_count": 3, "source": "schedule_reports", "fetched_at": null },
"candidates": []
}
}/api/v1/schedules/batchscope: schedule:readResolve up to 25 addresses in one call (the account's batch limit may be lower). Each address consumes one request unit.
Request Body
{
"requests": [
{ "external_id": "unit-1", "address": { "line1": "4567 Park Blvd", "city": "San Diego", "state": "CA", "postal_code": "92103" } }
]
}- Idempotency-Key header is required (1–200 characters) — the same key with a different body returns 409 idempotency_conflict.
Response
{
"request_id": "uuid",
"replay": false,
"data": {
"results": [ { "external_id": "unit-1", "status": "resolved", "normalized_address": { ... }, "coverage": { ... }, "schedule": { ... }, "candidates": [] } ],
"result_counts": { "resolved": 1, "unsupported": 0, "not_found": 0, "ambiguous": 0, "invalid": 0, "error": 0 }
}
}/api/v1/coveragescope: coverage:readList explicit coverage status per city. A live status does not guarantee every address in that city will resolve.
Query Parameters
status(string) — Filter: live, limited, coming_soon, or pilotstate(string) — Two-letter US state abbreviationResponse
{
"request_id": "uuid",
"data": {
"records": [ { "city_slug": "san-diego", "city": "San Diego", "state": "CA", "status": "live", "source": "...", "detail": null } ],
"count": 1,
"filters": { "status": null, "state": null },
"qualifier": "A live city path does not guarantee that every address in the city will resolve."
}
}/api/v1/usagescope: usage:readRead the account's current monthly quota and result counts. Does not consume a request unit, so it stays available even after the quota is exhausted.
Response
{
"request_id": "uuid",
"data": {
"period_start": "2026-09-01",
"period_end": "2026-10-01",
"quota_limit": 5000,
"units_used": 12,
"units_remaining": 4988,
"batch_limit": 25,
"request_count": 10,
"batch_item_count": 12,
"result_counts": { "resolved": 9, "unsupported": 1, "not_found": 0, "ambiguous": 0, "invalid": 0, "error": 0, "rate_limited": 0 },
"usage_endpoint_metered": false
}
}/api/v1/credentialsCreates an account-scoped API credential. Requires an authenticated portfolio-account session (dashboard login) — API keys cannot create other credentials. The plaintext secret is returned only in this response.
Request Body
{
"name": "Pilot integration",
"environment": "test",
"scopes": ["schedule:read", "usage:read"]
}- Auth: portfolio-account session, not an API key — this endpoint manages keys, so a leaked key cannot use it to mint more.
- environment defaults to test; scopes accepts 1 to 3 of schedule:read, coverage:read, usage:read and defaults to all three when omitted.
Response
{
"request_id": "uuid",
"data": {
"credential": { "id": "uuid", "account_id": "uuid", "name": "Pilot integration", "environment": "test", "key_prefix": "ta_test_...", "scopes": ["schedule:read", "usage:read"], "status": "active", "created_at": "...", "revoked_at": null, "expires_at": null },
"secret": "ta_test_... (shown once — save it now)"
}
}/api/v1/credentialsLists the account's API credential metadata. Requires an authenticated portfolio-account session. Plaintext secrets and secret hashes are never included.
Query Parameters
environment(string) — Filter: test or live- Auth: portfolio-account session, not an API key.
Response
{
"request_id": "uuid",
"data": {
"credentials": [ { "id": "uuid", "account_id": "uuid", "name": "Pilot integration", "environment": "test", "key_prefix": "ta_test_...", "scopes": ["schedule:read"], "status": "active", "created_at": "...", "revoked_at": null, "expires_at": null } ]
}
}/api/v1/credentials/{credentialId}Revokes an account-owned API credential. Requires an authenticated portfolio-account session. Repeating a successful revoke is idempotent — revoking an already-revoked credential still returns 200.
- Auth: portfolio-account session, not an API key.
Response
{
"request_id": "uuid",
"data": { "revoked": true, "already_revoked": false, "credential_id": "uuid" }
}/api/v1/openapiReturns the machine-readable OpenAPI 3.1 document for this preview API. This endpoint does not require an API key or a portfolio session.
Response
Returns the full OpenAPI 3.1 JSON document (see link below).Questions? hud@trashalert.io