Skip to content
blnd

API reference · v1

Cases in. Estimates out.

Push assignments into blnd from your own systems and take the finished estimate back: the CCC-importable .xme, with a signed webhook the moment it's ready.

  • REST
  • JSON
  • Webhooks
  • OpenAPI 3.1
your-claims-system ⇄ blnd.one/api/v1
  1. POST/v1/casescase created201
  2. POST/v1/cases/:id/estimate12 photos queued202
  3. HOOKestimate.readysigned · webhook-id evt_8c1f…2xx
  4. GET/v1/cases/:id/files/:file_idC26006608.xme200

Introduction

The blnd API is a small REST API over HTTPS. Requests and responses are JSON (photo uploads are multipart/form-data), every call is authenticated with a bearer key, and blnd calls you back with signed webhooks when something changes.

Every case in the workspace is visible to the API, whatever its source: portal syncs, email and the API itself. With it you can:

  • Connect a claims system. Push desk-review assignments in as cases, route them to a reviewer, and get the estimate back without anyone re-keying it.
  • Automate your office. Mirror blnd's inbox into your own tools, close cases when they're billed, keep your own ids in metadata.
  • Bring in other sources. Create cases from a portal or a feed blnd doesn't sync itself.

Start with a test key: it works at once, only touches sandbox data, and is never billed. The OpenAPI 3.1 description covers everything on this page, for code generators and API tools.

Base URL
https://blnd.one/api/v1
GET/v1
curl https://blnd.one/api/v1
200Response
{
  "object": "api",
  "version": "2026-10-01",
  "docs": "https://blnd.one/docs/api"
}

Quickstart

From a fresh key to a downloaded estimate in five calls. Everything here runs in test mode, on the free demo engine.

1Step 1

Make a test key

A workspace owner or admin creates keys in Settings > Developers > Keys. Pick Test, give it a name and scopes, and copy it: it's shown once. Keep it in an environment variable, then call GET /me to check it.

Shell
export BLND_API_KEY=blnd_test_…
GET/v1/me
curl https://blnd.one/api/v1/me \
  -H "Authorization: Bearer $BLND_API_KEY"
2Step 2

Create a case

Send what you know about the assignment; every field is optional. Use your own id as external_id so a retry can't create a twin, and add an Idempotency-Key for safe retries. The case shows in the blnd inbox (behind the Test filter) and the answer is the case object.

POST/v1/cases
curl https://blnd.one/api/v1/cases \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -H "Idempotency-Key: a8098c1a-f86e-41d8-9d0c-3c0b8f4a1e2d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CLM-88213-01",
    "claim_number": "C26006608",
    "carrier": "Sedgwick",
    "assignment_type": "DR",
    "insured_name": "Dana Lee",
    "vehicle": {
      "year": 2021,
      "make": "Toyota",
      "model": "Camry",
      "vin": "4T1G11AK5MU123456"
    },
    "loss": {
      "type": "Collision",
      "date": "2026-10-02",
      "description": "Front-end, driver side"
    },
    "inspection_address": "12 Main St, Austin, TX 78701",
    "deductible": "500",
    "owner": {
      "phone": "+15125550100",
      "email": "dana@example.com"
    },
    "adjuster": {
      "name": "Sam Ortiz",
      "email": "sam@carrier.com"
    },
    "due_at": "2026-10-20T17:00:00Z",
    "metadata": {
      "claims_system_id": "88213"
    }
  }'
3Step 3

Upload photos and start the estimate

Post 1 to 64 damage photos (JPEG, PNG or WebP, up to 15 MB each) as multipart/form-data, one photos part per file. blnd answers 202 straight away with the estimate queued, and writes it in the background.

POST/v1/cases/{id}/estimate
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/estimate \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -H "Idempotency-Key: a8098c1a-f86e-41d8-9d0c-3c0b8f4a1e2d" \
  -F "photos=@front.jpg;type=image/jpeg" \
  -F "photos=@driver-side.jpg;type=image/jpeg" \
  -F vin=4T1G11AK5MU123456
4Step 4

Wait for estimate.ready, or poll

Add a webhook endpoint listening to estimate.ready and estimate.failed: blnd posts the estimate, files included, the moment it settles. No public URL yet? Poll GET /cases/{id}/estimate every few seconds until status is ready or failed.

Webhook · estimate.ready
{
  "id": "evt_8c1f2e7d4a9b3c6e0f5a1d2b",
  "object": "event",
  "type": "estimate.ready",
  "livemode": false,
  "created_at": "2026-10-18T14:06:40.415Z",
  "data": {
    "object": {
      "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
      "object": "estimate",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "status": "ready",
      "files": [
        {
          "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
          "kind": "xme",
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b"
        },
        {
          "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
          "kind": "xme",
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c"
        }
      ]
    }
  }
}
GET/v1/cases/{id}/estimate
# Every few seconds, until status is "ready" or "failed"
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/estimate \
  -H "Authorization: Bearer $BLND_API_KEY"
5Step 5

Download the .xme and .AWF

Each entry in the estimate's files has a download_url. Fetch it with your key (scope files:read) to get the bytes: the CCC-importable .xme and .AWF.

GET/v1/cases/{id}/files/{file_id}
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -o C26006608.xme

Authentication

Send your key on every call as Authorization: Bearer <key>. Keys are long random strings, shown once when they're made; blnd stores only a hash. Treat them like passwords: server-side only, never in a browser or an app.

PrefixModeWorks
blnd_test_…TestOnce the workspace is in the Developer Program. Only sees and creates sandbox cases.
blnd_live_…LiveOnce the workspace is in the Developer Program. Real cases, real estimates.

The Developer Program

The API is open to approved workspaces only. An owner or admin applies in Settings > Developers: who you are, what you're building and roughly how many cases a month. A person on the blnd team reviews every application, usually within two business days, and emails the answer. Until then no key can be created, and a key of a workspace that isn't approved gets 403 developer_program_required. Once you're in, where the workspace stands is on GET /me as api_access.

api_accessShown asWhat it means
noneNot appliedNot applied yet. No key can be created and none works.
pendingUnder reviewApplication submitted; a person on the blnd team is reviewing it. Keys open once it's approved.
approvedApprovedIn the program: test and live keys work, and webhook endpoints can be enabled.
rejectedNot approvedNot approved. Keys don't work; you can apply again from Settings > Developers.
suspendedSuspendedEvery key and webhook is paused, test keys too. Contact support@blnd.one.

Scopes

Each key carries the scopes it was given, or * for full access. A call outside them is a 403 missing_scope. Give each integration only what it needs.

ScopeAllows
cases:readRead cases, their timeline and status
cases:writeCreate and update cases
estimates:writeUpload photos and start estimates
files:readDownload estimate files (.xme, .AWF)

Rolling a key

Rolling makes a new key with the same name and scopes. Choose how long the old one keeps working so a deploy can switch over without downtime: 0 h (it stops now), 1 h, 24 h or 7 days. Revoking stops a key at once.

Request
GET /api/v1/me HTTP/1.1
Host: blnd.one
Authorization: Bearer blnd_live_…
401Without a key
{
  "error": {
    "type": "authentication_error",
    "code": "missing_api_key",
    "message": "No API key. Send it as `Authorization: Bearer blnd_live_…`.",
    "doc_url": "https://blnd.one/docs/api#errors",
    "request_id": "req_5f1c0e9a7b3d2c4e6a8b0d1f"
  }
}

