API Reference

Estimatic API docs

Everything you need to generate, refine, and push estimates from any language — plus the MCP server for AI agents. Base URL: https://api.estimatic.ai/v1

Quickstart

Five minutes from zero to your first priced estimate. Pick a language, paste a key, ship.

1. Get a key

Email developers@estimatic.ai or grab one from Settings → Developers inside the app. Keys are scoped to a single organization and prefixed sk_live_ or sk_test_.

2. Curl — first estimate

bash
curl -X POST https://api.estimatic.ai/v1/estimates \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Replace 24 LF of cedar privacy fence, 6ft tall, with 2 gates.",
    "trade": "fencing",
    "project_zip": "30303"
  }'

3. Node SDK (preview — request access)

typescript
// npm i @estimatic/node
import { Estimatic } from "@estimatic/node";

const estimatic = new Estimatic({ apiKey: process.env.ESTIMATIC_KEY! });

const estimate = await estimatic.estimates.create({
  prompt: "Master bath gut remodel, double vanity, heated floors",
  trade: "remodel",
  project_zip: "30303",
  options: { good_better_best: true },
});

console.log(estimate.options.map(o => `${o.tier}: $${o.total_price}`));

4. Python SDK (preview — request access)

typescript
# pip install estimatic
from estimatic import Estimatic

client = Estimatic(api_key=os.environ["ESTIMATIC_KEY"])

estimate = client.estimates.create(
    prompt="Replace 30sq architectural shingle roof, tear-off, ice & water on eaves",
    trade="roofing",
    project_zip="30303",
)

print(estimate.totals.grand_total)

5. MCP — hello world

json
{
  "mcpServers": {
    "estimatic": {
      "url": "https://api.estimatic.ai/mcp",
      "headers": { "Authorization": "Bearer sk_live_..." }
    }
  }
}

Introduction

The Estimatic API turns any system you already run — a CRM, a lead form, an in-house AI agent — into an estimating powerhouse. Same engine, same local pricing, same one-click push that powers the Estimatic app.

Two surfaces, one engine

Use the REST API for traditional integrations. Use the MCP server when you want an AI agent (Claude, Cursor, ChatGPT, your own) to generate, refine, and push estimates as a tool. Both call the same backend, share the same auth, and emit the same webhooks.

Design principles

Predictable resource URLs. Verbs map to HTTP methods. Errors are structured JSON. Every mutating call accepts an Idempotency-Key. List endpoints use cursor pagination. We will never break a /v1/ contract — breaking changes ship behind /v2/.

Status

Currently in private beta. Request a key from your account manager or email developers@estimatic.ai. We onboard new partners weekly.

Authentication

All requests require a Bearer token scoped to a single organization. Generate keys from Settings → Developers. Treat them like passwords — server-side only, never in a mobile app or browser bundle.

Key scopes

Keys can be scoped at creation. estimates:read — list/get only. estimates:write — create, refine, archive. integrations:push — push to connected CRMs. webhooks:manage — create/delete webhooks. clients:write — create/update clients. Combine as needed; default new keys get estimates:read + estimates:write.

Multi-org partners

If you hold a partner key that can act on behalf of multiple orgs, pass X-Estimatic-Org: org_01HX... on every request. Without the header the request is rejected with permission_denied.

Key rotation

Rotate from Settings → Developers. The old key keeps working for 24 hours after rotation, giving you a window to deploy. We email every key holder 7 days before any forced rotation.

GET/v1/me

Verify the current API key and return the org it belongs to.

bash
curl https://api.estimatic.ai/v1/me \
  -H "Authorization: Bearer sk_live_..."

Errors

All errors return a consistent JSON shape. Read error.type — never parse the message string. HTTP status follows REST conventions.

Error envelope

json
{
  "error": {
    "type": "validation_failed",
    "message": "project_zip must be a 5-digit US ZIP code.",
    "param": "project_zip",
    "request_id": "req_01JXYZ..."
  }
}

Error types

invalid_request (400) — malformed JSON or missing required field. authentication_failed (401) — bad or missing key. permission_denied (403) — key lacks the scope or org. not_found (404) — id doesn't exist or isn't visible to the caller. conflict (409) — resource already exists in a state that blocks the operation. validation_failed (422) — input parsed but failed business rules; check param. rate_limited (429) — slow down, see Retry-After. idempotency_replayed (200, header) — not an error, but the response was served from cache; check Idempotency-Replayed: true. upstream_unavailable (502) — a connected integration timed out; safe to retry. internal_error (500) — ours; we get paged, you can retry with backoff.

