---
title: "Create a prepared query"
method: POST
path: "/v3/{ledgerName}/prepared-queries"
tags: ["Prepared Queries"]
---

# Create a prepared query

`POST /v3/{ledgerName}/prepared-queries`

## Request

- Base URL: `http://localhost:9000`
- URL: `http://localhost:9000/v3/{ledgerName}/prepared-queries`
- Auth: one of:
  - HTTP bearer
  - none

Creates a new prepared query for a ledger. A prepared query defines a reusable
filter that can be executed later with parameter substitution. Requires `ledger:WriteQuery` scope.

## Path parameters

- `ledgerName` string, required

## Headers

- `Idempotency-Key` string

## Request body

- CreatePreparedQueryRequest
  - `name` string, required: Unique name for the prepared query
  - `target` 'ACCOUNTS' | 'TRANSACTIONS' | 'LOGS', required: Target entity type. `LOGS` queries return log entries; the log-only filter fields (`logId`, `date`, `ledger`) are valid only when the target is `LOGS`.
  - `filter` union: A filter supplied to a prepared-query create/update request, accepted in EITHER of two interchangeable representations that parse to the same filter and are validated against the same per-target rules (EN-1511): - the structured JSON QueryFilter object (see `QueryFilter`); or - the textual filter grammar carried as a JSON string, e.g. `"metadata[status] == \"active\""`. `null` represents a match-all query. An empty object and an empty or whitespace-only textual expression are invalid. Responses echo a stored non-null filter in the structured `QueryFilter` form and omit this field for match-all queries.
    - QueryFilter: Recursive filter expression using the Formance query DSL (aligned with the v2 API). Each node is an object with **exactly one** top-level `$`-prefixed operator key. Combinators: - `{"$and": [QueryFilter, ...]}` — all sub-filters must match (non-empty). - `{"$or": [QueryFilter, ...]}` — at least one sub-filter matches (non-empty). - `{"$not": QueryFilter}` — negates a single sub-filter. Leaf conditions are a single-key operator whose value is a `{ "<field>": <value> }` map with exactly one field: - `{"$match": {"<field>": <value>}}` — equality; for `address`/`source`/`destination` a trailing `:` means prefix match, otherwise exact. - `{"$gt"|"$gte"|"$lt"|"$lte": {"<field>": <value>}}` — range bound. A closed range is expressed as an `$and` of a lower and an upper bound on the same field. - `{"$exists": {"metadata": "<key>"}}` — the metadata key is present. - `{"$exists": {"asset": "<assetRef>"}}` — the account has ever held the asset (`assetRef` is `BASE` or `BASE/PRECISION`, e.g. `USD/2`). Requires a READY `account-asset` index. Supported fields: `metadata[<key>]`, `address`, `source`, `destination`, `reference`, `reverted` (bool), `ledger`, `id`, `timestamp`, `insertedAt`, `revertedAt`, `logId`, `date`. Not every field applies to every query target; a field that does not apply is rejected. On the numeric built-ins (`id`, `timestamp`, `insertedAt`, `revertedAt`, `logId`, `date`) use `$gt`/`$gte`/`$lt`/`$lte` for ranges; `$match` is an equality (the closed range from the value to itself). On the audit-entries endpoint only, the bare audit fields (`outcome`, `ledger`, `seq`, `proposal_id`, `timestamp`, `log_seq`, `caller_subject`, `order_type`, `idempotency_key`) replace the `ledger`/`timestamp` meanings above: `$match` on any of them, range operators on the numeric ones, no `$param` values. The log-only fields (`logId`, `date`, `ledger`) are valid only when the prepared query targets `LOGS`; they are rejected on `ACCOUNTS` and `TRANSACTIONS` targets. A value is either a JSON literal or a parameter reference `{"$param": "<name>"}`, resolved at prepared-query execution time. A JSON `null` in value position is rejected. uint64 fields (`id`, `timestamp`, `insertedAt`, `revertedAt`, `logId`, `date`) are carried as decimal strings to stay lossless above 2^53. The date-semantic fields (`timestamp`, `insertedAt`, `revertedAt`, `date`) additionally accept an RFC3339 timestamp string (e.g. `{"$gte":{"date":"2023-11-14T22:13:20Z"}}`), coerced to the same Unix microseconds as the raw form; a value before the Unix epoch (1970-01-01) is rejected.
      - `$and` QueryFilter[]
      - `$or` QueryFilter[]
      - `$not` QueryFilter (recursive)
      - `$match` object: `{ "<field>": <value> }` — exact/prefix/bool match.
      - `$gt` object: `{ "<field>": <value> }` — strictly greater than.
      - `$gte` object: `{ "<field>": <value> }` — greater than or equal.
      - `$lt` object: `{ "<field>": <value> }` — strictly less than.
      - `$lte` object: `{ "<field>": <value> }` — less than or equal.
      - `$exists` object: `{ "metadata": "<key>" }` (metadata key present) or `{ "asset": "<assetRef>" }` (account has held the asset).
      - `$in` object: `{ "<field>": [<value>, ...] }` — value is one of a set. Reserved for the list endpoints; not accepted on prepared queries yet.
    - string, nullable: Textual filter expression (filter grammar).

## Response `204`

Prepared query created successfully

## Other responses

- `400`: Bad request
- `401`: Authentication required (missing or invalid JWT token)
- `403`: Insufficient permissions (missing required scope)
- `404`: Not found
- `405`: Method not allowed
- `409`: Conflict (e.g. idempotency key conflict, ledger deleted)
- `429`: Resource exhausted. Write admission can reject with `WRITES_BLOCKED_DISK_FULL` while disk pressure blocks writes; `SEQUENCE_EXHAUSTED` means an authoritative identifier allocator cannot allocate another identifier and is permanent for that allocator. Inspect `errorCode` before deciding whether to retry. No retry interval is supplied, and retrying cannot resolve sequence exhaustion.
- `500`: Internal server error
- `503`: Service unavailable — the server cannot serve the request right now but an immediate retry can succeed. The `errorCode` field discriminates the cause: `NO_LEADER` (no elected Raft leader), `CACHE_HORIZON_EXCEEDED` (admission snapshot too old, retry against a fresh one), `UNAVAILABLE` (an internal forwarded stream was torn down mid-transfer), or the reason of any domain error of the unavailable kind (e.g. `INDEX_BUILDING`).

## Changes

- **2026-11-03** `f5e817eca038`: 1 info
  - api operation id `createPreparedQuery` removed and replaced with `v3CreatePreparedQuery`
- **2026-10-08** `3da23d327316`: 3 info
  - added the media type `text/plain` for the response with the status `401`
  - added the media type `text/plain` for the response with the status `403`
  - added the non-success response with the status `429`
- **2026-10-06** `551211dfe58a`: 1 breaking, 1 info
  - removed `subschema #2, subschema #3` from the `filter` request property `oneOf` list
  - added `subschema #2` to the `filter` request property `oneOf` list
- **2026-10-01** `2587b9f91dd1`: 2 info
  - the request property `filter` became optional
  - added `subschema #2` to the `filter` request property `oneOf` list
- **2026-09-30** `b747efd5cd77`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/formancehq/apis/ledger-api/changes/v3/:ledgerName/prepared-queries/post.md)

---

[API](https://skmtc.dev/formancehq/apis/ledger-api.md) - [All operations](https://skmtc.dev/formancehq/apis/ledger-api/llms.txt) - [OpenAPI document](https://skmtc.dev/formancehq/apis/ledger-api/revisions/f5e817eca038?raw)