Errors

Success is a 2xx. Anything else carries one error object: a broad type, a stable code to branch on, a message written for a person (don't parse it), the param at fault when there is one, and the request_id to quote to support.

A test key never sees live cases and a live key never sees sandbox ones: an id from the other mode is a 404 resource_missing.

HTTPTypeCodeMeaning
400invalid_request_errorparameter_missingA required parameter wasn't sent. param names it.
parameter_invalidA parameter has the wrong type or value. param names it.
invalid_jsonThe request body isn't valid JSON.
401authentication_errormissing_api_keyNo Authorization: Bearer header.
invalid_api_keyNot a blnd key, or the key was revoked or has expired.
403permission_errormissing_scopeThe key doesn't have the scope this endpoint needs.
developer_program_requiredThe key's workspace isn't approved for the Developer Program (not applied, under review, or not approved).
api_access_suspendedAPI access for the workspace is suspended. Test keys stop too.
404invalid_request_errorresource_missingNo such object, it belongs to the other mode (test keys only see sandbox cases), or a file is missing from storage.
no_estimateThe case has no estimate yet.
409conflict_errorcase_existsA case with this external_id already exists in the workspace (in test or live).
estimate_refusedThe estimate can't be started for this case.
estimate_in_progressAn estimate is already running for this case.
idempotency_key_in_useA request with this Idempotency-Key is still running. Retry shortly.
411invalid_request_errorlength_requiredA photo upload was sent without a Content-Length header.
413invalid_request_errorpayload_too_largeA photo is over 15 MB, the upload is over 250 MB in all (25 MB with a test key), or a JSON body is over 1 MB.
422invalid_request_erroridempotency_key_mismatchThis Idempotency-Key was used with a different request.
429rate_limit_errorrate_limitedToo many requests this minute. Wait Retry-After seconds.
500api_errorserver_errorSomething went wrong on blnd's side. Retry; quote the request_id to support.
400Error
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "vehicle.vin: must be 17 characters, A-Z and 0-9 without I, O or Q",
    "param": "vehicle.vin",
    "doc_url": "https://blnd.one/docs/api#errors",
    "request_id": "req_5f1c0e9a7b3d2c4e6a8b0d1f"
  }
}

Idempotency

Network calls fail halfway. Send an Idempotency-Key header (any unique string up to 255 characters; a UUID is ideal) on POST /cases and POST /cases/{id}/estimate, and a retry can never create a second case or a second estimate.

  • The first answer is kept for 24 hours and replayed for the same key, with Idempotent-Replayed: true. Refusals (other 4xx answers) are replayed too.
  • 409 and 429 answers and 5xx errors are not kept: retry with the same key and it runs again.
  • The same key with a different body is a 422 idempotency_key_mismatch. Use a new key for a new request.
  • A key whose first request is still running answers 409 idempotency_key_in_use. Retry shortly.

Keys are scoped to the workspace and the mode. GET and PATCH are safe to repeat as they are.

A retried create
POST /api/v1/cases HTTP/1.1
Authorization: Bearer blnd_test_…
Idempotency-Key: a8098c1a-f86e-41d8-9d0c-3c0b8f4a1e2d

HTTP/1.1 201 Created
Idempotent-Replayed: true
Request-Id: req_9e2b7c41d0a35f68be19c2d4

Pagination

Lists come newest first, in pages. Pass limit (1 to 100, default 25) and, for the next page, starting_after set to the previous page's next_cursor: the id of its last object. has_more says whether another page follows; next_cursor is null on the last one.

Paged lists: cases and a case's timeline. Team members and a case's files always fit on one page. For an incremental sync, filter cases with updated_since.

GET/v1/cases
curl -G https://blnd.one/api/v1/cases \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -d limit=100 \
  -d starting_after=7c9e6679-7425-40de-944b-e07fc1f90ae7
200A page
{
  "object": "list",
  "data": [
    "…"
  ],
  "has_more": true,
  "next_cursor": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}

Rate limits

Each key has a budget of requests per minute: 120 for live keys and 30 for test keys, unless your workspace has its own limit. GET /me shows the one that applies as rate_limit.

Every keyed answer reports the budget in headers. Past it, the answer is 429 rate_limited with Retry-After in seconds: wait that long, then carry on.

HeaderMeaning
RateLimit-PolicyThe key's quota per window, e.g. "default";q=120;w=60.
RateLimitWhat's left and when the window resets, e.g. "default";r=119;t=60.
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left this minute.
X-RateLimit-ResetUnix time (seconds) the window resets.
Retry-AfterOn a 429 only: seconds to wait.
Over the limit
HTTP/1.1 429 Too Many Requests
Retry-After: 17
RateLimit-Policy: "default";q=120;w=60
RateLimit: "default";r=0;t=17
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1792332417

Versioning

This reference describes version 2026-10-01. You don't send a version: every response says which one answered in Blnd-Version. Read objects tolerantly, ignoring fields and enum values you don't recognise.

Every response also carries a Request-Id (req_…), repeated in error bodies. Log it; quoting it lets support find the exact call.

Response headerMeaning
Request-Idreq_…, unique to this request. Quote it to support; it's also in every error body.
Blnd-VersionThe API version that answered: 2026-10-01.
RateLimit-PolicyThe key's quota per window, e.g. "default";q=120;w=60.
RateLimitWhat's left and when the window resets, e.g. "default";r=119;t=60.
X-RateLimit-LimitRequests allowed per minute.
X-RateLimit-RemainingRequests left this minute.
X-RateLimit-ResetUnix time (seconds) the window resets.
Retry-AfterOn a 429 only: seconds to wait.
Idempotent-Replayedtrue when the answer is a replay of an earlier request with the same Idempotency-Key.
Every response
HTTP/1.1 200 OK
Content-Type: application/json
Request-Id: req_5f1c0e9a7b3d2c4e6a8b0d1f
Blnd-Version: 2026-10-01
Cache-Control: no-store

Reference

Objects

Every object has an `id` and an `object` field naming its kind. Timestamps are ISO 8601 in UTC; fields with no value are `null`, never missing.

The case object