Retry policy

Retry 429, 502, 503, and 504 with exponential backoff (start at 1s, double, cap at 30s, max 5 attempts). Always retry with the same Idempotency-Key — never generate a new one for retries.

Rate limits & idempotency

Base URL & versioning

https://api.estimatic.ai/v1 — versioned in the URL. Breaking changes ship behind /v2/. Additive changes never break existing /v1/ clients.

Idempotency

Every POST accepts an Idempotency-Key header (any unique string up to 255 chars — UUID v4 recommended). Re-sending the same key within 24 hours returns the original response with header Idempotency-Replayed: true. Different request body + same key = 409 conflict.

Rate limits

60 req/min per key by default. Burst headroom of 20. Higher limits available on request — email developers@estimatic.ai with your expected volume.

Response headers

X-RateLimit-Limit — your ceiling. X-RateLimit-Remaining — what's left in the current window. X-RateLimit-Reset — Unix seconds until the bucket refills. Retry-After — only on 429, seconds to wait. Idempotency-Replayed — present and true when a cached response is returned. X-Request-Id — include this in any support email.

Estimates

The headline resource. One endpoint takes a plain-English prompt (plus optional files, ZIP, trade) and returns a fully-priced estimate with good/better/best options.

POST/v1/estimatesIdempotent

Generate a new estimate from a prompt, files, or both.

bash
curl -X POST https://api.estimatic.ai/v1/estimates \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Master bath gut remodel...",
    "project_zip": "30303",
    "options": { "good_better_best": true }
  }'

Request fields

FieldTypeDescription
promptrequiredstringPlain-English scope of work. The more detail, the better the estimate. Include trade, dimensions, materials, and any special conditions.
tradestringHint for trade routing. Inferred from prompt if omitted.
remodelroofingfencingelectricalplumbinghvaclandscapinggeneral
project_addressstringFull street address. Used for permit lookup and accurate local pricing.
project_zipstring5-digit US ZIP. Drives local labor and material pricing. Strongly recommended.
filesarray<File>Attached plans, photos, LiDAR scans, or reports. Each item is { url, kind }. Upload via POST /v1/files first.
client_idstringAttach the estimate to an existing client. Optional.
pricing_profile_idstringOverride the org default pricing profile (markups, supplier preferences, rounding).
options.good_better_bestbooleanGenerate three priced tiers. Default true.
options.upsellsbooleanInclude suggested add-on line items (heated floors, smart fixtures, etc.). Default false.
options.local_permitsbooleanLook up and add jurisdictional permit fees. Default true.

Response fields

FieldTypeDescription
idstringUnique estimate id. Prefix est_.
titlestringAuto-generated from the prompt; editable via PATCH.
scope_summarystring1–2 sentence client-presentable summary of the scope.
groupsarray<Group>Line items grouped by phase (Demolition, Framing, Finishes, etc.).
optionsarray<Option>Good/Better/Best tiers. Each has tier, name, total_price, highlights[].
totalsobjectAggregated math: subtotal, labor, materials, permits, overhead, profit, tax, grand_total.
is_demobooleanTrue for sandbox/demo responses; never invoiceable.
  • Average response time: 8–14 seconds. Use POST /v1/estimates:stream for token-by-token progress.
  • Local pricing is resolved from project_zip. Falls back to org default if omitted.
  • files[].url must be an Estimatic-hosted URL — upload first via POST /v1/files.
POST/v1/estimates:streamIdempotent

Same as POST /v1/estimates, but streams progress as Server-Sent Events.

bash
curl -N -X POST https://api.estimatic.ai/v1/estimates:stream \
  -H "Authorization: Bearer sk_live_..." \
  -H "Accept: text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Re-roof 30sq architectural","project_zip":"30303"}'
  • Event types: status, line_item, option, total, done, error.
  • Always emits exactly one done or error event. Close the stream after either.
GET/v1/estimates

List estimates, newest first. Supports cursor pagination.

bash
curl "https://api.estimatic.ai/v1/estimates?limit=20&status=draft" \
  -H "Authorization: Bearer sk_live_..."
  • Query params: limit (1–100, default 20), cursor, status (draft|sent|accepted|declined|archived), client_id, created_after (ISO8601), created_before.
GET/v1/estimates/{id}

Retrieve a single estimate by id.

