Priority OData Errors: Telling an Invalid Filter From a Permission or a Missing Record
Back to blog
full stack·September 5, 2026·4 min read·By Yehonatan Saadia

Priority OData Errors: Telling an Invalid Filter From a Permission or a Missing Record

Priority returns standard HTTP status codes, and since 19.1 the error body is JSON. How to read 400, 403, 404 and 429, and narrow a filter down to one clause.

Key takeaways

  • The status code separates three problems that need three different fixes: syntax, permission or licensing, and a record that does not exist.
  • From version 19.1, Priority REST API errors are returned in JSON rather than XML. If your code still parses XML, that is your bug.
  • A 404 from Priority is deliberately ambiguous: either the form is not exposed, or the record itself is absent.
  • The only reliable technique is reduction: one record, one field, no filter - then add one component at a time.
  • A query that works from your browser and fails from the integration is almost always a different user, a different company, or licensing.

"The Priority API returned an error" is not a diagnosis. Priority returns standard HTTP status codes - 400 for a bad request, 404 for a form or record not found, 409 for a conflict, 429 for throttling, 500 on the server side - and since version 19.1 the error body comes back as JSON. The code tells you which layer to look at.

Three layers hiding behind the word "error"

An OData request to Priority passes through three independent layers before it returns data, and each fails differently:

  • Protocol and syntax. The URL, the entity name, the shape of $filter, the value types. This is where 400 lives.
  • Identity, permission and licensing. Who the user is, whether they can reach that form, and whether API licensing is in place. This is where 401 and 403 live, and sometimes 404 too.
  • Business data. The syntax is right, the permission exists, and no record satisfies the condition.

The confusion comes from all three arriving through the same channel. Without recording the code and the response body, they look identical in a log file.

What each status code tells you to check

CodeWhat happenedWhere to look first
400Malformed request$filter syntax, field name, value type, quoting
401Not identifiedAuthentication method, personal access token, changed password
403Identified, not permittedUser's form privileges, API licensing, operation type
404Not foundEntity name, whether the form is exposed to the API, or a missing record
409ConflictA POST against a record that already exists with that key
429Too many requestsCall frequency, several processes pulling in parallel
500Server-side failureServer availability, then support with the full trace

How do you narrow a broken filter to one clause?

With one method that always works: strip everything, then restore a single component at a time.

  1. Call the entity with no $filter, no $select and no expansion, limited to one record. If even that fails, the problem is access to the entity, not the query.
  2. Add $select on a single field. A failure here means the field name is wrong or not exposed.
  3. Add one filter clause, on a numeric field if you can. Text and date fields are the usual source of syntax errors.
  4. Add the second clause, then the third. The clause that breaks the request is the culprit.
  5. Only at the end add subform expansion and ordering.

The value of that order is that each step answers a different question, so at the end you have a diagnostic sentence rather than a hunch.

Why does the query work for me and fail from the integration?

Because it is not really the same query. Three differences account for nearly every case:

  • A different user. You run as an account with broad privileges; the integration runs as a narrow service user. Priority restricts access at form level, so that entity simply does not exist for it.
  • A different company. In a multi-company environment the same entity holds entirely different records, and a field populated in one may be undefined in another.
  • Licensing. API access requires the API module and matching licences, and from version 18.3 Priority supports application-level licence headers - X-App-Id and X-App-Key. An integration that does not send them behaves differently from your browser session.

Missing data or a field that does not exist

This is the distinction that costs the most time, because both outcomes look like an empty result.

  • A 200 with an empty array means syntax and permissions are fine. Nothing satisfies the condition, which is a business question, not a technical one.
  • A 400 means the name you wrote is not a valid field on that entity. Compare against the entity metadata, not against the on-screen form.
  • A 404 means either the form is not exposed to the API in this environment, or the key you asked for does not exist.
  • A 200 with records but one field empty on all of them usually means the field exists but is unpopulated in the company you connected to.

Three calls worth keeping as a health check

An integration that only exercises itself through the business flow discovers faults only when there is work to do. Three standing calls, scheduled and reporting only when their result changes, remove most of the "nothing works" conversations.

  • One entity, one record, no filter. If this fails, the problem is connectivity, authentication or licensing, and there is no point looking at any other query.
  • A filter on a numeric field known to return a result. If it comes back empty, something changed in the data or in the company that was selected.
  • A call against a form requiring higher privilege than the routine ones. It exposes a change to the service user's permissions before the integration falls over in the middle of a working day.

The value of all three is that they separate the layers before anybody opens a ticket. When a complaint arrives you already know whether the server answers, whether the user is recognised, and whether the data is where it should be.

What to record on every failed attempt

A consistent record per failed call turns a recurring incident from an argument into a check. Store:

  • The full URL without secrets, including the query string exactly as it was sent.
  • Entity name, selected fields and filter, each in its own field.
  • The status code and the complete response body, not a truncated message.
  • The user the call ran as, and the company it selected.
  • A timestamp and a sample record ID somebody can open manually on screen.

Without the response body there is no retrospective diagnosis. Code that catches an exception and throws "API error" erases exactly the information that would have solved it.

Related: integrating with the Priority API, common Priority API errors, Priority webhooks and triggers.

Sources

#Priority ERP#OData#REST API#error handling#integration#Priority

Frequently asked questions

What format do Priority API errors come back in?

Starting with Priority version 19.1, errors returned by the REST API are formatted as JSON instead of XML. An older integration that parses XML will fail to read the error body even when the call itself is perfectly well formed, and will log a misleading message for whoever reads it later.

I got a 404 - is the entity missing or is it a permission?

Both produce a 404, which is why the code alone is not enough. Check first that the form is actually exposed to the API in the environment you are connected to, then repeat the same call as a user with broad privileges. If it succeeds, you have a permission problem.

What should I do about a 429?

A 429 signals that you exceeded the allowed request rate. Reduce frequency, add increasing backoff between retries, and make sure two processes are not pulling the same data in parallel. A full extract running every minute where a change-only extract would do is the usual cause.

Do I need a special licence to write data through the API?

Yes. API access in Priority depends on the API module and matching licences, and writing data also consumes transaction packages. If read calls succeed and write calls fail consistently, that is the first thing to check with the vendor before changing any code.

Keep reading

Related service

Integrations

Make the systems you already pay for talk to each other.

Learn more →←

About the author

Yehonatan Saadia

Freelance automation, web & MVP developer

I'm Yehonatan Saadia, a senior developer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.

Work with me

Have a project like this?

Tell me what you're trying to automate or build. I reply within 24 business hours with a few targeted questions, then we walk through it on a free 30-minute call, with no commitment. You come away with a scope, a timeline and a fixed price - or a straight answer that it isn't worth building.