Documentation
Documents
documents.readiness
REST
ShippingPOST /api/v1/documents/readinessMCP tool
Livedocuments.readinessExposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Documents domain.
Operating contract
THE PRE-FLIGHT BEFORE A CUSTOMER IS HANDED THE TABLET. It resolves the shop's own template against this job's actual records and reports which values will print and which will print BLANK.
Why it matters: the renderer degrades a value it cannot find into a row of underscores, which is right for a document that has to print regardless — and exactly wrong as the first time anybody notices the VIN was never entered, because by then the customer has signed a contract that identifies their car by a blank.
ready: false with a non-empty blocking list means DO NOT SIGN YET. Those are the gaps that make the paperwork not paperwork — who is signing, and which vehicle.
recommended gaps are worth thirty seconds and do not hold the handoff. advisory gaps are genuinely unknowable at the counter right now — a pickup odometer at drop-off, an invoice total on a job with no invoice — and are reported without blame. Do not present an advisory gap as somebody's mistake.
Each gap names the RECORD AND FIELD it can be fixed in, which is the difference between 'the VIN is missing' and 'here is the box'. Filling them is a person at the counter looking at the car — this reports, and vehicles.update or customers.update is where a value actually lands.
doc_type is service_agreement (the intake paperwork) or vehicle_release (the handover). A shop that does not use one of them gets a refusal saying so rather than an empty report.
This resolves one document for one job and returns every field it carries, so nothing is truncated.
Who may call it
- Permission
- NoneThe signing station at `/documents/signing-station` is a counter surface open to every active member — whoever hands the customer the tablet runs this check, and that is often a technician rather than an admin. It reports gaps and writes nothing.
- Plan
- Every planNo plan gate. Available on every Service VIN plan.
- Retries
naturalNaturally idempotent — running it twice leaves the same world as running it once. A retrying integration needs no key.- Rate class
readCounted against the read budget — the widest of the four.
Input
| Field | Type | Description |
|---|---|---|
| job_idrequired | stringuuid | The job whose paperwork is being checked, as returned by jobs.list. |
| doc_typerequired | string | service_agreement for intake, vehicle_release for handover. One of:service_agreement, vehicle_release |
Output
| Field | Type | Description |
|---|---|---|
| job_id | string | — |
| doc_type | string | — |
| ready | boolean | — |
| resolved | number | — |
| total | number | — |
| request_status | string | null | — |
| blocking | object[] | — |
| blocking[].label | string | — |
| blocking[].tag | string | — |
| blocking[].fixable_field | string | null | — |
| recommended | object[] | — |
| recommended[].label | string | — |
| recommended[].tag | string | — |
| recommended[].fixable_field | string | null | — |
| advisory | object[] | — |
| advisory[].label | string | — |
| advisory[].tag | string | — |
Examples
Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.
export SERVICEVIN_API_KEY=svk_live_…
curl -X POST https://www.servicevin.com/api/v1/documents/readiness \
-H "Authorization: Bearer $SERVICEVIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"job_id":"9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20","doc_type":"service_agreement"}'const res = await fetch("https://www.servicevin.com/api/v1/documents/readiness", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"job_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"doc_type": "service_agreement"
}),
});
// Success and failure are both envelopes. Switch on error.code, never
// on error.message — the codes are stable, the messages are for people.
const payload = await res.json();
if (!res.ok) throw new Error(payload.error.code);
const data = payload.data;import os, requests
res = requests.post(
"https://www.servicevin.com/api/v1/documents/readiness",
headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
json={
"job_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"doc_type": "service_agreement"
},
timeout=30,
)
payload = res.json()
if not res.ok:
raise RuntimeError(payload["error"]["code"])
data = payload["data"]{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "documents.readiness",
"arguments": {
"job_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"doc_type": "service_agreement"
}
}
}Refusals
The four gates run in this order on every surface, and the order is not arbitrary — see Authentication.
| Status | Code | When |
|---|---|---|
| 404 | not_found | The id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both. |
| 422 | validation_error | An argument was wrong. The message names the field. |
| 429 | rate_limited | Too many read calls. Back off and retry. |
| 500 | internal_error | Something failed on our side. Nothing was changed. |