bash
curl https://api.estimatic.ai/v1/estimates/est_01JXYZ \
  -H "Authorization: Bearer sk_live_..."
PATCH/v1/estimates/{id}

Programmatic edits to line items — rename, change qty/unit_price, reorder, add custom lines. Use refine() for natural-language edits.

json
{
  "ops": [
    { "op": "update_item", "group": "Demolition", "item_id": "li_01...", "fields": { "quantity": 16 } },
    { "op": "add_item", "group": "Permits", "item": { "kind": "fee", "name": "City of Atlanta permit", "quantity": 1, "unit_price": 450 } },
    { "op": "remove_item", "item_id": "li_07..." }
  ]
}
  • ops are applied in array order, atomically — if any op fails, the whole request rolls back.
  • op types: update_item (mutate fields on an existing line), add_item (append a new line to a group), remove_item (delete a line), update_group (rename or reorder a group), set_title, set_status.
  • update_item.fields can include: name, description, quantity, unit, unit_cost, unit_price, taxable, notes.
POST/v1/estimates/{id}/refineIdempotent

Chat-style refinement. The agent re-prices and returns the updated estimate.

bash
curl -X POST https://api.estimatic.ai/v1/estimates/est_01JXYZ/refine \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"message":"Add a permit line for the city of Atlanta."}'
POST/v1/estimates/{id}/pushIdempotent

Push a finished estimate into a connected CRM or accounting system. Same field-mapping the Chrome extension uses.

bash
curl -X POST https://api.estimatic.ai/v1/estimates/est_01JXYZ/push \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"integration":"servicetitan","target":"estimate"}'
POST/v1/estimates/{id}/sendIdempotent

Email or SMS the estimate to the customer with a signable share link.

json
{
  "channel": "email",
  "to": "sarah@example.com",
  "subject": "Your master bath estimate",
  "message": "Hi Sarah — here's the estimate we discussed. Tap to review and sign.",
  "require_signature": true
}

Request fields

FieldTypeDescription
channelrequiredstringDelivery channel.
emailsmsboth
tostringOverride recipient. Defaults to the attached client's primary contact.
subjectstringEmail subject. Ignored for SMS.
messagestringCustom message body. A default branded template is used if omitted.
require_signaturebooleanRequire the customer to e-sign before accepting. Default true.
POST/v1/estimates/{id}/duplicateIdempotent

Clone an estimate as a fresh draft. Useful for templating recurring jobs.

json
{ "title": "Master bath — Greene residence v2", "client_id": "cli_01HZ..." }
POST/v1/estimates/{id}/status

Manually transition an estimate's lifecycle status.

json
{ "status": "accepted", "selected_option": "better", "note": "Signed at site visit." }

Request fields

FieldTypeDescription
statusrequiredstringTarget status. Must be a valid forward transition.
draftsentaccepteddeclinedarchived
selected_optionstringWhich tier the customer chose. Required when status=accepted.
goodbetterbest
notestringInternal note attached to the status change.
DELETE/v1/estimates/{id}

Archive an estimate. Soft-delete — restorable for 30 days from the app.

bash
curl -X DELETE https://api.estimatic.ai/v1/estimates/est_01JXYZ \
  -H "Authorization: Bearer sk_live_..."

Files

Upload plans, site photos, LiDAR scans, Hover/EagleView reports, or PDFs. The returned URL is what you pass into files[] on POST /v1/estimates.

POST/v1/files

Upload a file. Multipart/form-data. 50 MB cap per file.

bash
curl -X POST https://api.estimatic.ai/v1/files \
  -H "Authorization: Bearer sk_live_..." \
  -F "kind=plan" \
  -F "file=@./floorplan.pdf"
  • Returned URL is a signed, single-org URL valid for 24 hours. Attach it to an estimate before it expires — once attached, it's persisted.
  • Allowed mime types: application/pdf, image/jpeg, image/png, image/heic, model/gltf-binary, application/octet-stream (LiDAR).
GET/v1/files/{id}

Retrieve file metadata and a fresh signed URL.

Contacts (leads)

Inbound leads — people who filled out a form, called in, or were captured by your CRM but haven't been qualified into a paying client yet. Convert a contact to a client when you're ready to send them an estimate.

POST/v1/contactsIdempotent

Create a lead. Idempotent on (email|phone) + source within 24h.

