Skip to main content
Documentation

Documents

documents.list

Search the shop's document library by title, kind or tag
Read-onlyRead budget

REST

Shipping
POST /api/v1/documents/list

MCP tool

Live
documents.list

Exposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Documents domain.

Operating contract

The filing cabinet: photos, PDFs, contracts and signed paperwork, newest first, with what each one is filed against.

filed_against is the useful column. A document can be attached to several records at once — the same photo can sit on a job, a vehicle and a warranty claim — and each entry names the record type, its id and a human label. Those ids are what customers.get, jobs.get, vehicles.get and the rest take.

kind separates what a file IS: photo and video are the work, contract and signature are paperwork somebody signed, pdf is everything else printed.

THE FILE ITSELF IS NOT HERE, and there is no capability that returns it. This read gives you a document's metadata; a URL to its bytes would be a bearer link into the shop's private storage, and a read that hands one out is a distribution channel wearing a search box.

q matches the title and the stored filename, so a document nobody titled is still findable by what it was called when it was uploaded.

TRUNCATION: total is the exact number of documents matching the filters and omitted is how many are not in this response. When omitted is not 0, call again with page incremented.

Who may call it

Permission
NoneThe library is open to every active member of the shop — `/documents` is guarded by `requireShopContext()` and by no section at all, because a technician needs the photos of the car they are working on. Gating this would make a machine see less of the shop's own filing than the bench does.
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.

Partial answers

omitted is how many matching records are not in the response, and 0 is a real answer meaning you have all of them. Continue with page.

Do not summarise from a truncated response

When `omitted` is not 0 there are more documents matching these filters. Call again with `page` set to one more than the `page` on this response.

Input

FieldTypeDescription
qstringmax 120 chars

Part of a document's title or its original filename.

kindstring

One document kind — photo, video, pdf, signature, contract or other.

One of: photo, video, pdf, signature, contract, other
tagstringmax 60 chars

One tag, exactly as it appears on another document's `tags`.

pageinteger1–1000

1-based page number. Defaults to 1.

Default: 1

Output

FieldTypeDescription
itemsobject[]

items[].idstring

items[].titlestring | null

items[].kindstring

items[].content_typestring | null

items[].size_bytesnumber | null

items[].tagsstring[]

items[].created_atstring

items[].filed_againstobject[]

items[].filed_against[].owner_typestring

items[].filed_against[].owner_idstring

items[].filed_against[].labelstring

items[].filed_against[].rolestring | null

totalnumber

pagenumber

page_sizenumber

omittednumber

Examples

Built from this capability's own schema — required fields and the ones carrying a default, and nothing invented. Paste one and it validates.

curl
export SERVICEVIN_API_KEY=svk_live_…

curl -X POST https://www.servicevin.com/api/v1/documents/list \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"page":1}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/documents/list", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "page": 1
  }),
});

// 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;

Python (requests)
import os, requests

res = requests.post(
    "https://www.servicevin.com/api/v1/documents/list",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "page": 1
},
    timeout=30,
)
payload = res.json()
if not res.ok:
    raise RuntimeError(payload["error"]["code"])
data = payload["data"]

MCP tools/call — https://www.servicevin.com/api/mcp
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "documents.list",
    "arguments": {
      "page": 1
    }
  }
}

Refusals

The four gates run in this order on every surface, and the order is not arbitrary — see Authentication.

StatusCodeWhen
404not_foundThe id is unknown, or the feature is not enabled for this account. Deliberately the same answer for both.
422validation_errorAn argument was wrong. The message names the field.
429rate_limitedToo many read calls. Back off and retry.
500internal_errorSomething failed on our side. Nothing was changed.