One assignment: a vehicle, a claim and the people around it. Cases arrive from a claims portal sync, from an email, or from your own POST /cases. Every case in a workspace is visible to the API, whatever its source.

  • idstring

    Unique identifier for the case.

  • objectstring

    Always case.

  • livemodeboolean

    true for real cases; false for sandbox cases made with a test key.

  • sourceenum

    Where the case came from.

    apiportalemail
  • portalstringnullable

    The portal the case was synced from, e.g. vista. null for API and email cases.

  • external_idstringnullable

    Your id for an API case, or the portal's own id for a portal case. null for email cases.

  • statusenum

    Where the case stands in blnd.

    newin_progressdraft_readyuploadedclosed
  • claim_numberstringnullable

    The carrier's claim number.

  • file_numberstringnullable

    The portal's file number, when it differs from the claim number.

  • policy_numberstringnullable

    The insured's policy number.

  • carrierstringnullable

    The insurance carrier.

  • assignment_typestringnullable

    The portal's or your code for the kind of work: DR, SDR, AO, QE, TL and so on.

  • insured_namestringnullable

    The insured's name.

  • vehicleobject

    The vehicle.

    Child attributes(4)
    • yearintegernullable

      Model year.

    • makestringnullable

      Make, e.g. Toyota.

    • modelstringnullable

      Model, e.g. Camry.

    • vinstringnullable

      The 17-character VIN.

  • lossobject

    What happened.

    Child attributes(3)
    • typestringnullable

      Kind of loss, e.g. Collision.

    • datedatenullable

      Date of loss, YYYY-MM-DD.

    • descriptionstringnullable

      The loss description or point of impact.

  • inspection_addressstringnullable

    Where the vehicle can be inspected.

  • deductiblestringnullable

    The deductible, as written on the assignment (a string, e.g. "500").

  • ownerobject

    How to reach the vehicle's owner.

    Child attributes(2)
    • phonestringnullable

      Phone number.

    • emailstringnullable

      Email address.

  • adjusterobject

    The carrier's adjuster.

    Child attributes(3)
    • namestringnullable

      Name.

    • phonestringnullable

      Phone number.

    • emailstringnullable

      Email address.

  • assignee_idstringnullable

    The team member working the case. GET /members lists the ids.

  • due_attimestampnullable

    When the work is due.

  • assigned_attimestampnullable

    When the case was assigned, as the portal reports it.

  • appointment_attimestampnullable

    The inspection appointment, when one is set.

  • metadataobject

    Your own key/value pairs. Never read by blnd.

  • urlstring

    The case in the blnd app.

  • created_attimestamp

    When the case was created in blnd.

  • updated_attimestamp

    When anything on the case last changed.

case object
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "case",
  "livemode": false,
  "source": "api",
  "portal": null,
  "external_id": "CLM-88213-01",
  "status": "new",
  "claim_number": "C26006608",
  "file_number": null,
  "policy_number": null,
  "carrier": "Sedgwick",
  "assignment_type": "DR",
  "insured_name": "Dana Lee",
  "vehicle": {
    "year": 2021,
    "make": "Toyota",
    "model": "Camry",
    "vin": "4T1G11AK5MU123456"
  },
  "loss": {
    "type": "Collision",
    "date": "2026-10-02",
    "description": "Front-end, driver side"
  },
  "inspection_address": "12 Main St, Austin, TX 78701",
  "deductible": "500",
  "owner": {
    "phone": "+15125550100",
    "email": "dana@example.com"
  },
  "adjuster": {
    "name": "Sam Ortiz",
    "phone": null,
    "email": "sam@carrier.com"
  },
  "assignee_id": null,
  "due_at": "2026-10-20T17:00:00.000Z",
  "assigned_at": "2026-10-18T14:02:11.482Z",
  "appointment_at": null,
  "metadata": {
    "claims_system_id": "88213"
  },
  "url": "https://blnd.one/app/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "created_at": "2026-10-18T14:02:11.482Z",
  "updated_at": "2026-10-18T14:02:11.482Z"
}

The estimate object

One estimate run for a case: from photos to a written estimate file. A new estimate replaces the case's previous one. Poll it with GET /cases/{id}/estimate or listen for the estimate.* webhooks.

  • idstring

    Unique identifier for the estimate.

  • objectstring

    Always estimate.

  • case_idstring

    The case this estimate belongs to.

  • statusenum

    Where the estimate stands. Branch on this field.

    awaiting_photosqueuedprocessingreadyfailed
  • stageenum

    The finer-grained internal step behind status. Useful for display; may gain values.

    awaiting_photosqueueddraftingpricingbuildingreadyfailed
  • stepstringnullable

    Progress in words while it runs, e.g. Reading the photos.

  • errorstringnullable

    Why the estimate failed, when status is failed.

  • photo_countinteger

    How many photos the estimate was started with.

  • vinstringnullable

    The VIN the estimate was written for.

  • totalsobjectnullable

    The estimate's money totals, once it is ready. null until then. Keys inside are camelCase.

    Child attributes(7)
    • categoriesarray of objects

      One row per category (parts, labor kinds, materials).

      Child attributes(4)
      • labelstring

        Category name, e.g. Body Labor.

      • hoursnumberoptional

        Labor hours, for labor categories.

      • ratenumberoptional

        Hourly rate, for labor categories.

      • costnumber

        The category's total, in dollars.

    • subtotalnumber

      Before tax.

    • salesTaxnumber

      Sales tax, in dollars.

    • taxRatenumber

      Sales tax rate, as a percentage (e.g. 8.25).

    • grandTotalnumber

      The estimate's total, tax included.

    • netTotalnumber

      grandTotal less the deductible.

    • deductiblenumber

      The deductible applied.

  • line_countintegernullable

    Number of estimate lines, once ready.

  • notesarray of string

    Plain-language notes about the run, e.g. a file that could not be built.

  • filesarray of file

    The files the estimate produced (.xme, .AWF). Empty until ready.

  • started_attimestamp

    When the estimate was started.

  • finished_attimestampnullable

    When it became ready or failed.

estimate object
{
  "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
  "object": "estimate",
  "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "ready",
  "stage": "ready",
  "step": null,
  "error": null,
  "photo_count": 12,
  "vin": "4T1G11AK5MU123456",
  "totals": {
    "categories": [
      {
        "label": "Parts",
        "cost": 1842.5
      },
      {
        "label": "Body Labor",
        "hours": 7.4,
        "rate": 58,
        "cost": 429.2
      },
      {
        "label": "Paint Labor",
        "hours": 4.8,
        "rate": 58,
        "cost": 278.4
      },
      {
        "label": "Paint Supplies",
        "hours": 4.8,
        "rate": 42,
        "cost": 201.6
      }
    ],
    "subtotal": 2751.7,
    "salesTax": 227.02,
    "taxRate": 8.25,
    "grandTotal": 2978.72,
    "netTotal": 2478.72,
    "deductible": 500
  },
  "line_count": 14,
  "notes": [],
  "files": [
    {
      "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 61440,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "created_at": "2026-10-18T14:06:40.117Z"
    },
    {
      "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 41872,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "created_at": "2026-10-18T14:06:40.117Z"
    }
  ],
  "started_at": "2026-10-18T14:03:02.950Z",
  "finished_at": "2026-10-18T14:06:40.402Z"
}

The file object

A file blnd wrote for a case: the CCC-importable .xme or .AWF.

  • idstring

    Unique identifier for the file.

  • objectstring

    Always file.

  • case_idstring

    The case the file belongs to.

  • kindenum

    The file type.

    xmeawf
  • namestring

    The file name.

  • bytesintegernullable

    Size in bytes.

  • uploaded_to_portalboolean

    Whether blnd has uploaded this file back to the case's portal.

  • download_urlstring

    Download the bytes with your API key (files:read).

  • created_attimestamp

    When the file was written.

file object
{
  "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
  "object": "file",
  "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "kind": "xme",
  "name": "C26006608.xme",
  "bytes": 61440,
  "uploaded_to_portal": false,
  "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
  "created_at": "2026-10-18T14:06:40.117Z"
}

The case event object

One entry on a case's timeline: a sync, a status change, a file written or uploaded, a note.

  • idstring

    Unique identifier. Use it as starting_after to page.

  • objectstring

    Always case_event.

  • case_idstring

    The case the entry belongs to.

  • typestring

    The kind of entry, e.g. synced, status, file, upload, note. Treat unknown types as plain entries.

  • messagestring

    What happened, in words.

  • attimestamp

    When it happened.