json
{
  "name": "Marcus Webb",
  "email": "marcus@example.com",
  "phone": "+14045550199",
  "source": "website_form",
  "utm": { "source": "google", "medium": "cpc", "campaign": "bath-remodel-atl" },
  "scope_summary": "Wants to gut master bath, asked about heated floors.",
  "address": { "line1": "812 Peachtree", "city": "Atlanta", "state": "GA", "postal_code": "30303" },
  "custom_fields": { "budget_band": "$25-40k", "timeline": "next 60 days" }
}

Request fields

FieldTypeDescription
namerequiredstringFull name.
emailstringAt least one of email or phone is required.
phonestringE.164 format preferred (+14045550199).
sourcestringWhere the lead came from. Free-form, but common values are conventional.
website_formphone_callreferralgoogle_lsafacebookyelpangimanual
utmobjectUTM tracking object: { source, medium, campaign, term, content }.
scope_summarystringWhat the lead asked for. Free-form notes from the intake call/form.
addressobjectProject address. line1, line2, city, state, postal_code.
custom_fieldsobjectArbitrary key-value bag for org-defined metadata.
GET/v1/contacts

List contacts with filters and search.

bash
curl "https://api.estimatic.ai/v1/contacts?status=new&source=google_lsa&limit=50" \
  -H "Authorization: Bearer sk_live_..."
  • Query params: status (new|qualified|converted|lost|spam), source, q (search name/email/phone), created_after, created_before, limit (1–100, default 20), cursor.
GET/v1/contacts/{id}

Retrieve a contact by id, including any draft estimates linked to them.

PATCH/v1/contacts/{id}

Update a contact — most often used to change status or append notes.

json
{ "status": "qualified", "notes": "Spoke 5/6, scheduling site visit Tuesday." }
POST/v1/contacts/{id}/convertIdempotent

Promote a contact to a client. Returns the new client_id; the contact stays in place with status=converted.

json
{ "create_estimate": true, "estimate_prompt": "Master bath gut remodel, double vanity, heated floors." }
  • If create_estimate is true, an estimate is generated using the contact's scope_summary as the prompt fallback.
DELETE/v1/contacts/{id}

Soft-delete a contact. Restorable for 30 days.

Clients

The customer the estimate is for. A converted contact becomes a client. Estimates can also be drafted without a client and attached later.

POST/v1/clientsIdempotent

Create a client record.

json
{
  "name": "Sarah Greene",
  "email": "sarah@example.com",
  "phone": "+14045551212",
  "address": { "line1": "1428 Elm Street", "city": "Atlanta", "state": "GA", "postal_code": "30303" }
}

Request fields

FieldTypeDescription
namerequiredstringFull legal/billing name.
emailstringPrimary email. Used as the default recipient for sent estimates.
phonestringPrimary phone. E.164 preferred.
addressobjectBilling address: line1, line2, city, state, postal_code, country.
companystringOptional company name for B2B clients.
tax_exemptbooleanSkip tax on estimates for this client. Default false.
GET/v1/clients

List clients with search and filters.

bash
curl "https://api.estimatic.ai/v1/clients?q=greene&limit=20" -H "Authorization: Bearer sk_live_..."
  • Query params: q (search name/email/phone), created_after, created_before, limit (1–100, default 20), cursor.
GET/v1/clients/{id}

Retrieve a client by id.

PATCH/v1/clients/{id}

Update any client field. Partial — only send what's changing.

json
{ "phone": "+14045559999", "tax_exempt": true }
DELETE/v1/clients/{id}

Soft-delete a client. Linked estimates remain but become unattached.

GET/v1/clients/{id}/estimates

List every estimate for a client, newest first.

bash
curl "https://api.estimatic.ai/v1/clients/cli_01HZ/estimates?status=accepted" \
  -H "Authorization: Bearer sk_live_..."

Pricing profiles

Your cost book. Markup rules, labor burden, supplier preferences, and rounding. Every estimate uses one — defaults to the org default.

GET/v1/pricing-profiles

List pricing profiles for the org.

POST/v1/pricing-profiles

Create a new pricing profile.

json
{
  "name": "Premium remodels",
  "labor_markup": 1.45,
  "material_markup": 1.25,
  "preferred_suppliers": ["abc_supply", "qxo"],
  "rounding": "nearest_25"
}

Integrations

List the CRMs, FSMs, and accounting tools connected to the org. Use these slugs in /estimates/{id}/push.

GET/v1/integrations

List connected integrations.

