Integrations API
Can Cleaning Software
Overview
Can Cleaning Software provides a secure, tenant-scoped, read-only HTTP API for Google Sheets, Zapier, and other third-party tools.
This page documents the production Integrations API v1. All endpoints below are GET-only and never mutate CRM data. Write APIs, REST-hook webhooks, and administrative key-management HTTP endpoints are not part of this public surface.
- Authentication is by tenant API key only (not staff JWT sessions)
- Tenant identity always comes from the authenticated key — caller-supplied tenant ids are ignored for authorization
- Rate limit: 120 requests per minute per API key
- Card numbers, payment tokens, private notes, and other secrets are never returned
Authentication
Create an API key
- Sign in to Can Cleaning Software as an administrator
- Open Settings → Integrations → API Access
- Enable external API access if it is disabled
- Create a key and select only the scopes your integration needs
- Copy the secret once when it is shown (it starts with ccs_ak_…). Store it in your integration’s secure credential store — never in spreadsheets, chat, or source control
Send the key on every request
Use either header:
X-Api-Key: ccs_ak_…or:
Authorization: Bearer ccs_ak_…Staff JWT sessions cannot call Integrations API endpoints. Integration API keys cannot call JWT-only admin endpoints.
Verify a key
GET /api/v1/integrations/me HTTP/1.1
Host: api.cancleaningsoftware.com
X-Api-Key: ccs_ak_…Permission scopes
Keys are limited to the scopes granted at creation time. Missing a required scope returns HTTP 403.
| Scope | Access |
|---|---|
| customers:read | /customers, /customers/{id}, /customers/lookup, /customers/changes, /customers/changes/recent |
| services:read | /services, /services/changes |
| jobs:read | /jobs, /jobs/changes |
| routes:read | /routes, /routes/changes |
GET /me succeeds with any valid key and returns the key’s tenant, name, scopes, and optional expiry.
Rate limits
Integrations API traffic is limited to 120 requests per minute per API key (policy name rl-integration-api). When the limit is exceeded the API returns HTTP 429 with a Retry-After header.
- Partitioning is by authenticated API key id when present
- Failed or pre-auth requests are partitioned separately so one caller cannot exhaust another key’s budget
- Design connectors to poll change feeds rather than hammering list endpoints
Endpoints
Base path: https://api.cancleaningsoftware.com/api/v1/integrations
| Method | Path | Required scope |
|---|---|---|
| GET | /me | any valid key |
| GET | /customers | customers:read |
| GET | /customers/lookup | customers:read |
| GET | /customers/{id} | customers:read |
| GET | /customers/changes | customers:read |
| GET | /customers/changes/recent | customers:read |
| GET | /services | services:read |
| GET | /services/changes | services:read |
| GET | /jobs | jobs:read |
| GET | /jobs/changes | jobs:read |
| GET | /routes | routes:read |
| GET | /routes/changes | routes:read |
Pagination and list filters
List endpoints accept page (1-based) and pageSize (default 50, maximum 100). Prefer keyset pagination with afterId for snapshots — offset page numbers are convenience-only and are not sync-safe under concurrent inserts or deletes.
Keyset snapshot
GET /api/v1/integrations/customers?pageSize=100
GET /api/v1/integrations/customers?pageSize=100&afterId={nextAfterId}Responses include items, page, pageSize, totalCount, hasMore, and nextAfterId (null when finished). Optional filters vary by resource (for example customer status, routeId, updatedSinceUtc; service customerId / activeOnly; job date range / status; route activeOnly).
Customer list and detail responses include scheduling and billing-health fields such as storedNextServiceDate, actualNextServiceDate, paymentStatus, and billingAlert. Ledger and payment-health fields are null when an authoritative calculation fails (they are never silently inventing a “current” balance). Card data and private notes are never exposed.
Durable change feeds
Each resource type (customer, service, job, route) has a durable, ordered change stream. Poll /{resource}/changes with an opaque checkpoint cursor for lossless catch-up.
GET /api/v1/integrations/customers/changes?limit=100
GET /api/v1/integrations/customers/changes?cursor={checkpoint}&limit=100Every response includes checkpoint (always set, including empty pages), hasMore, streamHead, and items. Default limit is 100 (maximum 200). Persist checkpoint only after you successfully process the page. On failure, retry the same cursor (at-least-once delivery; upsert by resourceId).
| changeKind | Recommended action |
|---|---|
| created | Insert or upsert by resourceId |
| upserted | Upsert by resourceId |
| deactivated | Upsert and mark inactive / archived |
| deleted | Remove the row (data is null) |
Each item’s changeKind is the historical event recorded at commit time. data plus currentStatus / currentIsActive / resourceExists / polledAtUtc describe the live resource at poll time and may differ after later transitions (for example deactivated, then reactivated).
Recent changes (Zapier-style polling)
For stateless pollers that cannot share a durable cursor, use the newest-first recent feed:
GET /api/v1/integrations/customers/changes/recent?limit=100&kinds=created
GET /api/v1/integrations/customers/changes/recent?limit=100&kinds=upserted
GET /api/v1/integrations/customers/changes/recent?limit=100&kinds=deactivatedOptional kinds is a comma-separated list of created, upserted, deactivated, deleted. Deduplicate by changeId. Default limit is 100 (maximum 200). When truncated is true, more matching events exist beyond the window and may be missed between polls under high volume — use the durable /changes checkpoint feed (or a future webhook) when lossless sync is required.
Customer lookup
Bounded exact-match lookup for Find Customer style searches. Provide at least one of id, email, or phone. Results are capped (default 10, maximum 25). This is not unrestricted free-text search.
GET /api/v1/integrations/customers/lookup?email=ada@example.test
GET /api/v1/integrations/customers/lookup?phone=208-555-0100
GET /api/v1/integrations/customers/lookup?id={guid}Error responses
| HTTP status | Meaning |
|---|---|
| 400 | Invalid request (for example lookup with no id/email/phone) |
| 401 | Missing, invalid, revoked, or expired key, or external API access disabled |
| 402 | Tenant billing suspended |
| 403 | Authenticated key is missing the required scope |
| 404 | Resource not found in this tenant (including cross-tenant ids) |
| 429 | Rate limited — wait and retry (Retry-After: 60) |
Error bodies are generic and do not echo secrets. Treat 401 uniformly — do not probe for key state.
Zapier integration
Can Cleaning Software publishes an official Zapier Platform CLI app (invite-only while in private release). Scope of the app: read-only customer triggers plus Find Customer. No writes, messaging, payments, or scheduling actions.
Connect a tenant account
- In the CRM: Settings → Integrations → API Access → enable access and create a key with customers:read (name it e.g. Zapier)
- Copy the secret once (ccs_ak_…)
- In Zapier, connect Can Cleaning Software and paste the key into the secure API Key field
- Connection test calls GET /api/v1/integrations/me and labels the connection with tenant and key name
- The Zapier app always calls https://api.cancleaningsoftware.com — the API host is not user-configurable
Supported triggers
| Trigger | API | Dedup key | Notes |
|---|---|---|---|
| New Customer | GET /customers/changes/recent?kinds=created | changeId | Fires on CRM create only — not on later edits |
| Updated Customer | GET /customers/changes/recent?kinds=upserted | changeId | Each update event stays distinct |
| Customer Deactivated | GET /customers/changes/recent?kinds=deactivated | changeId | Archived/deactivated only |
Supported search
- Find Customer — exact match on Customer ID, email, and/or phone via GET /customers/lookup
Polling notes
- Zapier remembers seen changeId values per Zap; there is no shared cursor across Zaps
- changeKind / eventKind is historical; live status fields may differ after later transitions
- Polling is not lossless under sustained high volume when truncated is true — prefer the Google Sheets checkpoint sync or a future webhook for full catch-up
Security and data handling
- TLS to the production API host
- Never put API keys in query strings, sample payloads, logs, or error messages
- Revoke keys immediately from Settings → Integrations → API Access if a secret may have leaked
- Responses never include full card numbers, payment tokens, or private internal notes
- Each key is scoped to a single tenant; cross-tenant ids return 404
Questions: support@cancleaningsoftware.com
See also our Privacy Policy and Terms and Conditions.