case event object
{
  "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
  "object": "case_event",
  "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "type": "file",
  "message": "Estimate written: 14 lines, $2,978.72. .xme, .AWF ready",
  "at": "2026-10-18T14:06:40.311Z"
}

The event object

What a webhook delivers. data.object is the case or estimate as it was when the event happened.

  • idstring

    Unique identifier, evt_…. The same as the webhook-id header and stable across retries: dedupe on it.

  • objectstring

    Always event.

  • typeenum

    The event type.

    case.createdcase.updatedestimate.startedestimate.readyestimate.failed
  • livemodeboolean

    false for events about sandbox data.

  • created_attimestamp

    When the event happened.

  • dataobject

    The event's subject.

    Child attributes(1)
    • objectcase | estimate

      A case for case.* events, an estimate for estimate.* events.

event object
{
  "id": "evt_8c1f2e7d4a9b3c6e0f5a1d2b",
  "object": "event",
  "type": "estimate.ready",
  "livemode": false,
  "created_at": "2026-10-18T14:06:40.415Z",
  "data": {
    "object": {
      "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
      "object": "estimate",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "status": "ready",
      "stage": "ready",
      "step": null,
      "error": null,
      "photo_count": 12,
      "vin": "4T1G11AK5MU123456",
      "totals": {
        "categories": [
          {
            "label": "Parts",
            "cost": 1842.5
          },
          {
            "label": "Body Labor",
            "hours": 7.4,
            "rate": 58,
            "cost": 429.2
          },
          {
            "label": "Paint Labor",
            "hours": 4.8,
            "rate": 58,
            "cost": 278.4
          },
          {
            "label": "Paint Supplies",
            "hours": 4.8,
            "rate": 42,
            "cost": 201.6
          }
        ],
        "subtotal": 2751.7,
        "salesTax": 227.02,
        "taxRate": 8.25,
        "grandTotal": 2978.72,
        "netTotal": 2478.72,
        "deductible": 500
      },
      "line_count": 14,
      "notes": [],
      "files": [
        {
          "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
          "object": "file",
          "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "kind": "xme",
          "name": "C26006608.xme",
          "bytes": 61440,
          "uploaded_to_portal": false,
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
          "created_at": "2026-10-18T14:06:40.117Z"
        },
        {
          "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
          "object": "file",
          "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "kind": "xme",
          "name": "C26006608.xme",
          "bytes": 41872,
          "uploaded_to_portal": false,
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
          "created_at": "2026-10-18T14:06:40.117Z"
        }
      ],
      "started_at": "2026-10-18T14:03:02.950Z",
      "finished_at": "2026-10-18T14:06:40.402Z"
    }
  }
}

Endpoints

Workspace

Check the API, the key and the team. Besides each endpoint's own errors, any call with a key can answer `401`, `403`, `429` or `500`.

GET/v1

Check the API

Answers without a key: the API version and where the docs live. Handy as a health check.

No key needed

Returns

200 The API object.

  • objectstring

    Always api.

  • versionstring

    The API version, e.g. 2026-10-01.

  • docsstring

    Where these docs live.

Full URL: https://blnd.one/api/v1

GET/v1
curl https://blnd.one/api/v1
200Response
{
  "object": "api",
  "version": "2026-10-01",
  "docs": "https://blnd.one/docs/api"
}
GET/v1/me

Retrieve your workspace

The workspace and the key behind this request: its mode, scopes and rate limit, and where the workspace's Developer Program status stands. The first call to make with a new key.

Any API key

Returns

200 The workspace object.

  • objectstring

    Always workspace.

  • idstring

    The workspace id.

  • namestringnullable

    The workspace name.

  • livemodeboolean

    true for a live key, false for a test key.

  • api_accessenum

    Where the workspace's live API access stands.

    nonependingapprovedrejectedsuspended
  • keyobject

    The key making this request.

    Child attributes(4)
    • idstring

      The key id.

    • namestringnullable

      The name you gave the key.

    • modeenum

      Live or test.

      livetest
    • scopesarray of string

      What the key may do; ["*"] is full access.

  • rate_limitinteger

    Requests per minute this key may make.

Full URL: https://blnd.one/api/v1/me

GET/v1/me
curl https://blnd.one/api/v1/me \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "object": "workspace",
  "id": "org_2kq8Zt0VbN4mXc7Ra1Lp",
  "name": "Lone Star Desk Review",
  "livemode": false,
  "api_access": "pending",
  "key": {
    "id": "key_9d2c71",
    "name": "Claims system (staging)",
    "mode": "test",
    "scopes": [
      "*"
    ]
  },
  "rate_limit": 30
}
GET/v1/members

List team members

The people on the workspace's team. Their ids are what assignee_id takes when you route a case to a reviewer.

Scope cases:read

Returns

200 A list of member objects.

  • idstring

    The person's user id: the value assignee_id takes and cases carry.

  • objectstring

    Always member.

  • namestring

    Display name.

  • emailstring

    Email address.

  • rolestring

    Their role in the workspace, e.g. owner, admin, member.

Full URL: https://blnd.one/api/v1/members