json
{
  "object": "list",
  "data": [
    { "slug": "jobber",        "name": "Jobber",        "status": "connected" },
    { "slug": "servicetitan",  "name": "ServiceTitan",  "status": "connected" },
    { "slug": "housecall_pro", "name": "Housecall Pro", "status": "disconnected" },
    { "slug": "quickbooks",    "name": "QuickBooks",    "status": "connected" }
  ]
}

Webhooks

Get notified when estimates are created, updated, pushed, sent, viewed by your customer, accepted, or declined. Payloads are HMAC-SHA256 signed.

Event catalog

estimate.created — fires after a successful POST /v1/estimates. estimate.updated — any field change (incl. PATCH). estimate.refined — natural-language refinement applied. estimate.pushed — sent to a connected CRM/accounting tool. estimate.sent — delivered to the customer (email/SMS). estimate.viewed — customer opened the share link. estimate.accepted — customer signed/accepted. estimate.declined — customer declined.

Example payload — estimate.viewed

json
{
  "id": "evt_01JXYZ...",
  "type": "estimate.viewed",
  "created_at": "2026-05-06T14:22:08Z",
  "data": {
    "estimate_id": "est_01JXYZ...",
    "viewer_ip": "73.12.x.x",
    "user_agent": "Mozilla/5.0 ...",
    "first_view": true
  }
}

Example payload — estimate.accepted

json
{
  "id": "evt_01JXYZ...",
  "type": "estimate.accepted",
  "created_at": "2026-05-06T15:01:22Z",
  "data": {
    "estimate_id": "est_01JXYZ...",
    "selected_option": "better",
    "signer_name": "Sarah Greene",
    "signed_at": "2026-05-06T15:01:21Z"
  }
}

Verifying signatures (Node / Express)

typescript
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post(
  "/hooks/estimatic",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const sig = req.header("x-estimatic-signature") ?? "";
    const ts  = req.header("x-estimatic-timestamp") ?? "";
    const expected = crypto
      .createHmac("sha256", process.env.ESTIMATIC_WEBHOOK_SECRET!)
      .update(`${ts}.${req.body}`)
      .digest("hex");

    const ok =
      sig.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));

    if (!ok) return res.status(401).end();
    if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(401).end();

    const event = JSON.parse(req.body.toString("utf8"));
    // ... handle event ...
    res.status(200).end();
  }
);

Verifying signatures (Python / Flask)

python
import hmac, hashlib, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["ESTIMATIC_WEBHOOK_SECRET"].encode()

@app.post("/hooks/estimatic")
def hook():
    sig = request.headers.get("x-estimatic-signature", "")
    ts  = request.headers.get("x-estimatic-timestamp", "")
    body = request.get_data()  # raw bytes — do not parse first

    expected = hmac.new(SECRET, f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, expected):
        abort(401)
    if abs(time.time() - int(ts)) > 300:
        abort(401)

    event = request.get_json()
    # ... handle event ...
    return "", 200
POST/v1/webhooks

Subscribe a URL to one or more events.

json
{
  "url": "https://yourapp.com/hooks/estimatic",
  "events": ["estimate.created", "estimate.pushed", "estimate.viewed"],
  "description": "Sync estimates to internal pipeline"
}
  • Save the secret on creation — it's only shown once.
GET/v1/webhooks

List webhook subscriptions.

DELETE/v1/webhooks/{id}

Unsubscribe a webhook.

bash
curl -X DELETE https://api.estimatic.ai/v1/webhooks/wh_01 \
  -H "Authorization: Bearer sk_live_..."

MCP server

The Estimatic MCP server exposes the same operations as tools to any MCP-compatible AI client — Claude Desktop, Cursor, ChatGPT (with MCP), or your own agent. Authenticate with the same Bearer token as the REST API. One config block, your AI can now scope, price, and push real jobs.

Endpoint & transport

https://api.estimatic.ai/mcp — Streamable HTTP transport per the MCP spec. Every request must send Authorization: Bearer sk_live_... and Accept: application/json, text/event-stream. The server responds with either JSON or an SSE stream depending on the tool call.

Authentication

Same Bearer key as the REST API, scoped to one org. For multi-org partners, set X-Estimatic-Org on the MCP transport headers — your AI client must support custom headers (Claude Desktop, Cursor, and the OpenAI Agents SDK all do).

Tools — overview

create_estimate, list_estimates, get_estimate, refine_estimate, patch_estimate, archive_estimate, push_estimate, create_client, list_clients, list_integrations, list_pricing_profiles, upload_file. Arguments and return shapes mirror the REST endpoints above.

Tool — create_estimate

