Documentation
Invoices
invoices.record_payment
REST
ShippingPOST /api/v1/invoices/record_paymentMCP tool
Liveinvoices.record_paymentExposed on: REST API · Shop MCP server · Claude connector · Dash (in-app copilot) · Zapier. Part of the Invoices domain.
Operating contract
WRITES DOWN MONEY THE SHOP HAS ALREADY BEEN HANDED — cash at the counter, a cheque, an e-transfer that landed, a card taken on somebody else's terminal. It CANNOT charge anybody: no card is touched, no payment is requested, nothing reaches the customer.
Only ever call this when a person has told you the money arrived. A model that records a payment because a customer SAID they would pay has written a shop's balance down against money that does not exist, and the customer stops being chased for it.
method says how it came in and it must be true — the reconciliation between the ledger and the shop's bank reads it. received_on is the civil date in the shop's own timezone; omit it and today is used.
tip rides ALONGSIDE the payment and never comes off the balance. A $100 payment with a $20 tip settles $100 of the bill.
Refused when the amount is more than the balance due, when the invoice is still a draft (issue it first), and when it is void or refunded. Read invoices.get for the current balance rather than assuming the one you last saw.
IDEMPOTENCY IS none: calling this twice records the money twice, and there is no key that makes it not. Read payments.list for this invoice before recording anything you are not certain about.
Who may call it
- Permission
invoices.accessThe caller must hold Invoices 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
noneA repeat genuinely does it twice and no key can make it not. Read before you write; the operating contract says what to read.- Rate class
writeCounted against the write budget, which is tighter than a read.
Input
| Field | Type | Description |
|---|---|---|
| invoice_idrequired | stringuuid | The issued invoice, as returned by invoices.list or invoices.get. |
| amountrequired | numbermax 10000000 | How much was received, in the shop's currency. Must not exceed the balance. |
| methodrequired | string | How the money arrived. Must be the truth — the bank reconciliation reads it. One of:cash, card, cheque, etransfer, ach, other |
| tip | number0–10000000 | A gratuity received alongside. Never applied to the balance. |
| received_on | string | The civil date it arrived, YYYY-MM-DD in the shop's timezone. Defaults to today. |
| note | stringmax 500 chars | A short note for the ledger — a cheque number, who took it. |
Output
| Field | Type | Description |
|---|---|---|
| invoice_id | string | — |
| recorded_amount | number | — |
| already_recorded | boolean | — |
| status | string | — |
| balance | number | — |
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/invoices/record_payment \
-H "Authorization: Bearer $SERVICEVIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"invoice_id":"9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20","amount":1,"method":"cash"}'const res = await fetch("https://www.servicevin.com/api/v1/invoices/record_payment", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SERVICEVIN_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"invoice_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"amount": 1,
"method": "cash"
}),
});
// 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/invoices/record_payment",
headers={"Authorization": f"Bearer {os.environ['SERVICEVIN_API_KEY']}"},
json={
"invoice_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"amount": 1,
"method": "cash"
},
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": "invoices.record_payment",
"arguments": {
"invoice_id": "9b2f1c6e-4a77-4d2b-9f31-0f1c9a8e5d20",
"amount": 1,
"method": "cash"
}
}
}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. |
| 403 | forbidden | This login does not hold invoices.access. |
| 403 | insufficient_scope | The credential is read-only and this capability writes. |
| 422 | validation_error | An argument was wrong. The message names the field. |
| 429 | rate_limited | Too many write calls. Back off and retry. |
| 500 | internal_error | Something failed on our side. Nothing was changed. |