RQL Advanced Tips
Advanced RQL patterns, pitfalls, and best practices from real-world API usage.
This page expands on the Resource Query Language with patterns and pitfalls learned from building integrations against the Marketplace Platform REST API. Use it when you need to write correct, efficient RQL and avoid common errors.
1. Discover fields before writing RQL
RQL filter and sort expressions only work with fields that exist on the resource. The API returns errors (e.g. "Unknown expression group" or 400 Bad Request) if you use a field name that does not exist or is not exposed for filtering.
What to do
Use the resource schema (OpenAPI or your API documentation) to see the exact property names and types for each resource.
Do not invent field names. For example, there is no generic
subscriptionsCountfield on agreements; the number of subscriptions is not a filterable RQL field. To get "agreements with more than N subscriptions" you must either:Query the child resource (e.g. subscriptions) and filter by parent (e.g.
agreement.id), orFetch agreements with
select=+id,+name,+subscriptions.id,+subscriptions.nameand compute or filter in your application.
Summary
gt(subscriptionsCount,5) on agreements
Query subscriptions filtered by agreement.id, or fetch agreements with +subscriptions and count/filter in code.
Filtering by a field you assume exists
Check the resource schema first; use only documented fields.
order=created when the field is under audit
Use audit.created.at and include select=audit (see below).
2. How RQL is sent (query string, not a parameter)
A very common source of 400 errors is sending RQL as a single query parameter.
Wrong
Many clients will send this when given a params object like { "rql": "eq(status,Published)" }. The API typically responds with 400 Bad Request or "Invalid equals shortcut expression" because it does not treat rql as a parameter whose value is the expression.
Correct
Append the RQL expression as the query string (or as the first part of it):
When you have both RQL and other parameters (e.g. limit, offset), combine them with &:
So in code: build the URL so that the RQL string is the first query segment; then append &limit=...&offset=... (and &select=..., &order=...) as needed. Do not put the whole RQL inside a single rql= parameter.
Do not mix parameters inside the RQL string
Wrong: rql="ilike(name,*Microsoft*) limit=20" or rql="eq(status,Active)&order=-created" — limit and order are not part of RQL; they are separate query parameters.
Correct: Send RQL as the filter only (e.g. ilike(name,*Microsoft*) or eq(status,Active)), and pass limit, offset, select, and order as separate parameters. The final URL will combine them: ?eq(status,Active)&limit=20&order=-audit.created.at.
3. IDs and special characters in RQL values
ID values (e.g.
ACC-1234-5678,PRD-xxx,BUY-xxx,USR-xxx) must be in double quotes in RQL. Example:eq(client.id,"ACC-4402-5918"),eq(buyer.id,"BUY-1234-5678"). Unquoted IDs can return zero results.Special characters (spaces,
&,?, etc.) in string values must be enclosed in double or single quotes:eq(firstName,"Buzz !!!").To use a literal asterisk inside an
ilikepattern, escape it:ilike(name,"The\**").
4. Audit fields and the select parameter
Date and audit metadata (created, updated, etc.) live under the audit object. The API behavior is:
Filtering or sorting by audit fields (e.g.
audit.created.at,audit.updated.at) only works if the audit data is in scope for the response. That means you must includeauditin theselectparameter when you filter or sort by it.If you omit
select=audit(or equivalent), the API may ignore the filter or return 400.
Examples
Filter by creation date:
Sort by most recently updated:
Date format
Dates must be UTC with 3-digit millisecond precision: YYYY-MM-DDTHH:mm:ss.sssZ (e.g. 2026-01-31T23:00:00.000Z). Prefer the full timestamp for precise filters; date-only (YYYY-MM-DD) may not behave as expected when comparing timestamps. Other formats may not filter or sort correctly.
5. Efficient counting with limit=0
limit=0To get only the total count of resources matching a filter (e.g. "how many subscriptions does this agreement have?"):
Call the child resource endpoint (e.g. subscriptions).
Apply the filter (e.g. by agreement id via RQL or a direct filter like
agreement.id=AGR-XXXif supported).Set
limit=0.Read the count from
$meta.pagination.total.
The response will have an empty data array but the correct total. This avoids transferring full payloads when you only need the number.
Example:
Response:
6. Select: additive, subtractive, and omitted fields
Additive select (+field)
+field)select=+id,+name,+status— Include only these fields in addition to the default set. Use this to keep payloads small when you know what you need.Many resources omit large or sensitive fields by default. The response includes
$meta.omittedlisting properties that were not returned (e.g.["lines", "parameters", "subscriptions"]). To include one of them, add it withselect=+field(e.g.select=+lines,+parameters).
Subtractive select (-field)
-field)select=-metadata— Exclude a field. Some integrations useselect=-*,+id,+name,+field(exclude all, then add back only what is needed). Not all endpoints support the subtractive form; if you get 400, retry with additive only (e.g.select=+id,+name,+field).
Nested collections
+subscriptions— Return the full nested representation of the collection.+subscriptions.id,+subscriptions.name— Return onlyidandnamefor each nested item. Prefer this when you only need to list or count related items; it keeps responses smaller.
Commonly omitted fields
Many resources omit certain fields by default for performance. They are listed in $meta.omitted in the response. Common examples: lines (order line items), parameters, subscriptions, assets, termsAndConditions, certificates, audit. To include them, use select=+field (e.g. select=+lines,+parameters).
7. Limit and pagination
Default limit: If you omit
limit, the API may apply a large default (e.g. 1000). For most use cases, set an explicit limit (e.g. 20–50) to avoid oversized responses and timeouts.Maximum: The platform caps
limit(often at 100). Values above the cap are treated as the cap.Count-only: Use
limit=0to get only$meta.pagination.total(see above).Pagination: Use
limitandoffset(or the equivalent page-based parameters if documented) to page through results.
8. Combining count and list in one request
When you need both the total count and a list of items, use a single request with a reasonable limit (e.g. 20–50):
$meta.pagination.totalgives the total count.datagives the first page of items.
Avoid running a second request with limit=0 just to get the count if the first request already returns the total in metadata.
9. Order in the query string
Sort order is specified as part of the query string, not as a separate body or header:
Ascending:
order=+propertyororder=+audit.created.atDescending:
order=-propertyororder=-audit.updated.at
Example:
If you filter or sort by an audit field, include select=audit (see section 3).
10. Common RQL patterns (reference)
Exact match
eq(status,Active)
Case-insensitive search
ilike(name,*Microsoft*)
Greater / less than
gt(price.SPxM,100), lt(endDate,2026-12-31T23:59:59.999Z)
Multiple conditions
and(eq(status,Active),ilike(name,*Corp*))
Either condition
or(eq(type,A),eq(type,B))
In list
in(status,(Active,Updating))
Nested collection
any(groups,id=UGR-1234-5678)
Date range
and(gt(audit.created.at,"2024-01-01T00:00:00.000Z"),lt(audit.created.at,"2025-01-01T00:00:00.000Z"))
Always use field names that exist on the resource (see section 1).
11. Terminology and identity (Buyer, Client)
In the API, Buyer and Client have distinct meanings and ID formats:
Buyer — Legal entity; ID format
BUY-xxxx-xxx. Core for billing and legal.Client — One or more Buyers in the Client Portal; ID format
ACC-xxxx-xxxx.
Filtering by Client or Buyer: To filter by Client (ACC- IDs), use client.id in RQL on resources that expose it (e.g. commerce.orders, commerce.agreements): eq(client.id,"ACC-1234-5678"). To filter by Buyer (BUY- IDs), use buyer.id: eq(buyer.id,"BUY-1234-5678"). Check the resource schema to see which relationship field exists (client.id, buyer.id, etc.).
Many resources also expose externalIds (vendor, client, operations)—identifiers from actors outside the marketplace. Use them to filter by external references (e.g. eq(externalIds.vendor,"Microsoft-account-123") or eq(externalIds.client,"my-order-ref")) when the resource schema includes these fields. See External IDs for semantics and typical usage.
12. Price fields in RQL
Price objects typically expose SPxM / SPxY (sales/sell price) and currency. Include currency when stating amounts. See the API common object "Price" in the docs for full field semantics and visibility rules.
13. Subscription and commitment fields (commerce.subscriptions)
For "expiring", "renewal", or "commitment end" questions, key fields are: commitmentDate (ISO 8601 UTC), terms.commitment (e.g. 1y, 1m), terms.period (billing period), and autoRenew (boolean). Filter by commitmentDate in RQL for date ranges; include select=+commitmentDate,+autoRenew,+terms (or +terms.commitment) when you need to interpret renewal vs expiry in the response.
14. Path parameters (by-id endpoints)
Some resources have endpoints that require path parameters (e.g. get product by id: /catalog/products/{id}). RQL applies to list endpoints; when you need a single item by ID, use the appropriate by-id endpoint and supply the path parameter (e.g. id=PRD-1234-5678). The resource schema or API docs will indicate when path parameters are required.
15. 400 Bad Request or "Unknown expression"
If the API returns 400 or "Unknown expression" (or "Unknown expression group"):
Re-check the resource schema — the field path you used may not exist or may be nested differently (e.g.
seller.countryvsseller.address.country).Quote ID values — use
eq(client.id,"ACC-1234-5678")noteq(client.id,ACC-1234-5678).Include
select=auditwhen filtering or sorting byaudit.created.atoraudit.updated.at.Use full UTC date format —
YYYY-MM-DDTHH:mm:ss.sssZ.
Further resources
Resource Query Language — Core operators and syntax.
.NET RQL reference — C# implementation reference.
Last updated
Was this helpful?