The backend API
The custom CBP API that powers vehicle lookup, postcodes, address search, and installation booking.
The backend API
🛠 Dev
The custom features are powered by a separate backend service — the eb-cbp-rego-lookup
repository — deployed to Cloudflare Workers. The theme only ever talks to this API; the API in
turn talks to the external providers (vehicle data, postcodes, Google services, Shopify Admin). It
is maintained by Conversion Kings while CBP is on retainer (see Who owns what).
The backend has its own documentation in the eb-cbp-rego-lookup repo: a README.md, a Bruno/
Postman request collection under api-docs/, and the upstream AutoInfo specs under
api-reference/. Use those for exact request/response payloads; this page is the operational
overview.
Stack
| Concern | Technology |
|---|---|
| Framework | Hono on Cloudflare Workers |
| Language | TypeScript |
| Database | Cloudflare D1 (SQLite) via Drizzle ORM |
| Key/value | Cloudflare KV (OAuth state + per-shop Shopify tokens) |
| Auth | RS256 JWT via jose |
| Scheduled jobs | Cloudflare Cron Trigger (hourly) |
Configuration in the theme
The base URL is the api_base_url theme setting, exposed to JS via snippets/api-init.liquid
as window.theme.api.base (with a getEndpoint() helper). When debug mode
is on, the api_dev_base_url (dev/staging) is used instead.
Authentication
All customer-facing endpoints require a Bearer JWT from POST /v1/auth:
- Signed RS256 with the server's private key; the worker verifies with the matching public key.
- The token embeds the caller's forwarded IP/host and expires after 1 hour.
assets/session-manager.jsfetches, caches (insessionStorage), and refreshes it. Usewindow.theme.sessionManager.getToken()for authenticated calls.
The OAuth and webhook routes are not JWT-protected (they're secured by Shopify HMAC instead).
Endpoints
Used by the theme
| Endpoint | Method | Purpose |
|---|---|---|
/v1/auth | POST | Issue a 1-hour JWT. |
/v1/vehicle/{state}/{rego} | GET | Look up vehicle(s) by registration + state (AutoInfo). |
/v1/vehicle/{state}/{vehicleId}/parts | GET | Battery fitment for a vehicle, enriched with Shopify product data. |
/v1/postcode/{postcode} | GET | Resolve a postcode to suburb/state (Australia Post). |
/v1/places?q= | GET | Address autocomplete (Google Places). |
/v1/places/{placeId} | GET | Structured address for a selected place. |
/v1/schedule | GET | Installation slot availability (Google Calendar). |
/v1/schedule/reserve | POST | Reserve a slot and provision an Order ID. |
/v1/contact | POST | Contact-form submission (sends email). |
Platform / integration (not called by the storefront JS)
| Endpoint | Method | Purpose |
|---|---|---|
/v1/oauth/start, /v1/oauth/callback | GET | Shopify app OAuth onboarding (stores the shop's Admin API token). |
/v1/webhooks/order_paid | POST | Shopify webhook that confirms a booking on payment. |
/v1/heartbeat | GET | Health check. |
The /v1/.../parts response is enriched by querying the Shopify Admin API for each fitment SKU.
This requires the shop to be onboarded via OAuth (its token stored in KV). If the shop isn't
connected, parts lookup returns a 403 and no batteries appear — see
Managing battery products and Common issues.
The booking lifecycle (reserve → pay → confirm)
The Order ID ties a calendar slot, a database record, and a Shopify order together across three steps:
- Reserve —
POST /v1/schedule/reservetakes the slot, customer, vehicle and battery details. It flips the Google Calendar event from "Open Slot" to "[UNCONFIRMED] Installation for …", inserts anunconfirmedOrdersrow (a UUID = the Order ID,confirmedAt = null), emails a notification, and returns the Order ID. The theme attaches it to both cart line items. - Pay — the customer checks out in Shopify. The order carries the
_Order IDline-item property. - Confirm — Shopify fires
order_paid→POST /v1/webhooks/order_paid. The worker finds the booking by_Order ID, setsconfirmedAt, converts the calendar event to a permanent "Installation for …" event, and emails the customer and office.
Unpaid reservations expire. An hourly cron job releases any unconfirmedOrders older than
~1 hour that were never confirmed — it reverts the calendar event back to "Open Slot" and deletes
the row. So a slot a customer reserved but didn't pay for is returned to availability automatically.
This also means if the order_paid webhook is not configured or fails, a genuinely paid booking
can be auto-released — see Common issues.
External providers
| Provider | Used by | Configured via (secret/var names) |
|---|---|---|
| AutoInfo (vehicle + parts) | /v1/vehicle/* | AUTOINFO_USER_ID, AUTOINFO_AUTH_CODE |
| Australia Post (postcodes) | /v1/postcode/* | AUSPOST_API_KEY |
| Google Calendar (slots) | /v1/schedule*, webhook | GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_IMPERSONATE_EMAIL |
| Gmail (notifications) | reserve, contact, webhook | GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_IMPERSONATE_CONTACT_EMAIL |
| Google Places (addresses) | /v1/places* | GOOGLE_MAPS_API_KEY |
| Shopify Admin API (product enrichment, OAuth) | /v1/.../parts, OAuth | SHOPIFY_APP_CLIENT_ID, SHOPIFY_APP_SHARED_SECRET, SHOPIFY_DEFAULT_SHOP |
| JWT keys | /v1/auth, middleware | JWT_PRIVATE_KEY, JWT_PUBLIC_KEY |
These names come from the backend's .dev.vars.example / .env.example. Values are stored as
Cloudflare Worker secrets / wrangler vars — never in this handbook or the theme.
Database (D1)
Two tables (Drizzle schema, src/db/schema.ts in the backend repo):
installers— each installer's email and Google Calendar ID. Availability is read from these calendars. See Appointments & scheduling.unconfirmedOrders— a booking record per reservation: the Order ID (UUID), vehicle/battery, customer, slot/calendar IDs, booking date/time,createdAt, andconfirmedAt.
Environments & deployment
Two Cloudflare accounts, two wrangler configs (see Who owns what):
| Production | Staging | |
|---|---|---|
| Owner | Car Battery Pro | Conversion Kings |
| Wrangler config | wrangler.jsonc | wrangler-ck.jsonc |
| Worker name | eb-cbp-api | cbp-rego-lookup-api |
| Deploy command | npm run deploy:prod | npm run deploy |
The theme points at production (the api_base_url setting) in the live store, and at staging when
debug mode is on (api_dev_base_url).
For a future handover: the API is a separate repository and infrastructure from this theme.
Production Cloudflare resources are owned by Car Battery Pro; the staging account belongs to
Conversion Kings. A new maintainer needs the eb-cbp-rego-lookup repo, the Cloudflare accounts,
all the provider credentials above, and the Shopify app (client ID/secret + order_paid webhook).
Confirm exact access during handover.