GET/v1/members
curl https://blnd.one/api/v1/members \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "object": "list",
  "data": [
    {
      "id": "Hk3pQ8vLr2NwT6yZb1Xc",
      "object": "member",
      "name": "Riley Chen",
      "email": "riley@deskreview.example",
      "role": "admin"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Endpoints

Cases

Assignments from every source. Test keys see sandbox cases only; live keys, live cases only.

GET/v1/cases

List cases

Every case the key can see, newest first, from every source: portal syncs, email and the API. Filters combine.

Scope cases:readPaginated

Query parameters

  • limitinteger

    How many objects to return, 1 to 100. Default 25.

  • starting_afterstring

    A cursor: the id of the last object on the previous page (next_cursor).

  • statusenum

    Only cases with this status.

    newin_progressdraft_readyuploadedclosed
  • sourceenum

    Only cases from this source.

    apiportalemail
  • portalstring

    Only cases synced from this portal, e.g. vista.

  • assignee_idstring

    Only cases routed to this team member.

  • claim_numberstring

    Only cases with this claim number.

  • updated_sincetimestamp

    Only cases changed at or after this ISO 8601 time. Good for an incremental sync.

Returns

200 A list of case objects.

Notable errors

StatusCodeWhen
400parameter_invalidA filter or limit is out of range, or starting_after isn't in this list.

Full URL: https://blnd.one/api/v1/cases

GET/v1/cases
curl -G https://blnd.one/api/v1/cases \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -d status=new \
  -d limit=10
200Response
{
  "object": "list",
  "data": [
    {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "object": "case",
      "livemode": false,
      "source": "api",
      "portal": null,
      "external_id": "CLM-88213-01",
      "status": "new",
      "claim_number": "C26006608",
      "file_number": null,
      "policy_number": null,
      "carrier": "Sedgwick",
      "assignment_type": "DR",
      "insured_name": "Dana Lee",
      "vehicle": {
        "year": 2021,
        "make": "Toyota",
        "model": "Camry",
        "vin": "4T1G11AK5MU123456"
      },
      "loss": {
        "type": "Collision",
        "date": "2026-10-02",
        "description": "Front-end, driver side"
      },
      "inspection_address": "12 Main St, Austin, TX 78701",
      "deductible": "500",
      "owner": {
        "phone": "+15125550100",
        "email": "dana@example.com"
      },
      "adjuster": {
        "name": "Sam Ortiz",
        "phone": null,
        "email": "sam@carrier.com"
      },
      "assignee_id": null,
      "due_at": "2026-10-20T17:00:00.000Z",
      "assigned_at": "2026-10-18T14:02:11.482Z",
      "appointment_at": null,
      "metadata": {
        "claims_system_id": "88213"
      },
      "url": "https://blnd.one/app/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "created_at": "2026-10-18T14:02:11.482Z",
      "updated_at": "2026-10-18T14:02:11.482Z"
    }
  ],
  "has_more": true,
  "next_cursor": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
POST/v1/cases

Create a case

Push an assignment into blnd: from your claims system, a desk-review queue, or a portal blnd doesn't cover. Every field is optional; send what you know. The case shows in the blnd inbox at once and fires case.created.

Scope cases:writeAccepts Idempotency-Key

Body · application/json

  • external_idstring

    Your id for the case. Unique per workspace, across test and live: a second create with the same id, in either mode, is a 409 case_exists. Leave it out and the case has none.

    1 to 255 characters; can't start with blnd: or mail:.

  • claim_numberstringnullable

    The carrier's claim number.

    At most 500 characters.

  • file_numberstringnullable

    Your or the portal's file number.

    At most 500 characters.

  • policy_numberstringnullable

    The insured's policy number.

    At most 500 characters.

  • carrierstringnullable

    The insurance carrier.

    At most 500 characters.

  • assignment_typestringnullable

    Your code for the kind of work, e.g. DR for desk review.

    At most 20 characters.

  • insured_namestringnullable

    The insured's name.

    At most 500 characters.

  • vehicleobject

    The vehicle. Every key is optional.

    Child attributes(4)
    • yearintegernullable

      Model year.

      1900 to 2100.

    • makestringnullable

      Make.

      At most 500 characters.

    • modelstringnullable

      Model.

      At most 500 characters.

    • vinstringnullable

      The VIN. Upper-cased for you.

      17 characters, A-Z and 0-9 without I, O or Q.

  • lossobject

    What happened. Every key is optional.

    Child attributes(3)
    • typestringnullable

      Kind of loss, e.g. Collision.

      At most 500 characters.

    • datedatenullable

      Date of loss.

      YYYY-MM-DD.

    • descriptionstringnullable

      The loss description or point of impact.

      At most 5000 characters.

  • inspection_addressstringnullable

    Where the vehicle can be inspected.

    At most 500 characters.

  • deductiblestring | numbernullable

    The deductible. A number is accepted and stored as a string.

    At most 50 characters.

  • ownerobject

    The vehicle owner's contact details.

    Child attributes(2)
    • phonestringnullable

      Phone number.

      At most 50 characters.

    • emailstringnullable

      Email address.

      At most 320 characters.

  • adjusterobject

    The carrier's adjuster.

    Child attributes(3)
    • namestringnullable

      Name.

      At most 500 characters.

    • phonestringnullable

      Phone number.

      At most 50 characters.

    • emailstringnullable

      Email address.

      At most 320 characters.

  • assignee_idstringnullable

    Route the case to a team member: their user id, from GET /members.

  • due_attimestampnullable

    When the work is due. ISO 8601 with a time zone offset.

  • metadataobjectnullable

    Your own key/value pairs. Keys merge into what's there; a null value removes that key; null for the whole object clears it.

    At most 20 keys of up to 40 characters; string values up to 500 characters.

Returns

201 The case object.

Notable errors

StatusCodeWhen
400parameter_invalidA field fails validation, an unknown field was sent, or assignee_id isn't a team member; param says which.
409case_existsexternal_id is already taken in this workspace, in test or live.

Full URL: https://blnd.one/api/v1/cases

POST/v1/cases
curl https://blnd.one/api/v1/cases \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -H "Idempotency-Key: a8098c1a-f86e-41d8-9d0c-3c0b8f4a1e2d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "CLM-88213-01",
    "claim_number": "C26006608",
    "carrier": "Sedgwick",
    "assignment_type": "DR",
    "insured_name": "Dana Lee",
    "vehicle": {
      "year": 2021,
      "make": "Toyota",
      "model": "Camry",
      "vin": "4T1G11AK5MU123456"
    },
    "loss": {
      "type": "Collision",
      "date": "2026-10-02",
      "description": "Front-end, driver side"
    },
    "inspection_address": "12 Main St, Austin, TX 78701",
    "deductible": "500",
    "owner": {
      "phone": "+15125550100",
      "email": "dana@example.com"
    },
    "adjuster": {
      "name": "Sam Ortiz",
      "email": "sam@carrier.com"
    },
    "due_at": "2026-10-20T17:00:00Z",
    "metadata": {
      "claims_system_id": "88213"
    }
  }'
201Response
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "case",
  "livemode": false,
  "source": "api",
  "portal": null,
  "external_id": "CLM-88213-01",
  "status": "new",
  "claim_number": "C26006608",
  "file_number": null,
  "policy_number": null,
  "carrier": "Sedgwick",
  "assignment_type": "DR",
  "insured_name": "Dana Lee",
  "vehicle": {
    "year": 2021,
    "make": "Toyota",
    "model": "Camry",
    "vin": "4T1G11AK5MU123456"
  },
  "loss": {
    "type": "Collision",
    "date": "2026-10-02",
    "description": "Front-end, driver side"
  },
  "inspection_address": "12 Main St, Austin, TX 78701",
  "deductible": "500",
  "owner": {
    "phone": "+15125550100",
    "email": "dana@example.com"
  },
  "adjuster": {
    "name": "Sam Ortiz",
    "phone": null,
    "email": "sam@carrier.com"
  },
  "assignee_id": null,
  "due_at": "2026-10-20T17:00:00.000Z",
  "assigned_at": "2026-10-18T14:02:11.482Z",
  "appointment_at": null,
  "metadata": {
    "claims_system_id": "88213"
  },
  "url": "https://blnd.one/app/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "created_at": "2026-10-18T14:02:11.482Z",
  "updated_at": "2026-10-18T14:02:11.482Z"
}
GET/v1/cases/{id}

Retrieve a case

One case by its id.

Scope cases:read

Path parameters

  • idstringRequired

    The case id.

Returns

200 The case object.

Notable errors

StatusCodeWhen
404resource_missingNo such case in this mode.

Full URL: https://blnd.one/api/v1/cases/{id}

GET/v1/cases/{id}
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "case",
  "livemode": false,
  "source": "api",
  "portal": null,
  "external_id": "CLM-88213-01",
  "status": "new",
  "claim_number": "C26006608",
  "file_number": null,
  "policy_number": null,
  "carrier": "Sedgwick",
  "assignment_type": "DR",
  "insured_name": "Dana Lee",
  "vehicle": {
    "year": 2021,
    "make": "Toyota",
    "model": "Camry",
    "vin": "4T1G11AK5MU123456"
  },
  "loss": {
    "type": "Collision",
    "date": "2026-10-02",
    "description": "Front-end, driver side"
  },
  "inspection_address": "12 Main St, Austin, TX 78701",
  "deductible": "500",
  "owner": {
    "phone": "+15125550100",
    "email": "dana@example.com"
  },
  "adjuster": {
    "name": "Sam Ortiz",
    "phone": null,
    "email": "sam@carrier.com"
  },
  "assignee_id": null,
  "due_at": "2026-10-20T17:00:00.000Z",
  "assigned_at": "2026-10-18T14:02:11.482Z",
  "appointment_at": null,
  "metadata": {
    "claims_system_id": "88213"
  },
  "url": "https://blnd.one/app/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "created_at": "2026-10-18T14:02:11.482Z",
  "updated_at": "2026-10-18T14:02:11.482Z"
}
PATCH/v1/cases/{id}

