# InspectMind API & MCP

Submit plan sets for checking and read findings from your own tools or AI
assistant. Each submission becomes a project in your account, is quoted and
billed like an upload in the app, and emails results to the key's owner.

Create keys at https://checker.inspectmind.ai/developers.

Base URL: `https://api.inspectmind.ai`. Don't use `checker.inspectmind.ai`: its hosting rejects request bodies over about 6 MB with a 413.

## Auth

Send the API key as a bearer token:

```
Authorization: Bearer imk_...
```

Each key acts as the account that created it and sees only that account's
submissions. Treat it like a password; revoke it at /developers if it leaks.

## MCP

`https://api.inspectmind.ai/api/mcp` (Streamable HTTP) with the same bearer
key. Tools: `list_projects`, `get_findings`, `submit_check`, `submit_revision`,
`get_submission`. Files go as public links (Drive, Dropbox, OneDrive, Box, or a
direct .pdf/.zip URL).

```bash
claude mcp add --transport http inspectmind https://api.inspectmind.ai/api/mcp \
  --header "Authorization: Bearer imk_..."
```

JSON-config clients (Claude Desktop, Cursor):

```json
{
  "mcpServers": {
    "inspectmind": {
      "type": "http",
      "url": "https://api.inspectmind.ai/api/mcp",
      "headers": { "Authorization": "Bearer imk_..." }
    }
  }
}
```

Clients without header support: `npx mcp-remote https://api.inspectmind.ai/api/mcp --header "Authorization: Bearer imk_..."`.

## Submit a plan set

`POST /api/v1/checks` with `multipart/form-data`:

| Field | Required | Notes |
|---|---|---|
| `subject` | yes | Up to 300 chars. Names the project. |
| `notes` | no | Up to 4000 chars. Job details and instructions for the check. |
| `files` | one of | PDF or ZIP of PDFs. Repeat the field for multiple files. |
| `file_urls` | one of | Public PDF/ZIP link (Google Drive, Dropbox, OneDrive, Box, or a direct `https://….pdf`/`.zip`). Repeat for multiple. |
| `checklist` | no | One QA/QC checklist: PDF (text-based), DOCX, XLS, or XLSX, up to 20 MB. CSV is not supported; save it as XLSX. |
| `checklist_profile` | with `checklist` | `plan-review` or `shop-drawing`: how the checklist's items are interpreted. |

Limits: request body up to 100 MB, up to 20 files + URLs in total. Send larger
plan sets as `file_urls`.

Optional header `Idempotency-Key: <your-job-id>` makes retries safe: the same
key always returns the original submission instead of creating a new one.

```bash
curl https://api.inspectmind.ai/api/v1/checks \
  -H "Authorization: Bearer $INSPECTMIND_API_KEY" \
  -H "Idempotency-Key: job-1042-r0" \
  -F "subject=Job 1042 | 100 Main St, Springfield" \
  -F $'notes=Job 1042\nAddress: 100 Main St, Springfield' \
  -F "files=@Structural Set.pdf" \
  -F "files=@Structural Calcs.pdf"
```

Response `202`:

```json
{
  "id": "6f0c…",
  "subject": "Job 1042 | 100 Main St, Springfield",
  "status": "queued",
  "error": null,
  "project_id": null,
  "check_id": null,
  "review_url": null,
  "created_at": "2026-10-19T18:00:00.000Z"
}
```

A retry with the same `Idempotency-Key` returns `200` with the current state.

## Check status

`GET /api/v1/checks/{id}` returns the same shape. `status` moves through:

| Status | Meaning |
|---|---|
| `queued` | Received, waiting for a worker |
| `processing` | Files downloaded, project created, check being set up |
| `awaiting_payment` | Quote emailed to the key's owner; pay it, or start from credits, at `review_url` |
| `running` | Check started |
| `complete` | Results ready at `review_url`; the results email follows shortly |
| `failed` | See `error` |

`review_url` appears once the project exists. Results are also emailed to the
key's owner.

## QA/QC checklists

A `checklist` file becomes a checklist on the project. Its items are read
from the file (one item per line or row) and checked against the submitted
documents on every check run on the project, including later revisions.
Checklist results show on the review page's checklist view; they are not
part of `markups.pdf`. An unreadable checklist (e.g. a scanned PDF) fails the
submission with a specific `error`.

