All pages
Powered by GitBook
1 of 1

Loading...

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.

Discover fields before writing RQL

RQL filter and sort expressions only work with fields that exist on the resource. The API returns errors (for example, '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 subscriptionsCount field 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 (for example, subscriptions) and filter by parent (for example, agreement.id).

    • Fetch agreements with select=+id,+name,+subscriptions.id,+subscriptions.name and compute or filter in your application.

Mistake
Correct approach

A very common source of 400 errors is sending RQL as a single query parameter.

Many clients 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.

Append the RQL expression as the query string (or as the first part of it):

When you have both RQL and other parameters (like, 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.

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 (example, 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.

ID values (such as, 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, &, ?) in string values must be enclosed in double or single quotes: eq(firstName,"Buzz !!!").

To use a literal asterisk inside an ilike pattern, escape it: ilike(name,"The\**").

Date and audit metadata (created, updated) live under the audit object. The API behavior is:

Filtering or sorting by audit fields (such as audit.created.at, audit.updated.at) only works if the audit data is in scope for the response. That means you must include audit in the select parameter when you filter or sort by it.

If you omit select=audit (or equivalent), the API may ignore the filter or return 400.

Filter by creation date:

Sort by most recently updated:

Dates must be UTC with 3-digit millisecond precision: YYYY-MM-DDTHH:mm:ss.sssZ (for example, 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.

To get only the total count of resources matching a filter (such as, how many subscriptions does this agreement have?):

  1. Call the child resource endpoint (for example, subscriptions).

  2. Apply the filter (for example, by agreement ID via RQL or a direct filter like agreement.id=AGR-XXX if supported).

  3. Set limit=0.

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:

  • 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.omitted listing properties that were not returned (for example, ["lines", "parameters", "subscriptions"]). To include one of them, add it with select=+field (for exampple, select=+lines,+parameters).

select=-metadata - Exclude a field.

Some integrations use select=-*,+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 (for example, select=+id,+name,+field).

  • +subscriptions - Return the full nested representation of the collection.

  • +subscriptions.id,+subscriptions.name - Return only id and name for each nested item. Prefer this when you only need to list or count related items; it keeps responses smaller.

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 (for example, select=+lines,+parameters).

  • Default limit - If you omit limit, the API may apply a large default (like, 1000). For most use cases, set an explicit limit (such as, 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=0 to get only

When you need both the total count and a list of items, use a single request with a reasonable limit (for example, 20–50):

  • $meta.pagination.total gives the total count.

  • data gives 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.

Sort order is specified as part of the query string, not as a separate body or header:

  • Ascending - order=+property or order=+audit.created.at

  • Descending - order=-property or order=-audit.updated.at

Example:

If you filter or sort by an audit field, include select=audit (see ).

Need
Example

Always use field names that exist on the resource (see section 1).

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 (for example, 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 (for example, eq(externalIds.vendor,"Microsoft-account-123") or eq(externalIds.client,"my-order-ref")) when the resource schema includes these fields. See for semantics and typical usage.

Price objects typically expose SPxM / SPxY (sales/sell price) and currency. Include currency when stating amounts. See for full field semantics and visibility rules.

For "expiring", "renewal", or "commitment end" questions, key fields are: commitmentDate (ISO 8601 UTC), terms.commitment (for example, 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.

Some resources have endpoints that require path parameters (for example, 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.

If the API returns 400 or "Unknown expression" (or "Unknown expression group"):

  1. Re-check the resource schema - The field path you used may not exist or may be nested differently (e.g. seller.country vs seller.address.country).

  2. Quote ID values - use eq(client.id,"ACC-1234-5678") not eq(client.id,ACC-1234-5678).

  3. Include select=audit

  • - Core operators and syntax.

  • - C# implementation reference.

Read the count from $meta.pagination.total.
$meta.pagination.total
(see above).
  • Pagination - Use limit and offset (or the equivalent page-based parameters if documented) to page through results.

  • when filtering or sorting by
    audit.created.at
    or
    audit.updated.at
    .
  • Use full UTC date format - YYYY-MM-DDTHH:mm:ss.sssZ.

  • 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

    GET /public/v1/catalog/products?rql=eq(status,Published)
    GET /public/v1/catalog/products?eq(status,Published)
    GET /public/v1/catalog/products?eq(status,Published)&limit=20&offset=0
    GET /public/v1/commerce/orders?select=audit&and(gt(audit.created.at,"2024-12-01T00:00:00.000Z"),lt(audit.created.at,"2025-12-01T00:00:00.000Z"))&limit=20
    GET /public/v1/commerce/orders?select=+id,+name,audit&order=-audit.updated.at&limit=10
    GET /public/v1/commerce/subscriptions?eq(agreement.id,AGR-1234-5678)&limit=0
    {
      "$meta": {
        "pagination": {
          "total": 42,
          "limit": 0,
          "offset": 0
        }
      },
      "data": []
    }
    GET /public/v1/catalog/products?order=-audit.created.at&limit=10

    Exact match

    eq(status,Active)

    Case-insensitive search

    ilike(name,*Microsoft*)

    Greater / less than

    Summary

    How RQL is sent (query string, not a parameter)

    Wrong

    Correct

    Do not mix parameters inside the RQL string

    IDs and special characters in RQL values

    Audit fields and the select parameter

    Examples

    Date format

    Efficient counting with limit=0

    Select: additive, subtractive, and omitted fields

    Additive select (+field)

    Subtractive select (-field)

    Nested collections

    Commonly omitted fields

    Limit and pagination

    Combining count and list in one request

    Order in the query string

    Common RQL patterns (reference)

    Terminology and identity (Buyer, Client)

    Price fields in RQL

    Subscription and commitment fields (commerce.subscriptions)

    Path parameters (by-id endpoints)

    400 Bad Request or "Unknown expression"

    Further resources

    IDs and special characters in RQL values
    External IDs
    Price
    Resource Query Language
    .NET RQL reference

    Use audit.created.at and include select=audit (see below).

    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"))