Update a case

Change what you send; everything else stays. API cases take any create field. Portal and email cases belong to their source, so only assignee_id, assignment_type and metadata change on them. Any case can be closed with status: "closed" (not while an estimate runs) and reopened with status: "new". Fires case.updated.

Scope cases:write

Path parameters

  • idstringRequired

    The case id.

Body · application/json

  • statusenum

    closed closes the case (not while an estimate is running); new reopens a closed case.

    closednew
  • external_idstring

    Your id for the case. Unique per workspace, across test and live: a second create with the same id, in either mode, is a 409 case_exists. Leave it out and the case has none.

    1 to 255 characters; can't start with blnd: or mail:.

  • claim_numberstringnullable

    The carrier's claim number.

    At most 500 characters.

  • file_numberstringnullable

    Your or the portal's file number.

    At most 500 characters.

  • policy_numberstringnullable

    The insured's policy number.

    At most 500 characters.

  • carrierstringnullable

    The insurance carrier.

    At most 500 characters.

  • assignment_typestringnullable

    Your code for the kind of work, e.g. DR for desk review.

    At most 20 characters.

  • insured_namestringnullable

    The insured's name.

    At most 500 characters.

  • vehicleobject

    The vehicle. Every key is optional.

    Child attributes(4)
    • yearintegernullable

      Model year.

      1900 to 2100.

    • makestringnullable

      Make.

      At most 500 characters.

    • modelstringnullable

      Model.

      At most 500 characters.

    • vinstringnullable

      The VIN. Upper-cased for you.

      17 characters, A-Z and 0-9 without I, O or Q.

  • lossobject

    What happened. Every key is optional.

    Child attributes(3)
    • typestringnullable

      Kind of loss, e.g. Collision.

      At most 500 characters.

    • datedatenullable

      Date of loss.

      YYYY-MM-DD.

    • descriptionstringnullable

      The loss description or point of impact.

      At most 5000 characters.

  • inspection_addressstringnullable

    Where the vehicle can be inspected.

    At most 500 characters.

  • deductiblestring | numbernullable

    The deductible. A number is accepted and stored as a string.

    At most 50 characters.

  • ownerobject

    The vehicle owner's contact details.

    Child attributes(2)
    • phonestringnullable

      Phone number.

      At most 50 characters.

    • emailstringnullable

      Email address.

      At most 320 characters.

  • adjusterobject

    The carrier's adjuster.

    Child attributes(3)
    • namestringnullable

      Name.

      At most 500 characters.

    • phonestringnullable

      Phone number.

      At most 50 characters.

    • emailstringnullable

      Email address.

      At most 320 characters.

  • assignee_idstringnullable

    Route the case to a team member: their user id, from GET /members.

  • due_attimestampnullable

    When the work is due. ISO 8601 with a time zone offset.

  • metadataobjectnullable

    Your own key/value pairs. Keys merge into what's there; a null value removes that key; null for the whole object clears it.

    At most 20 keys of up to 40 characters; string values up to 500 characters.

Returns

200 The case object.

Notable errors

StatusCodeWhen
400parameter_invalidA field fails validation, the field can't change on a portal or email case, assignee_id isn't a team member, or status: "new" on a case that isn't closed.
404resource_missingNo such case in this mode.
409estimate_in_progressClosing a case while its estimate runs.
409case_existsA new external_id that another case already has.

Full URL: https://blnd.one/api/v1/cases/{id}

PATCH/v1/cases/{id}
curl -X PATCH https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7 \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "assignee_id": "Hk3pQ8vLr2NwT6yZb1Xc",
    "metadata": {
      "queue": "desk-review"
    }
  }'
200Response
{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "object": "case",
  "livemode": false,
  "source": "api",
  "portal": null,
  "external_id": "CLM-88213-01",
  "status": "new",
  "claim_number": "C26006608",
  "file_number": null,
  "policy_number": null,
  "carrier": "Sedgwick",
  "assignment_type": "DR",
  "insured_name": "Dana Lee",
  "vehicle": {
    "year": 2021,
    "make": "Toyota",
    "model": "Camry",
    "vin": "4T1G11AK5MU123456"
  },
  "loss": {
    "type": "Collision",
    "date": "2026-10-02",
    "description": "Front-end, driver side"
  },
  "inspection_address": "12 Main St, Austin, TX 78701",
  "deductible": "500",
  "owner": {
    "phone": "+15125550100",
    "email": "dana@example.com"
  },
  "adjuster": {
    "name": "Sam Ortiz",
    "phone": null,
    "email": "sam@carrier.com"
  },
  "assignee_id": "Hk3pQ8vLr2NwT6yZb1Xc",
  "due_at": "2026-10-20T17:00:00.000Z",
  "assigned_at": "2026-10-18T14:02:11.482Z",
  "appointment_at": null,
  "metadata": {
    "claims_system_id": "88213",
    "queue": "desk-review"
  },
  "url": "https://blnd.one/app/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "created_at": "2026-10-18T14:02:11.482Z",
  "updated_at": "2026-10-18T14:02:58.006Z"
}
GET/v1/cases/{id}/events

List a case's timeline

The case's timeline, newest first: syncs, status changes, files written and uploaded, notes.

Scope cases:readPaginated

Path parameters

  • idstringRequired

    The case id.

Query parameters

  • limitinteger

    How many objects to return, 1 to 100. Default 25.

  • starting_afterstring

    A cursor: the id of the last object on the previous page (next_cursor).

Returns

200 A list of case event objects.

Notable errors

StatusCodeWhen
404resource_missingNo such case in this mode.

Full URL: https://blnd.one/api/v1/cases/{id}/events

