Examples
Start quickly with curl examples for the current public v1 records and webhook registration.
Examples
These examples use curl, but the same request shapes work in Postman, Zapier, Make, or your own backend.
Set your key first:
export CEPRO_API_KEY="ce_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxx"Create A Lead
curl https://app.cleanestimate.pro/api/v1/leads \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lead-demo-001" \
-d '{
"customer_name": "Taylor Morgan",
"customer_email": "taylor@example.com",
"customer_phone": "+15551234567",
"service_address": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202",
"service_interest": ["house-wash"],
"notes": "Requested a Saturday estimate"
}'List Leads
curl "https://app.cleanestimate.pro/api/v1/leads?limit=25&sort=created_at&order=desc&status=new" \
-H "Authorization: Bearer $CEPRO_API_KEY"Create A Client
curl https://app.cleanestimate.pro/api/v1/clients \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client-demo-001" \
-d '{
"name": "Taylor Morgan",
"email": "taylor@example.com",
"phone": "+15551234567",
"address_line1": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202"
}'Write Notes To Clients, Leads, And Jobs
The current public v1 API supports note fields on clients, leads, and jobs. You can send notes when you create the record or patch notes onto an existing record later.
Add Notes To A Client
curl https://app.cleanestimate.pro/api/v1/clients \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client-notes-demo-001" \
-d '{
"name": "Taylor Morgan",
"email": "taylor@example.com",
"phone": "+15551234567",
"notes": "Prefers text first. Side gate code is 4421.",
"status": "active"
}'curl https://app.cleanestimate.pro/api/v1/clients/CLIENT_ID \
-X PATCH \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client-notes-update-001" \
-d '{
"notes": "Requested Friday afternoon appointments only."
}'Add Notes To A Lead
curl https://app.cleanestimate.pro/api/v1/leads \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lead-notes-demo-001" \
-d '{
"customer_name": "Taylor Morgan",
"customer_email": "taylor@example.com",
"customer_phone": "+15551234567",
"service_address": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202",
"notes": "Requested Saturday estimate. Interested in house wash and driveway.",
"source": "api"
}'curl https://app.cleanestimate.pro/api/v1/leads/LEAD_ID \
-X PATCH \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: lead-notes-update-001" \
-d '{
"notes": "Left voicemail. Follow up tomorrow morning."
}'Create An Estimate
curl https://app.cleanestimate.pro/api/v1/estimates \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: estimate-demo-001" \
-d '{
"customer_name": "Taylor Morgan",
"customer_email": "taylor@example.com",
"customer_phone": "+15551234567",
"address_line1": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip": "28202",
"services": [
{
"service_slug": "house_wash",
"enabled": true,
"sqft": 2400,
"stories": 2
}
],
"addons": [],
"custom_items": [],
"dirtiness_level": "light"
}'A successful live create returns the priced estimate and a signed customer link:
{
"data": {
"id": "ESTIMATE_ID",
"estimate_number": "EST-20260731-0001",
"status": "draft",
"total": 975,
"view_url": "https://app.cleanestimate.pro/estimate/ESTIMATE_ID?token=SIGNED_TOKEN"
}
}Use view_url as returned. Do not copy CleanEstimatePro's customer-token secret into the calling system or build the link yourself. Test API keys return view_url: null.
Test A Photo-Quote Integration Without Contacting A Lead
Keep the estimate in draft and send the returned view_url through your own email provider to a fixed operator address. The Rolling Suds listing-photo integration uses this pattern for its production test: it asks the configured Apify actor for up to 25 live Lancaster for-sale results, picks one property, analyzes its Zillow image, creates the draft estimate, and sends the result to the configured test inbox. It does not attach the test quote to the outreach lead or email the listing agent.
The scheduled photo analyzer uses gpt-5.6-luna with low reasoning and high-detail image input by default. This keeps routine listing analysis on the lower-cost vision model while preserving strict structured output for pricing. Its surface inventory records every visible serviceable deck, patio or hardscape, and fence even when that surface looks relatively clean. Dirt and staining still affect the condition assessment, but they are not required for a visible patio to appear in the draft quote. If an operator supplies a reviewed assessment for an isolated test, the matching Apify property payload and assessment must be provided together. That override is not accepted by the daily outreach run. A test also fails when vision produces no assessment, so a provider-credit or model error cannot look like a successful quote email.
Use a stable Idempotency-Key for the test property and recipient. A retry then reuses the same CleanEstimatePro estimate instead of creating another draft. External estimate integrations should send the registered api source value; CleanEstimatePro records the creating API key separately for integration-level traceability.
The Rolling Suds integration fingerprints the canonical quote request in its idempotency key. Ordinary retries of the same scope reuse the same estimate, while a legitimate scope revision—such as adding a patio found in another listing photo—creates a new draft without reusing a key with a different body.
Deliver A Photo Quote In Three Touches
The Rolling Suds realtor campaign uses one property and one request in each email:
- Day 0: Name the listing and send the signed CleanEstimatePro
view_urlas
a courtesy. Explain how clean siding and windows improve the buyer's first impression when they arrive at the home. Rolling Suds gives active listings priority scheduling so a crew can start once the seller is ready.
- Day 4: Resurface the quote and invite the agent or seller to call or text
with questions. Repeat the priority-scheduling benefit.
- Day 8: Close the note on that property, keep the quote available for a
showing, inspection, or settlement, and make one light referral request.
If the same agent adds another listing, the campaign can send a property-specific quote after the four-day contact cooldown. Each property is introduced at most once. The ordinary sequence ends after the third touch.
Quote copy must follow the result returned by the integration. When a signed view_url exists, the first email sends it as a courtesy and notes that crews confirm measurements and scope on site. The first email does not use roof or insurance pressure. When the photos do not support a reliable quote, the autopilot skips the email instead of claiming that a quote exists.
The outreach app checks suppression again before every send. Unsubscribes, bounces, and complaints stop later delivery. Mailbox replies are not ingested by the current integration, so an operator must pause or suppress a contact after a reply until inbound reply events are connected.
Update An Estimate
curl https://app.cleanestimate.pro/api/v1/estimates/ESTIMATE_ID \
-X PATCH \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: estimate-update-demo-001" \
-d '{
"customer_phone": "+15559876543",
"assigned_rep_id": "00000000-0000-0000-0000-000000000123",
"status": "sent",
"expires_at": "2026-04-19T00:00:00.000Z"
}'PATCH /api/v1/estimates/:id is intentionally narrow. Use it for customer/contact metadata, address/property details, assignment, draft/sent status, source, and expires_at. If the service selection or pricing inputs change, create a fresh estimate instead of trying to reprice through this endpoint.
Register A Webhook Destination
curl https://app.cleanestimate.pro/api/v1/webhooks \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: webhook-demo-001" \
-d '{
"name": "Ops Sync",
"url": "https://example.com/cepro/webhooks",
"events": ["lead.created", "estimate.created", "job.completed"]
}'The create response includes the webhook secret once. Store it immediately so you can verify signatures later.
ce_test_... keys cannot call /api/v1/webhooks in v1. Use a live key when you are ready to register real outbound destinations.
Create A Job
curl https://app.cleanestimate.pro/api/v1/jobs \
-X POST \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-demo-001" \
-d '{
"client_id": "00000000-0000-0000-0000-000000000001",
"service_type": "house_wash",
"scheduled_date": "2026-03-20",
"scheduled_time_start": "09:00:00",
"scheduled_time_end": "11:00:00",
"customer_name": "Taylor Morgan",
"service_address": "123 Main St",
"service_city": "Charlotte",
"service_state": "NC",
"service_zip": "28202"
}'curl https://app.cleanestimate.pro/api/v1/jobs/JOB_ID \
-X PATCH \
-H "Authorization: Bearer $CEPRO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: job-notes-update-001" \
-d '{
"notes": "Customer requested a 30-minute arrival call.",
"access_notes": "Use side gate. Code 4421.",
"special_instructions": "Park on the street, not in the driveway."
}'List Invoices
curl "https://app.cleanestimate.pro/api/v1/invoices?limit=25&sort=created_at&order=desc&status=sent" \
-H "Authorization: Bearer $CEPRO_API_KEY"Webhook routes are live-only in the current public v1 surface. Use a ce_live_... key for /api/v1/webhooks.
Pull The OpenAPI Spec
curl https://app.cleanestimate.pro/api/v1/openapi.jsonUse the OpenAPI file for:
- Swagger UI
- Postman import
- SDK generation
- Contract validation in CI
Was this article helpful?
Still need help? Contact support