Writing Data: API Operations
The API doesn't let you PATCH a row directly, and that's on purpose. Letting an outside system write arbitrary columns is how invoices end up with a total that disagrees with their line items, or a job whose status changed without the side effects that are supposed to fire.
Instead, changes go through operations — named, validated actions that wrap the same internal logic the app and the automation engine use. Call one by key with parameters; it validates, applies the change with its side effects, and returns a result.
Everything here requires a read & write scoped key. A read-only key gets a 403.
Discovering what you can do
GET /v1/operations
returns the list of operations available to your account, each with its key, a human label, and the parameters it expects. Read this at integration time rather than hard-coding a list — operations are added over time.
A typical entry looks like:
{
"key": "invoice.recalculate",
"label": "Recalculate Invoice Totals",
"params": [
{ "key": "invoice_id", "type": "integer", "required": true }
]
}
Running an operation
POST to the operation's key, with the parameters as a JSON body:
curl -X POST "https://your-account.suprata.com/api/v1/operations/invoice.recalculate" \
-H "Authorization: Bearer sk_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"invoice_id": 12345}'
A success returns 200 with a small result object describing what happened:
{ "ok": true, "invoice_id": 12345 }
A validation failure — a missing required parameter, an id that doesn't exist, or an operation that couldn't complete — returns 422 with an error message explaining why. Operations validate their inputs before doing anything, so a 422 means nothing changed.
What you can do today
The set grows, so always confirm against GET /v1/operations, but the core operations are:
invoice.recalculate— recompute an invoice's subtotal, tax, and total from its line items. Parameters:invoice_id(required). Use this after changing an invoice's contents through another path and needing its totals brought back in sync.job.update_status— move a job to a new status, running the same status side effects as the UI (rather than a blind column write). Parameters:job_id,status_id.
Some operations are intentionally not yet exposed where the underlying logic isn't safe to run headless — they'll appear in GET /v1/operations when they are.
Why not just PATCH a record?
Because a record is rarely just a record. Approving an estimate converts it to a job and checks a plan cap. Closing an invoice consumes stock cost layers. Changing a job's status can auto-create an appointment. An operation carries all of that with it; a raw column write carries none of it and quietly corrupts your data. If you find yourself wishing an operation existed for something, that's the right instinct — ask us to add it rather than reaching around it.
Common stumbling blocks
403on every write. The key is read-only. Scope is fixed at creation — issue a new read & write key.422with "required". You omitted a parameter, or sent it under the wrong name. Check the operation'sparamsfromGET /v1/operations.- Expecting an operation to return the whole updated record. It returns a compact result (
ok, plus a couple of ids), not the full object. Re-fetch the record with aGETif you need its new state. - Batching. There's no bulk operation endpoint — call the operation once per record. Respect a sane request rate; don't fire thousands in a tight loop.