GeoWise API
One engine, four calls that matter. You search availability for an address, hold a slot, create the booking from the hold, and listen to webhooks. Every time the API returns was already checked against zones, the coverage plan, working hours, travel from the neighbouring jobs, buffers, skills and capacity.
Base URL https://api.geowise.com/v1
Sandbox same URL — sandbox is selected by your key prefix (sk_test_)
Format JSON in, JSON out · UTF-8 · times are RFC 3339 with offset
Versioning path version (v1); additive changes are not versioned
Spec https://docs.geowise.com/openapi.json (OpenAPI 3.1)Rule that everything else follows from: never compute or guess an appointment time. Writes accept only slot_id and hold_id values that the API returned, never free-text times.
Authentication
Bearer tokens, per company. sk_test_… keys hit the sandbox (fake staff, fake travel times, no messages sent); sk_live_… keys act on the real calendar. Platform partners exchange an OAuth client for per-tenant keys.
curl https://api.geowise.com/v1/availability \
-H "Authorization: Bearer sk_test_51Q…" \
-H "Content-Type: application/json" \
-d '{ "service_id": "boiler-service", "address": "12 Oak Street, Sarajevo" }'Idempotency & retries
Send an Idempotency-Key header (any unique string, kept 24 h) on every POST. A network retry with the same key returns the original result instead of creating a second booking. Holds are the second safety net: a booking created from an expired hold is refused with hold_expired and fresh alternatives. It is never silently moved to a different time.
Errors & rate limits
Errors use one envelope. Availability is limited to 60 requests/min per key (burst 120); writes to 20/min. 429 responses carry Retry-After.
{ "error": { "code": "hold_expired",
"message": "Hold hold_h7 expired at 14:02. Re-check availability.",
"alternatives": [ { "slot_id": "slt_a12", "start": "2026-09-01T14:50:00+02:00" } ] } }invalid_address could not geocodeoutside_zones no zone covers it; fallbacks listedslot_taken someone booked it first; alternatives listedhold_expired re-check availabilitydeposit_required complete payment to confirmrate_limited respect Retry-After/v1/availabilityThe engine call. Takes an address and a service, tentatively inserts the job into every eligible technician's day, and returns the slots your scheduling mode chooses to show, with labels, optional grades, and a one-sentence explanation you can put in front of a customer or an agent.
service_id required service to book; duration comes from its settingsaddress required free-text address or {lat, lng}; geocoded and zone-matcheddate_from · date_to search window; defaults to lead time through horizonlimit max slots (default 6, max 10); keeps responses small for voice and chatanswers intake answers that derive duration (e.g. rooms: 4 → 150 min)POST /v1/availability
{ "service_id": "boiler-service",
"address": "12 Oak Street, Sarajevo",
"date_from": "2026-09-01", "date_to": "2026-09-07", "limit": 6 }
→ 200 OK
{ "zone": { "id": "north", "name": "North", "coverage_days": ["tue", "thu"] },
"mode": "back_to_back",
"slots": [
{ "slot_id": "slt_9f3", "start": "2026-09-01T11:55:00+02:00",
"window": "11:55–12:25", "technician": "Ana", "labels": ["nearby"],
"grade": 94, "why": "Ana is already on Oak Street at 11:30" },
{ "slot_id": "slt_a11", "start": "2026-09-01T09:05:00+02:00",
"window": "09:05–09:35", "technician": "Ana", "labels": ["first_of_day"] } ],
"fallbacks": { "request": true, "waitlist": true, "urgent": false,
"adjacent_zone": null } }/v1/holdsReserves a slot while the customer finishes checkout (default TTL 600 seconds, max 1800). The slot disappears from everyone else's availability for the duration. Holds are free and expire silently; nothing is booked until you convert one.
POST /v1/holds
{ "slot_id": "slt_9f3", "ttl_seconds": 600 }
→ 201 Created
{ "hold_id": "hold_h7", "slot_id": "slt_9f3",
"expires_at": "2026-09-01T14:02:11+02:00" }/v1/bookingsCreates the booking from a hold. The response status is confirmed when your rules allow instant confirmation, or pending when it becomes a request (outside plan, needs a quote, above your value threshold, or an untrusted channel). Pending requests keep reserving capacity until you decide.
POST /v1/bookings
Idempotency-Key: 4d3c…
{ "hold_id": "hold_h7",
"customer": { "name": "L. Hodžić", "phone": "+387 61 …", "channel": "whatsapp" },
"notes": "Combi boiler, error E119" }
→ 201 Created
{ "booking_id": "bkg_28xq", "status": "confirmed",
"start": "2026-09-01T11:55:00+02:00", "window": "11:55–12:25",
"technician": "Ana", "manage_url": "https://book.geowise.com/m/bkg_28xq",
"payment": { "deposit": null, "card_on_file": false } }Reschedule & cancel
Rescheduling is the same contract as booking: fetch availability, hold, then commit the hold onto the existing booking. Both operations are flagged destructive in the MCP schema, so assistants confirm with the customer before acting. Cancellations trigger waitlist backfill automatically.
GET /v1/bookings/bkg_28xq → status, times, technician, ETA link
PATCH /v1/bookings/bkg_28xq/reschedule { "hold_id": "hold_k2" }
POST /v1/bookings/bkg_28xq/cancel { "reason": "customer_request" }
→ cancellation policy applied,
gap offered to the waitlist/v1/customers?phone=…Lookup by phone or email, so an assistant or voice agent can answer "when is my appointment?" without a hand-off. Returns the customer, their upcoming bookings and their preferred technician.
GET /v1/customers?phone=%2B38761…
→ 200 OK
{ "customer_id": "cus_77b", "name": "L. Hodžić",
"preferred_technician": "Ana",
"upcoming": [ { "booking_id": "bkg_28xq",
"start": "2026-09-01T11:55:00+02:00", "service": "Boiler service" } ] }/v1/visits/{id}/etaThe live arrival estimate for a visit, recomputed continuously from technician location (shared during shifts only) and Google Maps traffic. The same data drives the customer's live-map link and proactive delay alerts.
→ 200 OK
{ "visit_id": "vis_3kk", "status": "en_route",
"promised_window": "11:55–12:25", "eta": "2026-09-01T12:07:00+02:00",
"delay_minutes": 12, "track_url": "https://book.geowise.com/t/vis_3kk" }Webhooks
Signed with HMAC-SHA256 in the Geowise-Signature header; retried with backoff for 24 h. Configure endpoints per event in the dashboard or via POST /v1/webhook_endpoints.
booking.confirmed booking.pendingbooking.rescheduled booking.cancelledvisit.en_route visit.arrivedvisit.completed visit.delayedroute.locked gap.openedwaitlist.offered payment.succeededpayment.failed{ "event": "visit.delayed", "created": "2026-09-01T11:41:02+02:00",
"data": { "visit_id": "vis_3kk", "booking_id": "bkg_28xq",
"promised_window": "11:55–12:25", "new_eta": "12:07",
"downstream_notified": true } }MCP server
One hosted server at https://mcp.geowise.com exposes the same engine to Claude, ChatGPT, Gemini, Copilot, Perplexity and any MCP client. Tools mirror the REST flow one-for-one; responses are capped at ten slots so chat and voice contexts don't overflow.
find_availability(business, service, address, date_range?) → slots with labels
hold_slot(slot_id, ttl?) → hold_id
create_booking(hold_id, customer) → confirmed | pending
lookup_bookings(phone | email) → upcoming bookings
reschedule_booking(booking_id, hold_id) ⚠ destructive — agent must confirm
cancel_booking(booking_id, reason?) ⚠ destructive — agent must confirm
get_eta(visit_id) → live arrival estimateSDKs, spec & llms.txt
TypeScript and Python SDKs generated from the OpenAPI 3.1 spec, a web component and a React component for the widget, and a machine-readable docs index for coding agents.
npm install @geowise/sdk # TypeScript, typed responses, retries built in
pip install geowise # Python
https://docs.geowise.com/llms.txt # index for agents — Claude Code, Cursor, Lovable, Bolt
npx skills add https://docs.geowise.comBuilding with an AI coding tool? The integration prompt on the feature page is written to be pasted straight into it.
GeoWise is an API first. Everything in the product is available through it.