GET/v1/cases/{id}/events
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/events \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "object": "list",
  "data": [
    {
      "id": "0f8fad5b-d9cb-469f-a165-70867728950e",
      "object": "case_event",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "type": "file",
      "message": "Estimate written: 14 lines, $2,978.72. .xme, .AWF ready",
      "at": "2026-10-18T14:06:40.311Z"
    },
    {
      "id": "1b4e28ba-2fa1-41d2-883f-0016d3cca427",
      "object": "case_event",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "type": "note",
      "message": "Created through the API",
      "at": "2026-10-18T14:02:11.502Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Endpoints

Estimates

Photos in, a written estimate out. One estimate runs per case at a time; a new one replaces the last.

POST/v1/cases/{id}/estimate

Start an estimate

Upload the damage photos and start writing the estimate. Answers 202 Accepted at once with the estimate in queued; follow it with estimate.ready / estimate.failed webhooks or by polling. With a test key the free demo engine writes it.

Scope estimates:writeAccepts Idempotency-Key

Path parameters

  • idstringRequired

    The case id.

Form fields · multipart/form-data

  • photosarray of filesRequired

    The damage photos. Repeat the photos field once per file.

    1 to 64 files; JPEG, PNG or WebP; at most 15 MB each. Send the body as multipart/form-data.

  • vinstringnullable

    The VIN to write the estimate for. Defaults to the case's VIN.

    17 characters.

Returns

202 The estimate object. Work continues in the background.

Notable errors

StatusCodeWhen
400parameter_missingNo photos were sent.
400parameter_invalidThe body isn't multipart/form-data, a photo isn't JPEG, PNG or WebP, there are more than 64, or vin isn't a valid VIN.
409estimate_in_progressAn estimate is already running for this case.
409estimate_refusedThe estimate can't be started for this case.
411length_requiredThe upload has no Content-Length header (chunked uploads aren't accepted).
413payload_too_largeA photo is over 15 MB, or the upload is over 250 MB in all (25 MB with a test key).

Full URL: https://blnd.one/api/v1/cases/{id}/estimate

POST/v1/cases/{id}/estimate
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/estimate \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -H "Idempotency-Key: a8098c1a-f86e-41d8-9d0c-3c0b8f4a1e2d" \
  -F "photos=@front.jpg;type=image/jpeg" \
  -F "photos=@driver-side.jpg;type=image/jpeg" \
  -F vin=4T1G11AK5MU123456
202Response
{
  "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
  "object": "estimate",
  "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "queued",
  "stage": "queued",
  "step": null,
  "error": null,
  "photo_count": 12,
  "vin": "4T1G11AK5MU123456",
  "totals": null,
  "line_count": null,
  "notes": [],
  "files": [],
  "started_at": "2026-10-18T14:03:02.950Z",
  "finished_at": null
}
GET/v1/cases/{id}/estimate

Retrieve the estimate

The case's newest estimate. Polling also moves a running estimate along, so a poll every few seconds is a fine alternative to webhooks.

Scope cases:read

Path parameters

  • idstringRequired

    The case id.

Returns

200 The estimate object.

Notable errors

StatusCodeWhen
404resource_missingNo such case in this mode.
404no_estimateNo estimate has been started for this case.

Full URL: https://blnd.one/api/v1/cases/{id}/estimate

GET/v1/cases/{id}/estimate
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/estimate \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
  "object": "estimate",
  "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "status": "ready",
  "stage": "ready",
  "step": null,
  "error": null,
  "photo_count": 12,
  "vin": "4T1G11AK5MU123456",
  "totals": {
    "categories": [
      {
        "label": "Parts",
        "cost": 1842.5
      },
      {
        "label": "Body Labor",
        "hours": 7.4,
        "rate": 58,
        "cost": 429.2
      },
      {
        "label": "Paint Labor",
        "hours": 4.8,
        "rate": 58,
        "cost": 278.4
      },
      {
        "label": "Paint Supplies",
        "hours": 4.8,
        "rate": 42,
        "cost": 201.6
      }
    ],
    "subtotal": 2751.7,
    "salesTax": 227.02,
    "taxRate": 8.25,
    "grandTotal": 2978.72,
    "netTotal": 2478.72,
    "deductible": 500
  },
  "line_count": 14,
  "notes": [],
  "files": [
    {
      "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 61440,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "created_at": "2026-10-18T14:06:40.117Z"
    },
    {
      "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 41872,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "created_at": "2026-10-18T14:06:40.117Z"
    }
  ],
  "started_at": "2026-10-18T14:03:02.950Z",
  "finished_at": "2026-10-18T14:06:40.402Z"
}

Endpoints

Files

The CCC-importable .xme and .AWF, as files you download with your key.

GET/v1/cases/{id}/files

List a case's files

The estimate files blnd wrote for the case: the CCC-importable .xme and .AWF.

Scope files:read

Path parameters

  • idstringRequired

    The case id.

Returns

200 A list of file objects.

Notable errors

StatusCodeWhen
404resource_missingNo such case in this mode.

Full URL: https://blnd.one/api/v1/cases/{id}/files

GET/v1/cases/{id}/files
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files \
  -H "Authorization: Bearer $BLND_API_KEY"
200Response
{
  "object": "list",
  "data": [
    {
      "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 61440,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
      "created_at": "2026-10-18T14:06:40.117Z"
    },
    {
      "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "object": "file",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "kind": "xme",
      "name": "C26006608.xme",
      "bytes": 41872,
      "uploaded_to_portal": false,
      "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
      "created_at": "2026-10-18T14:06:40.117Z"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
GET/v1/cases/{id}/files/{file_id}

Download a file

The file's bytes (application/octet-stream), with Content-Disposition: attachment and the file name. Follow download_url on a file object with your key.

Scope files:read

Path parameters

  • idstringRequired

    The case id.

  • file_idstringRequired

    The file id.

Returns

200 The file's bytes.

Notable errors

StatusCodeWhen
404resource_missingNo such case or file in this mode, or the file is missing from storage (start the estimate again).

Full URL: https://blnd.one/api/v1/cases/{id}/files/{file_id}

GET/v1/cases/{id}/files/{file_id}
curl https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b \
  -H "Authorization: Bearer $BLND_API_KEY" \
  -o C26006608.xme
200Response
// 200 OK: the file's bytes
// Content-Disposition: attachment; filename="C26006608.xme"

Webhooks

Webhooks

blnd posts an event to your HTTPS endpoint when a case arrives or changes and when an estimate starts, finishes or fails. Signed per Standard Webhooks.

Setting up an endpoint

Add endpoints in Settings > Developers > Webhooks. Each one has:

  • An https:// URL on a public host. Credentials go in your receiver, not in the URL.
  • A mode: a test endpoint hears only sandbox data, a live one only live data. Like keys, endpoints need an approved Developer Program workspace.
  • The events it listens to, or all of them.
  • A signing secret, whsec_…, to verify that a delivery came from blnd.

Send test event on the endpoint's page posts a signed sample, and every delivery's status, answer and timing is listed there, with a Resend button.

The body is an event object: { id, object: "event", type, livemode, created_at, data: { object } }, where data.object is the case or estimate as it was at that moment.

A delivery
POST /webhooks/blnd HTTP/1.1
Content-Type: application/json
User-Agent: blnd-webhooks/1
webhook-id: evt_8c1f2e7d4a9b3c6e0f5a1d2b
webhook-timestamp: 1792332400
webhook-signature: v1,f20wpH3s+o397OLg8QQfHSnD3so2AdU0s4cIZqgOU4c=

{"id":"evt_8c1f2e7d4a9b3c6e0f5a1d2b","object":"event","type":"case.created","livemode":false,"created_at":"2026-10-18T14:06:40.000Z","data":{"object":{"id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","object":"case"}}}

Event types

Eventdata.objectWhen
case.createdcaseA case arrived: from a portal sync, an email, or the API
case.updatedcaseA case's details, status or assignee changed
estimate.startedestimateAn estimate began drafting
estimate.readyestimateThe estimate files are written and ready to download
estimate.failedestimateAn estimate stopped; the error says why

estimate.ready carries the estimate with its files, so a receiver can download them straight away. New event types may be added; ignore the ones you don't handle.

estimate.ready
{
  "id": "evt_8c1f2e7d4a9b3c6e0f5a1d2b",
  "object": "event",
  "type": "estimate.ready",
  "livemode": false,
  "created_at": "2026-10-18T14:06:40.415Z",
  "data": {
    "object": {
      "id": "e4d909c2-90d0-4fb1-a0b2-9f5e0c7a3d11",
      "object": "estimate",
      "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "status": "ready",
      "stage": "ready",
      "step": null,
      "error": null,
      "photo_count": 12,
      "vin": "4T1G11AK5MU123456",
      "totals": {
        "categories": [
          {
            "label": "Parts",
            "cost": 1842.5
          },
          {
            "label": "Body Labor",
            "hours": 7.4,
            "rate": 58,
            "cost": 429.2
          },
          {
            "label": "Paint Labor",
            "hours": 4.8,
            "rate": 58,
            "cost": 278.4
          },
          {
            "label": "Paint Supplies",
            "hours": 4.8,
            "rate": 42,
            "cost": 201.6
          }
        ],
        "subtotal": 2751.7,
        "salesTax": 227.02,
        "taxRate": 8.25,
        "grandTotal": 2978.72,
        "netTotal": 2478.72,
        "deductible": 500
      },
      "line_count": 14,
      "notes": [],
      "files": [
        {
          "id": "b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
          "object": "file",
          "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "kind": "xme",
          "name": "C26006608.xme",
          "bytes": 61440,
          "uploaded_to_portal": false,
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/b1e2f3a4-5c6d-4e7f-8a9b-0c1d2e3f4a5b",
          "created_at": "2026-10-18T14:06:40.117Z"
        },
        {
          "id": "c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
          "object": "file",
          "case_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
          "kind": "xme",
          "name": "C26006608.xme",
          "bytes": 41872,
          "uploaded_to_portal": false,
          "download_url": "https://blnd.one/api/v1/cases/7c9e6679-7425-40de-944b-e07fc1f90ae7/files/c2f3a4b5-6d7e-4f80-9b1c-2d3e4f5a6b7c",
          "created_at": "2026-10-18T14:06:40.117Z"
        }
      ],
      "started_at": "2026-10-18T14:03:02.950Z",
      "finished_at": "2026-10-18T14:06:40.402Z"
    }
  }
}

Verifying signatures

Every delivery carries three headers:

HeaderValue
webhook-idThe event id, evt_…. The same on every retry of that event.
webhook-timestampUnix time, in seconds, of this attempt.
webhook-signaturev1, then the base64 HMAC-SHA256 of the signed content. May hold several space-separated signatures.

To verify one:

  1. Take the raw request body, before any JSON parsing.
  2. Build the signed content: {webhook-id}.{webhook-timestamp}.{body}.
  3. The key is the base64-decoded part of your secret after whsec_.
  4. Compute HMAC-SHA256, base64-encode it, and compare it with each v1, signature in constant time.
  5. Reject timestamps more than 5 minutes from now, so an old delivery can't be replayed.

Standard Webhooks libraries verify these headers as they are. The test vector beside this was signed by blnd's own signer: your check should accept it (ignore the timestamp tolerance for that test).

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

const secret = process.env.BLND_WEBHOOK_SECRET; // whsec_…
const TOLERANCE_S = 5 * 60;

export function verifyWebhook(rawBody, headers) {
  const id = headers["webhook-id"];
  const timestamp = headers["webhook-timestamp"];
  const signatures = headers["webhook-signature"];
  if (!id || !timestamp || !signatures) throw new Error("Missing webhook headers");

  // Reject old (or future) timestamps: replay protection
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_S) {
    throw new Error("Timestamp outside the tolerance");
  }

  const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
  const expected = crypto
    .createHmac("sha256", key)
    .update(`${id}.${timestamp}.${rawBody}`)
    .digest();

  // The header may carry several space-separated signatures: "v1,<base64> v1,<base64>"
  const ok = signatures.split(" ").some((part) => {
    const [version, sig] = part.split(",");
    const got = Buffer.from(sig ?? "", "base64");
    return version === "v1" && got.length === expected.length && crypto.timingSafeEqual(got, expected);
  });
  if (!ok) throw new Error("Invalid signature");
  return JSON.parse(rawBody);
}

const app = express();

// Verify against the raw bytes: parse the JSON only after the check
app.post("/webhooks/blnd", express.raw({ type: "application/json" }), (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body.toString("utf8"), req.headers);
  } catch {
    return res.sendStatus(400);
  }
  res.sendStatus(204); // answer fast, then do the work
  queue.add(event.type, event); // dedupe on event.id (= webhook-id)
});
Test vector
secret            = whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw
webhook-id        = evt_8c1f2e7d4a9b3c6e0f5a1d2b
webhook-timestamp = 1792332400
body              = {"id":"evt_8c1f2e7d4a9b3c6e0f5a1d2b","object":"event","type":"case.created","livemode":false,"created_at":"2026-10-18T14:06:40.000Z","data":{"object":{"id":"7c9e6679-7425-40de-944b-e07fc1f90ae7","object":"case"}}}

webhook-signature = v1,f20wpH3s+o397OLg8QQfHSnD3so2AdU0s4cIZqgOU4c=

Retries

A 2xx answer within 15 seconds is a success. Anything else (another status, a timeout, a refused connection) is retried on this schedule, each attempt with the same webhook-id:

AttemptWaitsSince the event
1None: sent at once0
25 s≈ 5 s
35 min≈ 5 min
430 min≈ 35 min
52 h≈ 3 h
65 h≈ 8 h
710 h≈ 18 h
824 h≈ 42 h

Failing endpoints are switched off

An endpoint whose deliveries fail for 3 days straight is disabled, and the reason shows on its page. Fix the receiver, then enable it again; deliveries that gave up can be resent from the list.

Best practices

  • Answer fast. Verify, store the event, return 2xx, and do the work afterwards on a queue. Slow answers time out at 15 seconds and are retried.
  • Dedupe on webhook-id. Retries and resends reuse it (it equals the event's id), so record the ids you've handled and skip repeats.
  • Don't rely on order. Retries can deliver an older event after a newer one. Compare created_at, or fetch the latest state with the API.
  • Verify every delivery against the raw body, and keep the secret on the server.
  • Use a test endpoint with test keys while you build; it only ever hears sandbox data.

Test mode

Free demo engine

Estimates on sandbox cases are written by the demo engine: real files, never billed.

Sandbox only

Test keys see and create sandbox cases only. Their estimates are never delivered into CCC.

Test webhooks

Test endpoints receive the events about sandbox data, signed exactly like live ones.

Sandbox cases carry livemode: false and show in the blnd app with a Test badge, outside the working inbox unless you turn the Test filter on. Test keys run at 30 requests a minute. Like live keys, they need an approved Developer Program workspace. When you go live, swap the key: nothing else changes.

Changelog

  1. 2026-10-01

    Initial release

    • Cases: list, create, retrieve, update, and each case's timeline.
    • Estimates: start one from up to 64 photos, and retrieve it.
    • Files: list and download the .xme and .AWF.
    • Workspace and team members.
    • Webhooks for 5 events, signed per Standard Webhooks.
    • Test mode, idempotency keys, cursor pagination and an OpenAPI 3.1 description.

Start building today

Apply to the Developer Program in a few minutes; once approved, your first test key is one click away. Questions: support@blnd.one.