Skip to main content
Documentation

Documents

documents.timeline

Read what has happened to one document — sent, opened, signed
Read-onlyRead budget

REST

Shipping
POST /api/v1/documents/timeline

MCP tool

Live
documents.timeline

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

Operating contract

THE PAPER TRAIL. Every recorded event on one document, newest first: uploaded, shared, opened by the customer, downloaded, signed.

THIS IS THE ANSWER TO 'DID THEY EVER SEE IT'. A waiver a customer never opened and a waiver they opened three times and did not sign are two completely different conversations, and nothing else in the product tells them apart.

summary is the event in plain words, written by the product. Quote it rather than translating an event type — the wording is what the shop reads on the document's own page.

AN EMPTY TIMELINE IS NOT PROOF OF NOTHING. Events are only recorded from the point the document rail started logging them, and a document filed before that carries no history. Say the trail is empty, not that nothing happened.

TRUNCATION: at most limit events come back, newest first. omitted is only ever an estimate of 'more than this' — an older event is not returned and is not therefore absent.

Who may call it

Permission
NoneThe document library sits outside the section gate — `documents/layout.tsx` guards dashboard membership and nothing narrower, because a document can hang off any record in the product and gating it on one section would hide a customer's waiver from the person holding their job. The timeline mirrors the panel on that same page.
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. There is no cursor here — narrow the filter instead.

Do not summarise from a truncated response

When `omitted` is not 0 the document has more history than was returned. Raise `limit` — there is no older-than cursor, because the trail is read newest-first from one capped window.

Input

FieldTypeDescription
document_idrequiredstringuuid

The document to trace, as returned by documents.list or documents.for_record.

limitinteger1–100

How many events to return, 1-100, newest first. Defaults to 30.

Default: 30

Output

FieldTypeDescription
document_idstring

itemsobject[]

items[].idstring

items[].occurred_atstring

items[].summarystring

totalnumber

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/timeline \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"document_id":"9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20","limit":30}'

TypeScript (fetch)
const res = await fetch("https://www.servicevin.com/api/v1/documents/timeline", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "document_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
    "limit": 30
  }),
});

// 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/timeline",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "document_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
    "limit": 30
},
    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.timeline",
    "arguments": {
      "document_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
      "limit": 30
    }
  }
}

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.