Writing Data: Customers, Jobs, Notes, Texts and Operations

How to change data through the API: create and update customers, add contacts, create jobs, add notes and send texts, all through validated endpoints that run the same code the app does.

Writing Data: Customers, Jobs, Notes, Texts and Operations

The API doesn't let you write arbitrary columns, and that's on purpose. Letting an outside system set raw fields is how invoices end up with a total that disagrees with their line items, or a job changes status without the side effects that are supposed to fire.

Instead, every change goes through an operation: a named, validated action that runs the same code the app runs when a person does the same thing. The common ones also have plain REST endpoints, so creating a customer is just POST /v1/accounts.

Everything here needs a read & write key. A read-only key gets 403.

Why this matters for you

When your booking site creates a customer through the API, it gets everything a customer created in the app gets: a unique customer number, the contact linked properly, a service location for the dispatcher, and your New Customer automations firing. When Zapier creates a job, the job gets its job number, its forms and contract text, an invoice if you auto-create them, and its workflow if its job type has one. A raw insert would have given you a row and none of that.

The write endpoints

All take JSON, and all answer with the record exactly as the matching GET returns it (201 Created, or 200 for an update). You never need a second call to see what you made.

Create a customer: POST /v1/accounts

curl -X POST "https://your-account.suprata.com/api/v1/accounts" \
  -H "Authorization: Bearer sk_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"account_type": "individual", "first_name": "Grace", "last_name": "Hopper",
       "email": "grace@example.com", "phone": "+1 (786) 555-0199",
       "address1": "200 Dock St", "city": "Key Biscayne", "state": "FL", "zip": "33149", "country": "US"}'
Field Notes
account_type individual (default) or business.
organization_name Required for a business.
first_name, last_name At least one: every customer has a named primary contact, and that's the name the app shows for an individual.
email, phone, alt_phone Phones in any format. They're stored as digits, with a leading +1 dropped, the way the app stores them.
address1, address2, city, state, zip, country country is a two-letter code (US, CA, DO).
notes Account notes.

The name, phones, email and address become the primary contact, and the customer's billing details are copied from it, the same as ticking "same as primary contact" in the app.

Check before you create. Search first (GET /v1/accounts?email=… or ?phone=…) and only create when nothing matches. The API won't stop you making a second Grace Hopper, and neither does the app. In Zapier this is the Find or Create Customer step.

Update a customer: PATCH /v1/accounts/{id}

Send only the fields you want to change. Anything you leave out, or send empty, is left alone, so a sync that only knows the email can't blank the address. (The flip side: you can't clear a field through the API. Do that in the app.)

first_name / last_name change the primary contact, because that's where the app keeps an individual's name. The update fires your Account Updated automations with a before-and-after, so "email changed" conditions work.

Add a contact: POST /v1/contacts

account_id plus the person fields above. The contact is added after the customer's existing contacts, so the primary contact doesn't change. Fires Contact Created.

Create a job: POST /v1/jobs

Field Notes
account_id Required.
summary Required: what the job is.
job_type_id From GET /v1/job_types. Defaults to your default job type.
business_unit_id From GET /v1/business_units. Defaults to your default unit.
status_id From GET /v1/job_statuses. Jobs are created Open; a different status is applied straight after, with that status's usual side effects (and the Job Status Change trigger).
on_site true = at the customer's location, false = in the shop. Defaults to your company setting.

Fires Job Created. If the status couldn't be applied, the job is still created and the reply carries a warning.

Add a note: POST /v1/notes

note plus exactly one of account_id or job_id. Returns the note with its id.

Send a text: POST /v1/sms

to (any format), message (up to 1,600 characters), and optionally account_id to file the text in that customer's conversation. It goes from your Suprata texting number and is billed like any other text. Suprata refuses, with a reason, when:

  • the number has opted out (they replied STOP): 422;
  • the number is a landline: 422;
  • texting isn't on your plan (403), your prepaid balance is empty (402), or there's no texting number set up yet (409).

None of those are worth retrying as-is. A 422 about opt-out means the person asked not to be texted; respect it.

Operations: the full list

The endpoints above are shortcuts. Underneath, each is an operation, and every operation can be called by key:

GET  /v1/operations          # what's available, with each one's parameters
POST /v1/operations/{key}    # run one, parameters as the JSON body
Operation Does Shortcut
account.create Create a customer with a primary contact POST /v1/accounts
account.update Change only the given customer fields PATCH /v1/accounts/{id}
contact.create Add a contact to a customer POST /v1/contacts
job.create Create a job POST /v1/jobs
job.update_status Move a job to a new status, with that status's side effects
note.create Note on a customer or job POST /v1/notes
sms.send Send a text POST /v1/sms
invoice.recalculate Recompute an invoice's subtotal, tax and total from its lines

Run through /v1/operations, a success returns 200 with a compact result ({"object": "operation_result", "operation": "job.update_status", "result": {"ok": true, "job_id": 5120, "status_id": 3}}). Prefer the shortcuts when one exists: they return the full record.

A failure returns a 4xx with an error sentence and nothing changed: 422 for a validation problem (a missing parameter, an id that doesn't exist), 404 when the record you named isn't there. Operations not yet safe to run headless don't appear in the list at all.

Why not just PATCH any field?

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

  • 403 on every write. The key is read-only. Scope is fixed at creation; issue a new read & write key.
  • 422 with "required" or "does not exist". You omitted a field, sent it under the wrong name, or pointed at an id that isn't there. The message names the field. Check GET /v1/operations for the exact parameter names.
  • Duplicate customers. Creating is not idempotent. Search by email or phone first, or use Find or Create in Zapier.
  • A PATCH "didn't clear" a field. By design: empty means "not sent". Clear it in the app.
  • Texts to a number that worked yesterday now fail. They probably replied STOP. That's a person opting out, not an error to work around.
  • Batching. There's no bulk endpoint; call once per record and keep within the rate limit of 120 requests per minute per key. On a 429, wait for Retry-After and continue.