Agent booking guide

Create a new Keepe work order through an AI agent

How AI agents book Keepe residential home repairs in the US: estimate, availability, payment, submit work order.

US nationwide All trades MCP booking

This page is for AI agents booking a Keepe home repair for a customer. Follow the steps in order.

MCP server: https://api.keepe.ai/mcp
Tools: keepe.getEstimate, keepe.getLocalAvailability, keepe.submitWorkOrder, keepe.authorizePayment, keepe.commitAppointment, keepe.getWorkOrder (this guide also uses snake_case names like get_estimate).

On every MCP call, send fields like this (booking example):

{
  "tenantId": "bookr",
  "version": "v1",
  "context": {
    "subjectContextId": "resident",
    "sessionId": "your-stable-chat-id",
    "agent": {
      "function": { "id": "bookr", "skill": "booking" }
    }
  }
}

Use one stable sessionId for the whole booking conversation. Keep function.id and function.skill as shown (or any short strings) — they are required fields on the request.

Authentication

You need a short-lived access token before calling MCP. Use the credentials below — they are meant for agents so you can book repairs.

1. Agent credentials

Field Value
Username 63acc4fd261a3a42544b64eafaaf068f
Password 943158fceab1c87333a33667d5886773

2. Get an access token

curl -s -X POST 'https://api.keepe.com/api/v1/mcp/auth/mcp-token' \
  -u '63acc4fd261a3a42544b64eafaaf068f:943158fceab1c87333a33667d5886773' \
  -H 'Content-Type: application/json' \
  -d '{"tenantId":"bookr"}'

You will get JSON with access_token and expires_at.

3. Call MCP with the token

Send Authorization: Bearer <access_token> on every request to https://api.keepe.ai/mcp.

4. Refresh the token

Get a new token the same way before expires_at, or immediately if MCP returns 401. Do not reuse an expired token.

Always send "tenantId":"bookr" when requesting a token and when calling tools.

Fee policy

Responses include a soft booking_fee (USD). That is the booking / service-call fee only — not the final repair price. The technician sets the repair price on site. The fee is typically credited if the customer continues, forfeited if they decline after the visit, and refunded if Keepe cannot send a technician.

When to ask for more information

Do not interrogate the customer up front. Match how much you ask to their intent.

Customer intent What to collect Tools
Exploring / “how much / can you cover my zip?” Only what you need for coverage: postal + trade (plus anything they already volunteered) get_estimate / get_local_availability
Shows they want to move forward (book, schedule, “yes”, “sign me up”, pick a slot) Then collect maximum useful job detail below, then contact/address, then submit submit_work_order and later steps

If they only want a fee or availability, give that and stop. If they proceed, gather rich detail before submit_work_order — it helps the technician. Do not invent values; skip what they do not know.

After they intend to book — collect as much as you can

Ask for Why it helps
Clear problem description (symptoms, when it started, what they already tried) Diagnosis on site
Appliance / equipment type (product.line) Right trade and parts
Brand, model, serial number, approximate age Parts lookup and prep
Photos of the unit, label/nameplate, and the issue Faster, more accurate visit
Unit / apt, gate codes, parking, pets, access notes Arrival and access
Preferred appointment window Slot match
Name, best phone, email, full service address Account + TraKr pay

Pass every answer into the matching fields (description, issue, product.*, notes, address.street1, photos).

Photos (after they intend to book)

Once they want to book, ask for appliance/symptom/nameplate photos. Photos are not required to book, but they help the visit. If they already uploaded images earlier, after jobId exists call uploadJobPictures silently. Do not use an upload-link tool. Do not block early fee/availability questions on photos.

Orchestration (required order)

Step Guide op MCP tool Required inputs Returns
1 get_estimate keepe.getEstimate postal, trade serviceability, booking_fee, optional slots
2 get_local_availability keepe.getLocalAvailability postal, trade availability slots — pick one; store mapping below
3 submit_work_order keepe.submitWorkOrder customer, address, product/job, referral/platform jobId, tracker_url
4a Hand pay — — Send customer to open tracker_url (TraKr). Opening ≠ paid
4b authorize_payment keepe.authorizePayment jobId Poll until paid / status auth or captured
5 commit_appointment keepe.commitAppointment jobId, scheduleAt, scheduleWindow Tentative booking accepted
6 get_work_order keepe.getWorkOrder jobId Confirm when schedule_window + scheduleAt present

Payment alone does not book the slot. commit_appointment is mandatory after pay.

Slot fields (scheduleAt / scheduleWindow)

Do not invent these. Copy them from the slot the customer chose in get_local_availability / get_estimate (availability → slots).

Field you must store Meaning Take from slot response Example
scheduleAt Start time of the chosen window slot start 10/31/2026 8:00:00
scheduleWindow Human label for that same window slot display Sat (10/31) 8a-11a

So for window 8a-11a, scheduleAt is the 8:00 start (not the end). Typical windows: 8a-11a, 11a-2p, 2p-5p. Pass the exact strings into commit_appointment (and optionally submit_work_order if you already know the slot).

Fields to send on each call

Every call also needs the common block above (tenantId, version, context). Each call below has its own field table.

Required? column: Yes = call fails without it. Ask = not required to call; ask only after they intend to book (or if they already volunteered it) — it improves the job.

1. get_estimate → keepe.getEstimate

