1. List endpoints are POST, not GET
You'll see 405 Method Not Allowed on something like GET /v1/customers.
Every list in Striven is a search. You POST a filter body to /v1/{entity}/search, even when you want everything. Single records are a normal GET /v1/{entity}/{id}.
Do this Treat POST .../search as your list call, and tell your retry logic it's a read. That matters for #9.
3. The Swagger spec is wrong about response casing
You'll see undefined where you expected an id, and then something like The value 'undefined' is not valid for Int32. on the next call.
The spec documents PascalCase responses (Id, PONumber, LineItems). The live API returns camelCase (id, poNumber, lineItems). Request bodies go the other way: search filters and writes use PascalCase, as documented. Even list wrappers come back as { totalCount, data } instead of the documented Data.
// Write: PascalCase, capital Id
POST /v1/sales-orders
{ "Customer": { "Id": 42 }, "PaymentTerms": { "Id": 3 }, ... }
// Read: camelCase, whatever the spec says
GET /v1/sales-orders/1001
{ "id": 1001, "customer": { "id": 42, "name": "..." }, "lineItems": [ ... ] }
Do this Type your responses from real captured responses, not from the spec. Be forgiving about casing when you read and strict when you write.
4. Writes silently drop fields they don't recognize
You'll see A 200, and a record that's missing the field you sent.
Send customer instead of Customer, or a field name that entity doesn't use, and Striven drops it. No 400. Field names don't always match the UI labels either. On a sales order it's orderName, not name, and customerPONumber, not clientPO. Different document types can also use different names for the same idea, so check each entity's schema instead of copying a payload from another one.
Read-only fields get the same treatment. In our testing, purchase orders created through the API always came back as fixed cost no matter what we sent for IsFixedCostPO. Only the UI checkbox changes it.
Do this Read every record back after you write it, at least until you trust that write path.
5. The API won't save a sales order the UI would
You'll see A 400 listing missing fields on POST /v1/sales-orders.
In the Striven UI you can save a half-finished estimate and come back to it. The API wants a complete record: order type, customer, bill-to and ship-to locations, payment terms, at least one line item, and a tax source (from the ship-to location, the tenant default, or set explicitly). If your tenant marks custom fields as required, or has class tracking turned on, those join the list.
Do this Decide up front what happens when you can't match a customer, because there's no draft to fall back on. Our answer was a placeholder customer that the team reassigns by hand.
6. Deactivated records look exactly like active ones
You'll see Value passed for Custom Field Id N does not exist on a create that worked yesterday. Or ShipTo Location with Id: N does not exist or does not match Customer, followed by a pile of tax errors.
When someone in Striven cleans up a custom list or merges duplicate locations, the old entries are deactivated, not deleted. Old records still show them when you read them. New creates reject them. Locations are the worst case: GET /v1/customers/{id}/locations returns active and inactive locations together with nothing that tells them apart. The tax errors are a side effect, since tax comes from the ship-to location.
Templates and copied estimates can also carry a deactivated option forward, so a record your team built in the UI can be one the API refuses to copy.
Do this Never hardcode an option or location id. Resolve custom-list options by name at request time and skip anything with active: false. Get locations from POST /v1/customer-locations/search with Active: true.
7. Permissions are per module, and read and write are separate
You'll see 403 Access Denied with a perfectly good token.
API access runs as the employee you pick as the resource owner when you enable the API, and that person's role has to allow each module the integration touches. View and create/edit are separate grants, so a GET working on a module tells you nothing about whether a POST will. Lookups need their own grants too, custom lists included.
A sandbox tenant's role may also not match production, so a write can pass every test you have and still 403 on day one in prod.
Do this Write down every module and verb your integration uses and confirm each one in production before go-live. Grant only what's on that list. It's an account with access to your books.
Stuck on a Striven integration? Book a free 15-minute call. Tell me what you're connecting and where it's breaking, and we'll figure out whether it's a quick fix or a bigger job.
Book a Free 15-Min Call 8. Rate limits are per tenant, not per integration
You'll see 429 Too Many Requests from a client that's barely doing anything.
Striven's documented limits are 100 requests a minute and 5,000 a day on Standard, and 500 a minute and 25,000 a day on Enterprise. They're shared by every API client on the account, so another integration, or your own backfill script, can use up the budget for everyone. The daily count resets at midnight UTC.
Every 429 comes with a Retry-After header. A per-minute 429 clears in seconds. A daily-limit 429 doesn't clear until the reset, so retrying it just burns time.
Do this Run one rate limiter for everything in your process instead of one per job, pace calls evenly, and track daily usage yourself so you hear about it well before the cap. Schedule big backfills overnight, not at 2pm on a Tuesday.
9. Don't blindly retry creates
You'll see Two sales orders with the same number.
You can set your own document numbers at create time (OrderNumber on sales orders, PONumber on purchase orders, TxnNumber on invoices), which is handy for keeping numbers in step with another system. But Striven accepts duplicates without complaint. We also see the occasional 522 or 524 timeout from Striven's side, and after a timeout you don't know whether the create went through.
Do this Retry reads (GETs and search POSTs) freely. Never replay a create after an ambiguous failure. Check whether the record exists first.
10. There's no PATCH
There's no PUT or PATCH on a single record. The create endpoint is documented as "creates new or updates existing", so you POST the record with its Id to update it. It wants the whole record echoed back, not just the field you changed. We've verified this on purchase orders and haven't tested it on every entity.
Related: when someone creates a purchase order from a sales order in the Striven UI, the PO gets Striven's own number, not the sales order's. If you want the numbers to match, something has to set it.
Do this Treat updates as read, modify, send everything. Leave out read-only totals. Try it on a sandbox record before you point it at production.
11. Finding a sales order's paperwork is a scan
There's no search filter for "everything tied to sales order X." The link lives in a different place on each document: invoices carry the order on the header, while purchase orders and bills carry it on each line item. The date filters aren't consistent either. Invoices take flat LastUpdatedDateFrom and LastUpdatedDateTo, bills take a LastUpdatedDateRange object, and purchase orders have no last-updated filter at all, so you sweep created dates and status-change dates and dedupe.
One more. An invoice's total comes back on search results, but a GET of the single invoice only gives you the open balance.
Do this Sweep recent activity per document type, match on line items, and keep the totals you get from search.
12. Vendors don't have email addresses
The vendor record has no email field. Email lives on a contact that you create and then associate with the vendor, flagged as primary. Vendors and subcontractors are the same entity with a flag, whatever your tenant calls them.
Vendor name search is a contains match, so searching "Acme" also returns "Acme Plumbing" and "North Acme." Some names also come back with an account number baked in, like #1234 - Acme.
Do this Filter for exact matches yourself, and strip account-number prefixes before you compare names.
13. Sandbox and production don't match
A test tenant can be missing modules your production tenant has, and the ids for order types, custom lists and classes won't line up between them. Even the labels are configuration. One tenant's "Customers" and "Vendors" are another's "Clients" and "Subcontractors", on the same endpoints.
Do this Look things up by name and cache them, keep config per environment, and don't copy ids from one tenant to the other.
14. One we haven't cracked: creating customers
You'll see Customer Status is required, whatever you send.
We tried the status as an object with an id (both casings), as a flat id field, and wrapped in a customer object. Every version came back with the same 400. Striven's own Zapier app can create customers, so there is a shape that works. We just haven't found it, and for now we create customers in the Striven UI.
If you've figured it out, email me and I'll add it here with your name on it.