json
{
  "name": "create_estimate",
  "description": "Generate a fully-priced estimate from a natural-language scope and (optionally) attached files.",
  "inputSchema": {
    "type": "object",
    "required": ["prompt"],
    "properties": {
      "prompt": { "type": "string" },
      "trade": { "type": "string", "enum": ["remodel","roofing","fencing","electrical","plumbing","hvac","landscaping","general"] },
      "project_zip": { "type": "string", "pattern": "^[0-9]{5}$" },
      "client_id": { "type": "string" },
      "file_ids": { "type": "array", "items": { "type": "string" } },
      "options": {
        "type": "object",
        "properties": {
          "good_better_best": { "type": "boolean", "default": true },
          "upsells": { "type": "boolean", "default": false },
          "local_permits": { "type": "boolean", "default": true }
        }
      }
    }
  }
}

Tool — refine_estimate

json
{
  "name": "refine_estimate",
  "description": "Apply a natural-language change to an existing estimate and re-price.",
  "inputSchema": {
    "type": "object",
    "required": ["estimate_id", "message"],
    "properties": {
      "estimate_id": { "type": "string" },
      "message":     { "type": "string" }
    }
  }
}

Tool — push_estimate

json
{
  "name": "push_estimate",
  "description": "Push a finished estimate into a connected CRM or accounting system.",
  "inputSchema": {
    "type": "object",
    "required": ["estimate_id", "integration"],
    "properties": {
      "estimate_id": { "type": "string" },
      "integration": { "type": "string", "enum": ["jobber","servicetitan","housecall_pro","quickbooks"] },
      "target":      { "type": "string", "enum": ["quote","estimate","invoice"], "default": "quote" },
      "client_id":   { "type": "string" }
    }
  }
}

Setup — Claude Desktop

typescript
// ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "estimatic": {
      "url": "https://api.estimatic.ai/mcp",
      "headers": {
        "Authorization": "Bearer sk_live_..."
      }
    }
  }
}

Setup — Cursor / VS Code

typescript
// .cursor/mcp.json (or VS Code MCP settings)
{
  "mcpServers": {
    "estimatic": {
      "url": "https://api.estimatic.ai/mcp",
      "headers": { "Authorization": "Bearer sk_live_..." }
    }
  }
}

Setup — ChatGPT (Custom Connector)

In ChatGPT → Settings → Connectors → Add custom connector. Name: Estimatic. URL: https://api.estimatic.ai/mcp. Auth: Bearer token, paste your sk_live_ key. Save, then enable it in any chat. Ask the model: 'Use the estimatic connector to draft a roof replacement estimate for ZIP 30303, 30sq architectural shingles.'

Setup — OpenAI Agents SDK (Python)

typescript
# pip install openai-agents
from agents import Agent, Runner
from agents.mcp import MCPServerStreamableHttp

estimatic = MCPServerStreamableHttp(
    name="estimatic",
    params={
        "url": "https://api.estimatic.ai/mcp",
        "headers": {"Authorization": f"Bearer {os.environ['ESTIMATIC_KEY']}"},
    },
)

agent = Agent(
    name="EstimatorBot",
    instructions="You draft contractor estimates. Always call estimatic.create_estimate.",
    mcp_servers=[estimatic],
)

result = Runner.run_sync(agent, "Quote a 24 LF cedar privacy fence in ZIP 30303.")
print(result.final_output)

Prompting tips

1) Be explicit about the tool: 'Use estimatic.create_estimate to scope this bath remodel...' — vague prompts often skip the tool entirely. 2) Always pass project_zip — without it, pricing falls back to org default and accuracy drops 5–15%. 3) Chain refinements: 'Now use estimatic.refine_estimate to add a permit line for the city of Atlanta.' rather than re-creating from scratch.

Changelog

Stability promise

Additive changes (new fields, new endpoints, new event types) are non-breaking and ship continuously to /v1/. Breaking changes ship behind /v2/ — never as a quiet change to /v1/. Deprecations get a 90-day email notice to every key holder.

v1.0 — public beta (target Q3 2026)

First stable release of /v1/estimates, /v1/clients, /v1/files, /v1/pricing-profiles, /v1/integrations, /v1/webhooks, and the MCP server.

Pre-beta

Private partners get early access via direct integration with our team. Endpoints are stable but breaking changes are still possible — we'll email every key holder before any.

Ready to build?

Grab a key, fire your first request, and tell us what you're shipping. We read every email at developers@estimatic.ai.