Skip to main content
Documentation

Documents

documents.signature_requests

Check which signature documents are signed, sent or waiting
Read-onlyRead budget

REST

Shipping
POST /api/v1/documents/signature_requests

MCP tool

Live
documents.signature_requests

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

Operating contract

The signing board: every document put in front of a customer to sign, and where each one got to.

status walks one way — draft, sent, viewed, signed, declined, expired. The interesting state is SENT-AND-NOT-VIEWED after a day or two: that is usually a text that never arrived rather than a customer ignoring it.

declined carries a decline_reason in the customer's own words. Read it rather than re-sending: a customer who declined and was sent the same document again has been ignored twice.

signed_at is when they signed and signed_document_id is the countersigned copy, which documents.get opens.

expires_at in the past with no signature means the link is dead. A new request is a person's decision, not a retry.

This is gated on customers.access rather than on nothing, unlike the rest of this domain: a signature request is a legal instrument carrying a customer's name and a link that opens their document, so enumerating who has signed what is a customer read rather than a filing one.

TRUNCATION: omitted is how many further requests match. When it is not 0, narrow by status or by job before summarising what is outstanding.

Who may call it

Permission
customers.accessThe caller must hold Customers at the ACT level. A read-only dashboard grant on the same section is refused.
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 there are more signature requests than came back. Narrow by `status`, `job_id` or `customer_id` before summarising what is outstanding.

Input

FieldTypeDescription
statusstring

One request status. Omit for every status.

One of: draft, sent, viewed, signed, declined, expired
job_idstringuuid

One job's id, as returned by jobs.list.

customer_idstringuuid

One customer's id, as returned by customers.list or customers.get.

limitinteger1–100

How many requests to return, 1-100. Defaults to 50.

Default: 50

Output

FieldTypeDescription
itemsobject[]

items[].idstring

items[].titlestring

items[].statusstring

items[].customer_idstring | null

items[].job_idstring | null

items[].document_idstring

items[].signed_document_idstring | null

items[].sent_atstring | null

items[].viewed_atstring | null

items[].signed_atstring | null

items[].declined_atstring | null

items[].decline_reasonstring | null

items[].expires_atstring | null

items[].created_atstring

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/signature_requests \
  -H "Authorization: Bearer $SERVICEVIN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"limit":50}'

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

// 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/signature_requests",
    headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
    json={
    "limit": 50
},
    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.signature_requests",
    "arguments": {
      "limit": 50
    }
  }
}

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.
403forbiddenThis login does not hold customers.access.
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.