Striven API gotchas

Striven's API is perfectly usable, but a lot of what you need to know isn't in the docs, and a few things in the docs are wrong. This is the list we wish we'd had before building a production integration on it.

Most of this came out of building and running the Zoho Desk and Striven integration for Donastar, which has been in production since spring 2026.

Striven publishes API docs and a Swagger spec. Start there. Everything below is the stuff that isn't in them, or that works differently once you're actually calling the API.

A lot of these don't throw an error. Striven tends to ignore what it doesn't understand and hand back a 200, so the first sign of trouble is usually a record that's missing something, and nobody notices for a week.

Each one has what you'll see, what's going on, and what to do about it. Last reviewed October 2026. If Striven has changed something here, email me and I'll update it.

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.

Integrations break for boring reasons. Striven ships an update, someone cleans up a custom list, someone changes a role, and a sync that worked on Friday doesn't on Monday.

So for every integration we run, we watch Striven's release notes and docs and open tickets against our own code when something is coming. Honestly, most of the list above would have been a lot cheaper if we'd been doing that from day one.

If you're building on Striven yourself, I hope this saves you a few afternoons. If you're connecting Striven to Zoho Desk specifically, the integration guide covers your options and the Zoho side.

FAQ

Striven API questions.

Does the Striven API support pagination?

Yes. Striven search endpoints take PageIndex (zero-based) and PageSize (up to 1,000) in the POST body, spelled in PascalCase. Lowercase names like page, offset or skip are silently ignored, so you get the first page back every time. Loop PageIndex until you have collected totalCount, and dedupe by id.

Why is Striven ignoring a field I send?

Striven drops fields it does not recognize on writes instead of returning an error. Reference objects need PascalCase with a capital Id, like Customer: { Id: 42 }, and some field names differ from the labels in the Striven UI (a sales order uses orderName and customerPONumber, for example). Read the record back after a write to confirm what was saved.

Is the Striven Swagger spec accurate?

Mostly for requests, not for responses. The spec documents PascalCase response keys, but the live API returns camelCase (id, poNumber, lineItems). Request bodies do use PascalCase as documented. Type your response shapes from real captured responses instead of the spec.

What are the Striven API rate limits?

Per Striven's documentation, the Standard plan allows 100 requests a minute and 5,000 a day, and the Enterprise plan allows 500 a minute and 25,000 a day. Limits are per tenant, so every API client on the account shares them, and the daily count resets at midnight UTC. Over the limit you get a 429 with a Retry-After header.

Why does Striven return 403 Access Denied with a valid token?

The API runs as the employee chosen as the resource owner, and that person's role needs permission for every module the integration touches. View and create/edit are granted separately, so a working GET does not mean a POST to the same module will work. Check each module and verb in production before going live, and grant only what the integration needs.

Why will the Striven UI save a sales order that the API rejects?

The UI lets you save an incomplete estimate and finish it later. The API does not. A sales order create needs the order type, customer, bill-to and ship-to locations, payment terms, at least one line item and a tax source, plus any custom fields your tenant marks as required.

Building on Striven?

Book a free 15-minute call. We'll talk through what you're connecting and where it's getting stuck.

Book a Call