A revision may resend the same checklist: identical items are a no-op. A
changed checklist under the same file name fails; send it under a new file
name instead (both then run).

## Completion webhook

If your key has a webhook URL (set when creating it), we `POST` JSON to it when a submission
reaches `complete` or `failed`:

```json
{
  "id": "0b9e…",
  "type": "check.completed",
  "created_at": "2026-10-26T18:00:00.000Z",
  "data": { "id": "6f0c…", "status": "complete", "review_url": "…", "…": "same shape as GET /api/v1/checks/{id}" }
}
```

`type` is `check.completed` or `check.failed`. Each submission sends each
type at most once; `id` is stable across retries, so dedupe on it. A failed
submission that our team reprocesses can later send `check.completed`, so
don't treat `check.failed` as final. Return any 2xx within 10 s; redirects
count as failures. Failed deliveries are retried twice (after 10 s and 60 s),
then dropped; `GET /api/v1/checks/{id}` stays the source of truth.

Verify every delivery. The `InspectMind-Signature` header is
`t=<unix seconds>,v1=<hex>`, where `v1` is HMAC-SHA256 of `<t>.<raw body>`
keyed with your webhook signing secret. Compare in constant time and reject
timestamps older than 5 minutes:

```python
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = f"{parts['t']}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300
```

## Download markups

`GET /api/v1/checks/{id}/markups.pdf` returns the completed check's findings
stamped onto one combined PDF as markup annotations: the same file as the
app's Bluebeam set export. It covers every finding the owner can see,
excluding dismissed ones (duplicates are stamped under their group).
Each call regenerates the file, which can take a minute or two on large
sets, so download it once per check.

| Code | Meaning |
|---|---|
| 200 | `application/pdf` |
| 409 | The check is not complete yet |
| 422 | The check has no findings to mark up |

## Revisions

`POST /api/v1/checks/{id}/revisions` uploads a revised set onto the same
project and runs a recheck, which compares against the previous check (fixed,
still open, new). Same fields, limits, and `Idempotency-Key` behavior as
`POST /api/v1/checks`; `notes` become the recheck notes. The response is a
new submission with its own `id`, status, and webhook.

`{id}`'s check must be complete and be the latest check on its project (so
post revisions to the most recent submission), with no other revision of it
still processing; otherwise `409`. Reusing an `Idempotency-Key` from a
different call returns `409`.

## Round trip

```bash
API=https://api.inspectmind.ai/api/v1/checks
AUTH="Authorization: Bearer $INSPECTMIND_API_KEY"

# 1. Submit drawings + calcs + QA/QC checklist
curl $API -H "$AUTH" -H "Idempotency-Key: job-1042-r0" \
  -F "subject=Job 1042 | 100 Main St" \
  -F "files=@Drawings.pdf" -F "files=@Calcs.pdf" \
  -F "checklist=@QAQC Checklist.xlsx" -F "checklist_profile=plan-review"
# → 202 {"id": "6f0c…", "status": "queued", …}

# 2. On the check.completed webhook (or polling GET $API/6f0c…), fetch markups
curl -fL "$API/6f0c…/markups.pdf" -H "$AUTH" -o "Job 1042 r0 markups.pdf"

# 3. Submit the revised set as a recheck on the same project
curl "$API/6f0c…/revisions" -H "$AUTH" -H "Idempotency-Key: job-1042-r1" \
  -F "subject=Job 1042 | 100 Main St | Rev 1" -F "notes=Revised per comments" \
  -F "files=@Drawings Rev1.pdf" -F "files=@Calcs Rev1.pdf"
# → 202 {"id": "9a21…", …}; its webhook fires when the recheck completes

# 4. Repeat 2-3 with the newest id (9a21…)
```

## Errors

| Code | Meaning |
|---|---|
| 400 | Invalid input; `details` lists every problem |
| 401 | Missing, invalid, or revoked key |
| 404 | Unknown id, or a submission owned by another account |
| 409 | Revision or markups requested before the check is complete, or a reused `Idempotency-Key` |
| 411 / 413 / 415 | Missing Content-Length / body over 100 MB / not multipart |
| 422 | A `file_urls` link isn't publicly downloadable, or no findings to mark up |
| 429 | Rate limit: 60 requests/min and 1,000/hour, 20 new submissions/hour and 100/day, per key. See `Retry-After` |
