Skip to content

    Partner API · v1 pilot

    Build on the moving stack.

    Create leads, deliver the AI video survey, and (private beta) collect deposits, from your own website, CRM, no-code tools, or AI voice agent.

    Base URLhttps://dkieategdogjcrtqhkbv.supabase.co/functions/v1
    openapi.yamlCreate an API key

    Introduction

    All requests are JSON over HTTPS, scoped to the partner that owns the API key. The pilot runs on the raw functions host; logical paths like POST /v1/leads map to the function slugs shown below. A pretty api.moveblueprint.com host is planned.

    Authentication

    Pass your partner API key as a bearer token. Create or revoke keys in the dashboard under Settings → Developers. Use server-side only; never expose a key in a browser.

    http
    Authorization: Bearer mbk_live_…
    Content-Type: application/json

    Rate limits

    120 requests per minute per key. Every response includes X-RateLimit-Remaining. Bursting over the limit returns 429 with a JSON error body.

    Idempotency

    Pass an Idempotency-Key header on any POST. Replaying the same key returns the original response. Replaying with a different body returns 422.

    http
    Idempotency-Key: 7f3c8a92-2b40-4e1d-9d6b-9a3f01a5c8e1

    Errors

    Errors return a consistent shape. Status codes follow HTTP semantics: 401 auth, 404 not found, 409 conflict, 422 validation, 429 rate limit, 502 upstream.

    json
    {
      "error": {
        "code": "validation_failed",
        "message": "phone must be E.164",
        "details": { "field": "phone" }
      }
    }

    Webhooks

    Subscribe to lifecycle events so you don't poll: lead.created, lead.updated, lead.booked, survey.completed, deposit.paid, quote.signed. Every delivery is signed with HMAC SHA-256 using your endpoint's signing secret, and carries the event id so you can dedupe.

    Bodies carry identifiers only by default, so a mis-registered endpoint leaks nothing. If you would rather receive the survey itself than fetch it, ask us to switch on full-payload delivery for your endpoint: survey.completed then arrives carrying the same body as GET /v1/leads/{id}/survey below. Nothing else changes, and you can still call the endpoint whenever you need to reconcile.

    Reference

    Endpoints

    POST/v1/leads

    Create a lead

    Provide at least one of customer_name, email, or phone. If from_address, to_address, home_size AND email are all present, an instant estimate is computed and returned. A lead is always created.

    Pilot path: /v1-leads

    Parameters

    customer_namestringbody

    Customer full name (or use `name`).

    emailstringbody

    Required for instant estimate.

    phonestringbody

    E.164 preferred.

    from_addressstringbody

    Origin street address.

    to_addressstringbody

    Destination street address.

    home_sizestringbody

    e.g. "2 bedroom".

    move_datedatebody

    ISO 8601 date.

    special_itemsstring[]body

    Piano, safe, gym, etc.

    sourcestringbody

    Your channel tag, e.g. "website".

    Idempotency-Keystringheader

    Safe retries.

    Request

    curl
    curl -X POST https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-leads \
      -H "Authorization: Bearer mbk_live_…" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: 7f3c…" \
      -d '{
        "customer_name": "Jane Doe",
        "email": "jane@example.com",
        "phone": "+15555550123",
        "from_address": "123 Ocean Dr, Miami, FL",
        "to_address": "55 Peachtree St, Atlanta, GA",
        "home_size": "2 bedroom",
        "move_date": "2026-08-15"
      }'

    Response · 200

    json
    {
      "lead_id": "8b2f4d6a-…",
      "status": "new",
      "estimate": { "min": 1840, "max": 2310, "currency": "usd" }
    }
    GET/v1/leads

    List leads (delta sync)

    Page forward through leads ordered by (updated_at, id) ascending. Use updated_since for delta sync ("what changed since X") and pass next_cursor back verbatim to get the next page.

    Pilot path: /v1-leads-list

    Parameters

    limitintegerquery

    Default 25, max 100.

    updated_sincedate-timequery

    Only leads with updated_at ≥ this.

    cursorstringquery

    Opaque cursor from previous page.

    Request

    curl
    curl https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-leads-list?limit=25&updated_since=2026-06-20T00:00:00Z \
      -H "Authorization: Bearer mbk_live_…"

    Response · 200

    json
    {
      "data": [
        {
          "lead_id": "8b2f…",
          "status": "surveying",
          "customer_name": "Jane Doe",
          "estimate": { "min": 1840, "max": 2310, "currency": "usd" },
          "created_at": "2026-06-20T14:02:11Z",
          "updated_at": "2026-06-21T09:18:44Z"
        }
      ],
      "next_cursor": "eyJ1cGRhdGVkX2F0Ijoi…",
      "has_more": true
    }
    GET/v1/leads/{id}

    Get one lead

    Returns the full lifecycle for a single lead: survey state, quote, deposit, and recommended estimate.

    Pilot path: /v1-lead-get/{lead_id}

    Parameters

    lead_iduuidpathrequired

    Lead identifier.

    Request

    curl
    curl https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-lead-get/8b2f4d6a-… \
      -H "Authorization: Bearer mbk_live_…"

    Response · 200

    json
    {
      "lead_id": "8b2f…",
      "status": "quoted",
      "survey": { "survey_id": "sv_…", "status": "inventory_ready" },
      "quote":  { "quote_id": "q_…", "stage": "sent", "accepted_at": null, "signed_at": null },
      "deposit": { "status": "none" },
      "recommended_estimate": { "min": 1840, "max": 2310, "currency": "usd" }
    }
    GET/v1/leads/{id}/survey

    Get the finished survey

    The completed walkthrough: every item grouped by room, cubic feet, weight, the carton estimate, who is packing, and access conditions. Only read totals and rooms once status is "reviewed"; while processing, items are still being written. Carries no price, so you can rate the shipment against your own tables. Carries no video URL either.

    Pilot path: /v1-lead-survey-get/{lead_id}

    Parameters

    lead_iduuidpathrequired

    Lead identifier.

    Authorizationstringheaderrequired

    Requires the surveys:read scope.

    Request

    curl
    curl https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-lead-survey-get/8b2f4d6a-… \
      -H "Authorization: Bearer mbk_live_…"

    Response · 200

    json
    {
      "survey_id": "a7f18eda-…",
      "lead_id": "2da5f037-…",
      "survey_type": "async_walkthrough",
      "status": "reviewed",
      "jurisdiction": { "move_type": "intrastate", "origin_state": "FL", "destination_state": "FL" },
      "totals": {
        "item_count": 34,
        "items_cuft": 600.5, "items_lbs": 2423,
        "packed_box_count": 3, "packed_box_cuft": 9, "packed_box_lbs": 120,
        "carton_count": 59, "carton_cuft": 243.3, "carton_lbs": 2160,
        "total_cuft": 843.8, "total_lbs": 4583
      },
      "rooms": [
        { "room": "Master Bedroom", "items": [
          { "name": "Dresser 6 Drawer", "quantity": 1,
            "volume_cuft": 42, "weight_lbs": 175, "disposition": "standalone", "notes": null },
          { "name": "Soundbar Standard", "quantity": 1,
            "volume_cuft": 2, "weight_lbs": 10, "disposition": "box_contents", "notes": null }
        ]}
      ],
      "cartons": {
        "cuft": 243.3, "lbs": 2160,
        "skus": [
          { "sku": "dish_pack", "label": "Dish Pack", "quantity": 15,
            "cubic_feet": 5.01, "weight_lbs": 47.37, "is_specialty": false,
            "dimensions_in": "18 x 18 x 28 (double-walled)", "max_weight_lbs": 60 },
          { "sku": "mattress_queen", "label": "Mattress carton (queen)", "quantity": 1,
            "cubic_feet": 0, "weight_lbs": 0, "is_specialty": true,
            "dimensions_in": "76 x 9 x 82", "max_weight_lbs": 60 }
        ]
      },
      "packing": { "choice": "full", "customer_progress": "not_started" },
      "access": {
        "origin": { "floor": 2, "elevator": false, "stairs": true },
        "destination": { "floor": 1, "elevator": true },
        "long_carry_feet": 75,
        "notes": "no stairs or long walks"
      },
      "special_items": [],
      "flags": {
        "needs_review": false,
        "low_confidence_item_count": 3,
        "unidentified_item_count": 0,
        "coverage_warnings": []
      },
      "video": { "available": true, "clip_count": 1 }
    }
    
    // Three things that decide whether your numbers come out right:
    //
    // 1. disposition rules the line. "box_contents" sits inside a carton that is already
    //    counted and contributes nothing. A rifle in a case is box contents, never a priced
    //    line. "not_moving" stays with the house. Only "standalone" and "pre_packed_box"
    //    reach totals.
    //
    // 2. A specialty carton publishes zero cube and zero weight on purpose. It is a material
    //    to bring; the mattress or mirror inside it is already an inventory item. Adding its
    //    cube counts the shipment twice.
    //
    // 3. Never turn cubic feet into pounds. Seven pounds per cubic foot is a whole-shipment
    //    rule of thumb, not a per-item conversion. The shipment above runs 5.4. Every item
    //    carries its own weight; use it.
    //
    // Item cube and weight are PER UNIT: multiply by quantity yourself. carton_cuft and
    // carton_lbs are already reduced by any boxes the customer packed, so do not subtract
    // twice. Room labels are not stable across reprocessing; key on survey_id plus the item,
    // never on the room string. packing.choice of null means nobody answered, not "no
    // packing needed".
    POST/v1/surveys/{id}/video-access

    Create a video link you can forward

    Mints a link to the walkthrough video that anyone can open, with no MoveBlueprint account. Built for handing the recording to the carrier who will actually haul the shipment. The link expires, you can revoke it, and every open is recorded. Takes a survey_id, not a lead_id: read it from the finished survey above.

    Pilot path: /v1-survey-video-access/{survey_id}

    Parameters

    survey_iduuidpathrequired

    Survey identifier, from survey_id on the finished survey.

    Authorizationstringheaderrequired

    Requires the surveys:read scope. Keys issued before this endpoint existed carry the scopes they were issued with, so an older key may need re-issuing.

    ttl_hoursnumberbody

    How long the link lives. Defaults to 72. Anything above 168 (7 days) is capped at 168.

    labelstringbody

    Your own note for the audit trail, for example the carrier name. Truncated at 120 characters.

    Request

    curl
    curl -X POST https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-survey-video-access/a7f18eda-… \
      -H "Authorization: Bearer mbk_live_…" \
      -H "Content-Type: application/json" \
      -d '{ "ttl_hours": 72, "label": "Atlas Van Lines, load 4471" }'

    Response · 200

    json
    {
      "survey_id": "a7f18eda-…",
      "grant_id": "e524e5f1-…",
      "view_url": "https://pro.moveblueprint.com/survey-video/mbv_9c31…",
      "expires_at": "2026-08-29T14:12:07.412Z",
      "clip_count": 1,
      "forwardable": true,
      "note": "This link may be forwarded to the assigned carrier. Every open is logged. Revoke with DELETE on this endpoint."
    }
    
    // Five things worth knowing before you send one of these:
    //
    // 1. view_url is shown ONCE. Only a hash of it is stored, so nothing can recover it
    //    later, not even us. Save it when you get it, or mint another.
    //
    // 2. It is forwardable on purpose. Whoever holds the URL can watch, and that is the
    //    point: the carrier has no account with us and never will. Treat the link the way
    //    you would treat the video itself.
    //
    // 3. Revoking stops new opens right away, but a video address already handed out keeps
    //    playing for up to ten minutes. Revoke is not a kill switch, so do not describe it
    //    to a customer as one.
    //
    // 4. A link is limited to 20 opens per hour. Past that it answers 429 and tells the
    //    viewer to wait a few minutes. A person watching a walkthrough will never reach it.
    //
    // 5. The open log proves a link was opened, at a time, from roughly one address. It is
    //    not a count of who watched or for how long: once the video is playing, replays and
    //    seeking add nothing to it.
    //
    // clip_count is the number of clips we can actually serve. A 422 with "this survey has
    // no playable video clips" means the recording is not there, not that your call was
    // wrong. A survey id belonging to another company answers 404, the same as one that does
    // not exist.
    DELETE/v1/surveys/{id}/video-access

    Revoke every video link on a survey

    Closes every live link on that survey at once. There is no way to revoke one link and keep another, by design: if a recording needs pulling back you almost never want a second copy of it still open somewhere.

    Pilot path: /v1-survey-video-access/{survey_id}

    Parameters

    survey_iduuidpathrequired

    Survey identifier.

    Authorizationstringheaderrequired

    Requires the surveys:read scope.

    Request

    curl
    curl -X DELETE https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-survey-video-access/a7f18eda-… \
      -H "Authorization: Bearer mbk_live_…"

    Response · 200

    json
    {
      "survey_id": "a7f18eda-…",
      "revoked": true,
      "grants_revoked": 2
    }
    
    // grants_revoked counts the links that were still live. Zero means there was nothing
    // left to close, which is a success, not an error. Calling it twice is safe.
    //
    // A video address someone already loaded keeps playing for up to ten minutes after this
    // returns. If the recording has to be out of reach immediately, revoking is not enough
    // on its own.
    POST/v1/leads/{id}/acceptPrivate beta

    Record acceptance

    Records the customer's acceptance: terms, timestamp and IP. Where the estimate type calls for a signature, signature fields are required and ESIGN/UETA evidence is recorded. Never books; booking happens after a paid deposit.

    Pilot path: /v1-accept/{lead_id}

    Parameters

    lead_iduuidpathrequired

    Lead identifier.

    accepter_emailstringbody

    Email of the accepting party.

    signer_namestringbody

    Signature flow only.

    signature_image_b64stringbody

    Signature flow only.

    esign_consent_checkedbooleanbody

    Signature flow only.

    Request

    curl
    curl -X POST https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-accept/8b2f4d6a-… \
      -H "Authorization: Bearer mbk_live_…" \
      -H "Content-Type: application/json" \
      -d '{ "accepter_email": "jane@example.com" }'

    Response · 200

    json
    { "status": "accepted", "deposit_required": true }
    POST/v1/depositsPrivate beta

    Create a deposit checkout

    Creates a Stripe Checkout URL that settles to the PARTNER’s connected account (merchant-of-record, 0% platform fee). Requires carrier identity + a connected Stripe account.

    Pilot path: /v1-deposits

    Parameters

    lead_iduuidbodyrequired

    Lead identifier.

    quote_share_link_iduuidbodyrequired

    Share link being accepted.

    amount_centsintegerbody

    Defaults to partner-configured deposit. 100-500000.

    Request

    curl
    curl -X POST https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-deposits \
      -H "Authorization: Bearer mbk_live_…" \
      -H "Content-Type: application/json" \
      -d '{
        "lead_id": "8b2f…",
        "quote_share_link_id": "qsl_…",
        "amount_cents": 25000
      }'

    Response · 200

    json
    {
      "deposit_id": "dep_…",
      "checkout_url": "https://checkout.stripe.com/c/pay/cs_…",
      "amount_cents": 25000,
      "status": "checkout_open"
    }
    GET/v1/deposits/{id}Private beta

    Poll deposit status

    Returns the current status of a deposit: pending, paid, failed, refunded, or disputed.

    Pilot path: /v1-deposit-status/{deposit_id}

    Parameters

    deposit_iduuidpathrequired

    Deposit identifier.

    Request

    curl
    curl https://dkieategdogjcrtqhkbv.supabase.co/functions/v1/v1-deposit-status/dep_… \
      -H "Authorization: Bearer mbk_live_…"

    Response · 200

    json
    {
      "deposit_id": "dep_…",
      "status": "paid",
      "amount_cents": 25000,
      "currency": "usd",
      "checkout_url": null,
      "paid_at": "2026-06-22T17:04:00Z",
      "lead_id": "8b2f…"
    }