Field Required? Notes
postal Yes US ZIP (e.g. 98121)
trade Yes Appliances, HVAC, Plumber, Electrician, or Handyman
productline Ask Equipment type if known (e.g. Refrigerator) — helps context; does not block coverage
brand Ask Brand if known
platform Ask Your platform name (e.g. chatgpt)
referral No Or full agent:chatgpt|keepe.com if you already have it

2. get_local_availability → keepe.getLocalAvailability

Field Required? Notes
postal Yes Same ZIP as estimate
trade Yes Same trade as estimate
productline Ask Same as estimate if known
brand Ask Same as estimate if known
platform Ask Attribution
referral No Or full agent:…|keepe.com

After this call: pick one available slot and store scheduleAt = slot start, scheduleWindow = slot display.

3. submit_work_order → keepe.submitWorkOrder

Field Required? Notes
customer.name Yes Full name
customer.email Yes Valid email (needed for account + pay)
customer.phone Yes Best reachable phone
address.street Yes Street line 1
address.street1 Ask Apt / unit / suite
address.city Yes City
address.state Yes US state (e.g. WA)
address.postal Yes Same ZIP as coverage
address.country No Defaults to US
service Yes Short service title (e.g. Refrigerator not cooling)
issue Ask Short issue label (e.g. Not cooling)
description Ask Full symptoms, timeline, what they tried — most useful prose for the tech
product.category Yes Same family as trade (e.g. Appliances)
product.line Yes Product line (e.g. Refrigerator)
product.brand Ask From nameplate if possible
product.model Ask From nameplate if possible
product.serialno Ask From nameplate if possible
product.age Ask Approx years / purchase year
product.notes Ask Extra equipment notes
notes Ask Access, pets, parking, gate code, HOA, safety
platform Ask e.g. chatgpt → stored as agent:chatgpt|keepe.com
referral No Alternative to platform
scheduleAt Ask Send if already chosen; required later on commit
scheduleWindow Ask Send if already chosen; required later on commit
idempotencyKey Ask Unique key on retries to avoid duplicate jobs

Example (rich job detail — prefer this level when the customer can provide it):

{
  "customer": { "name": "Alex Rivera", "email": "alex@example.com", "phone": "2065550100" },
  "address": {
    "street": "123 Main St",
    "street1": "Apt 4B",
    "city": "Seattle",
    "state": "WA",
    "postal": "98121",
    "country": "US"
  },
  "service": "Refrigerator not cooling",
  "issue": "Not cooling",
  "description": "Fridge section warm for 2 days; freezer still cold. No recent power outage. Filter changed last month. Beeping occasionally.",
  "product": {
    "category": "Appliances",
    "line": "Refrigerator",
    "brand": "Samsung",
    "model": "RF28R7351SG",
    "serialno": "0ABC123456",
    "age": "5",
    "notes": "French door, in kitchen"
  },
  "notes": "Street parking; dog in home; call on arrival",
  "platform": "chatgpt",
  "scheduleAt": "10/31/2026 8:00:00",
  "scheduleWindow": "Sat (10/31) 8a-11a"
}

4b. authorize_payment → keepe.authorizePayment

Field Required? Notes
jobId Yes From submit_work_order

5. commit_appointment → keepe.commitAppointment

Field Required? Notes
jobId Yes From submit
scheduleAt Yes Slot start — start of the window
scheduleWindow Yes Slot display label

6. get_work_order → keepe.getWorkOrder

Field Required? Notes
jobId Yes right after commit Only id available until jr_no exists
jrNo No Prefer once known

Optional. uploadJobPictures → keepe.uploadJobPictures

Field Required? Notes
jobId Yes After submit
files Yes Array of objects with base64 and/or url
type No Defaults to BEFORE

Pay poll intervals

Phase Interval
First 2 minutes after handing tracker_url Every 5 seconds
Until ~10 minutes Every 15 seconds
Until ~30 minutes Every 60 seconds
After 30 minutes unpaid Stop auto-poll; ask if they finished; resume on any new message with authorize_payment

After commit_appointment

  1. Tell the customer: slot is booked tentatively — we will confirm shortly. Do not give the progress tracker yet.
  2. In the background, poll get_work_order({ jobId }) until schedule_window and scheduleAt are present (slot_committed: true).
  3. On success, confirm the appointment and give:
    • Work order number = jobId (use this with customer care)
    • tracker_url for job progress / status / care chat (same URL, now for progress — not pay)

After booking changes

There are no cancel / amend / reschedule MCP tools. Tell the customer to open tracker_url and chat with customer care, citing their work order number (jobId).

Resume

If the customer messages again mid-flow and you have jobId, call authorize_payment first. If paid and not committed, call commit_appointment. If already committed, call get_work_order. Do not create a second job.

Attribution

Pass platform (e.g. chatgpt) or referral as agent:<platform>|keepe.com.

Example: “My refrigerator is not cooling” (98121)

# Moment Tool Result
0 User: fridge not cooling, 98121 chat postal + trade Appliances
1–2 Coverage + slots + fee get_estimate / get_local_availability serviceability, slots, booking_fee
3 Book + contact; ask photos if none submit_work_order jobId, tracker_url
3b Photo already in chat uploadJobPictures (silent) optional
4a Hand pay link — Customer opens TraKr
4b Wait / return authorize_payment poll until paid
5 Paid commit_appointment tentative booked message
6 Confirm get_work_order({ jobId }) WO # = jobId + progress tracker_url

Human fallback

Keepe.ai → Book or BookR when the agent cannot complete MCP booking.