> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.fynapse.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.fynapse.app/_mcp/server.

# Check whether a saved balance query would be saved

POST https://api.fynapse.com/api/v1/saved-queries/validate
Content-Type: application/json

This endpoint runs the checks the save action makes on the query in the body, and persists nothing.
A query that passes is reported here. One that would be refused is reported as the refusal itself, carrying the reason, rather than as a bare negative. The name is not checked for collision with an existing query.
Check failures and refusals give 400 and 403 as on create.

**Permissions for nested calls.** This endpoint calls other Fynapse services with your token. Depending on the request, the token also needs:

- `journal_line_definition_read`, when the entity is stored in ClickHouse, which applies to most queries.
- `system_settings_read` and either `fiscal_calendar_read` or `balance_read`, for a `balanceMovement` query with relative or fiscal dates.

Reference: https://docs.fynapse.app/api-reference/api-reference/accounting-processing/balance-processing/saved-balance-queries/validate-saved-query

## Authentication

- OAuth2 — send the obtained token as `Authorization: Bearer <token>`

## Request

### Body (application/json)

This endpoint expects a schema.SavedQueryRequest.

- `schema.SavedQueryRequest`
  - `type`: `balance` (Saved Balance Query Balance Type Schema)
    - `invalid` (boolean, optional) — Present, and true, only when the stored definition can no longer be read; `query` is then absent.
    - `name` (string, optional) — The query's name, unique across both types. Every extract configuration that uses the query holds it by this name. It must not be blank.
    - `query` (schema.BalanceQueryDefinition, optional) — The query's definition. Absent on a read when `invalid` is true.
    - `version` (long, optional) — The version the query is at. Send it back as `expectedVersion` on an update.
  - `type`: `balanceMovement` (Saved Balance Query Balance Movement Type Schema)
    - `invalid` (boolean, optional) — Present, and true, only when the stored definition can no longer be read; `query` is then absent.
    - `name` (string, optional) — The query's name, unique across both types. Every extract configuration that uses the query holds it by this name. It must not be blank.
    - `query` (schema.BalanceMovementQueryDefinition, optional) — The query's definition. Absent on a read when `invalid` is true.
    - `version` (long, optional) — The version the query is at. Send it back as `expectedVersion` on an update.

## Response

### 200

OK

- `valid` (true, required) — Always true. A query that would be refused is answered with the refusal as an error response, never with `valid` false.
- `persisted` (false, required) — Always false. The check stores nothing.

## Errors

### 400 Bad Request Error

Bad Request

- `args` (map from string to string, required)
- `detail` (string, required)
- `errors` (list of schema.ErrorItem, required)
- `instance` (string, required)
- `path` (string, required)
- `reference` (string, required)
- `status` (integer, required)
- `timestamp` (string, required)
- `title` (string, required)
- `type` (string, required)

### 401 Unauthorized Error

Authentication Error

- `args` (map from string to string, required)
- `detail` (string, required)
- `errors` (list of schema.ErrorItem, required)
- `instance` (string, required)
- `path` (string, required)
- `reference` (string, required)
- `status` (integer, required)
- `timestamp` (string, required)
- `title` (string, required)
- `type` (string, required)

### 403 Forbidden Error

Authentication Error

- `args` (map from string to string, required)
- `detail` (string, required)
- `errors` (list of schema.ErrorItem, required)
- `instance` (string, required)
- `path` (string, required)
- `reference` (string, required)
- `status` (integer, required)
- `timestamp` (string, required)
- `title` (string, required)
- `type` (string, required)

### 429 Too Many Requests Error

Too many requests. Rate limit exceeded.

- `args` (map from string to string, required)
- `detail` (string, required)
- `errors` (list of schema.ErrorItem, required)
- `instance` (string, required)
- `path` (string, required)
- `reference` (string, required)
- `status` (integer, required)
- `timestamp` (string, required)
- `title` (string, required)
- `type` (string, required)

### 500 Server Error

Internal Server Error

- `args` (map from string to string, required)
- `detail` (string, required)
- `errors` (list of schema.ErrorItem, required)
- `instance` (string, required)
- `path` (string, required)
- `reference` (string, required)
- `status` (integer, required)
- `timestamp` (string, required)
- `title` (string, required)
- `type` (string, required)

## Types

### schema.BalanceQueryDefinition

What a `balance` query selects, and as at which date. A `balanceMovement` query holds all of this too, with `balanceDate` as its closing date.

- `balanceDate` (schema.BalanceQueryClosingDate, required) — The date the balances are computed as at; for a `balanceMovement` query, the closing date.
- `fields` (list of string, required) — The Journal fields to return and group the balances by. Each must be a field of the Journal Line Definition.
- `currencyTypes` (list of string, optional) — The currencies to compute balances in: `transaction`, `functional`, or a reporting currency code such as `USD`. Absent or empty computes the transaction currency only. Not checked at save.
- `filters` (list of schema.BalanceQueryFilter, optional) — Filters on Journal fields, all combined with AND.
- `balanceFilter` (schema.BalanceQueryAmountFilter, optional) — A filter on the balance in transaction currency. When present it replaces `balanceFilters`: the per-currency list is ignored and this filter tests the transaction amount.
- `balanceFilters` (list of schema.BalanceQueryCurrencyAmountFilter, optional) — Filters on the balance in each `currency`. Ignored when `balanceFilter` is present. Not checked at save.
- `limit` (integer, optional, default: -1) — The most rows the query returns when it is run. -1, 0 or any other value below 1 means the service default. A read always reports it, as -1 when it was never set. It is not checked at save: a value above the service's configured maximum is saved, and the query then fails each time it runs, including for an extract that draws from it. An extract is not otherwise cut off at the limit.

### schema.BalanceMovementQueryDefinition

What a `balanceMovement` query selects, and between which dates. It holds everything a `balance` query does, with `balanceDate` as the closing date, and adds the opening date and the filters on the opening balance and the balance movement.

- `balanceDate` (schema.BalanceQueryClosingDate, required) — The date the balances are computed as at; for a `balanceMovement` query, the closing date.
- `fields` (list of string, required) — The Journal fields to return and group the balances by. Each must be a field of the Journal Line Definition.
- `openingDate` (schema.BalanceQueryOpeningDate, required) — The date the balance movement starts on.
- `currencyTypes` (list of string, optional) — The currencies to compute balances in: `transaction`, `functional`, or a reporting currency code such as `USD`. Absent or empty computes the transaction currency only. Not checked at save.
- `filters` (list of schema.BalanceQueryFilter, optional) — Filters on Journal fields, all combined with AND.
- `balanceFilter` (schema.BalanceQueryAmountFilter, optional) — A filter on the balance in transaction currency. When present it replaces `balanceFilters`: the per-currency list is ignored and this filter tests the transaction amount.
- `balanceFilters` (list of schema.BalanceQueryCurrencyAmountFilter, optional) — Filters on the balance in each `currency`. Ignored when `balanceFilter` is present. Not checked at save.
- `limit` (integer, optional, default: -1) — The most rows the query returns when it is run. -1, 0 or any other value below 1 means the service default. A read always reports it, as -1 when it was never set. It is not checked at save: a value above the service's configured maximum is saved, and the query then fails each time it runs, including for an extract that draws from it. An extract is not otherwise cut off at the limit.
- `openingBalanceFilter` (schema.BalanceQueryAmountFilter, optional) — A filter on the opening balance in transaction currency. When present it replaces `openingBalanceFilters`: the per-currency list is ignored and this filter tests the transaction amount.
- `openingBalanceFilters` (list of schema.BalanceQueryCurrencyAmountFilter, optional) — Filters on the opening balance in each `currency`. Ignored when `openingBalanceFilter` is present. Not checked at save.
- `balanceMovementFilter` (schema.BalanceQueryAmountFilter, optional) — A filter on the balance movement in transaction currency. When present it replaces `balanceMovementFilters`: the per-currency list is ignored and this filter tests the transaction amount.
- `balanceMovementFilters` (list of schema.BalanceQueryCurrencyAmountFilter, optional) — Filters on the balance movement in each `currency`. Ignored when `balanceMovementFilter` is present. Not checked at save.

### schema.ErrorItem

- `title` (string, required)
- `detail` (string, required)
- `type` (string, required)
- `path` (string, required)
- `args` (map from string to string, required)

### schema.BalanceQueryClosingDate

The date balances are computed as at, found by `type` each time the query runs. The Fiscal Calendar, year and period named here are not checked when the query is saved. `fiscalYear` and `period` are not required at save either.

- `type` (enum, required) — How the date is found, matched without regard to case. `specificDate` is `date`. `currentDate` is the business date (today when none is set), and `previousDay` the day before it. `specificPeriodEnd` is the last day of `period` in `fiscalYear` of `fiscalCalendarName`. `previousYearEnd` and `previousPeriodEnd` are the end of the fiscal year or period before the one the business date falls in, in `fiscalCalendarName`.
  - Allowed values: `specificDate`, `specificPeriodEnd`, `currentDate`, `previousDay`, `previousYearEnd`, `previousPeriodEnd`
- `date` (string, optional) — The date, for `specificDate`. Required when `type` is `specificDate`.
- `fiscalCalendarName` (string, optional) — The Fiscal Calendar that `specificPeriodEnd`, `previousYearEnd` and `previousPeriodEnd` read.
- `fiscalYear` (long, optional) — The fiscal year of `period`, for `specificPeriodEnd`.
- `period` (long, optional) — The fiscal period whose last day is the date, for `specificPeriodEnd`.

### schema.BalanceQueryFilter

A filter on one Journal field.

- `fieldName` (string, required) — The Journal field to filter. It must be a field of the Journal Line Definition, and not the transaction amount, which the amount filters test.
- `rules` (list of schema.BalanceQueryFilterRule, required)
- `operator` (enum, optional, default: and) — How the rules combine, matched without regard to case. `and` and `or` take up to 999 rules; `not` takes exactly one.
  - Allowed values: `and`, `or`, `not`

### schema.BalanceQueryAmountFilter

A filter on a computed amount.

- `rules` (list of schema.BalanceQueryFilterRule, required)
- `fieldName` (string, optional) — When present, it must be the transaction amount field.
- `operator` (enum, optional, default: and) — How the rules combine, matched without regard to case. `and` and `or` take up to 999 rules; `not` takes exactly one.
  - Allowed values: `and`, `or`, `not`

### schema.BalanceQueryCurrencyAmountFilter

A filter on a computed amount in one currency.

- `rules` (list of schema.BalanceQueryFilterRule, required)
- `fieldName` (string, optional) — When present, it must be the transaction amount field.
- `operator` (enum, optional, default: and) — How the rules combine, matched without regard to case. `and` and `or` take up to 999 rules; `not` takes exactly one.
  - Allowed values: `and`, `or`, `not`
- `currency` (string, optional) — The currency of the amount the filter tests - `transaction`, `functional`, or a reporting currency code such as `USD`, matched exactly.

### schema.BalanceQueryOpeningDate

The date a balance movement starts on, found by `type` each time the query runs. The opening balance is the balance as at the day before it, and the movement runs from it to the closing date. The Fiscal Calendar, year and period named here are not checked when the query is saved. `fiscalYear` and `period` are not required at save either.

- `type` (enum, required) — How the date is found, matched without regard to case. `specificDate` is `date`. `specificPeriod` and `periodFromTo` are the first day of `period` in `fiscalYear` of `fiscalCalendarName`. `periodToDate` and `yearToDate` are the start of the fiscal period or year the business date falls in, and `previousPeriod` and `previousYear` the start of the one before, in `fiscalCalendarName`. `daily` is the closing date, so the movement covers that day alone.
  - Allowed values: `specificDate`, `specificPeriod`, `periodToDate`, `periodFromTo`, `previousPeriod`, `previousYear`, `yearToDate`, `daily`
- `date` (string, optional) — The date, for `specificDate`. Required when `type` is `specificDate`.
- `fiscalCalendarName` (string, optional) — The Fiscal Calendar that every type other than `specificDate` and `daily` reads.
- `fiscalYear` (long, optional) — The fiscal year of `period`, for `specificPeriod` and `periodFromTo`.
- `period` (long, optional) — The fiscal period whose first day is the date, for `specificPeriod` and `periodFromTo`.

### schema.BalanceQueryFilterRule

One test that a filter applies to its field.

- `constraint` (enum, required) — Matched without regard to case. `isNull`, `isNotNull`, `isEmpty` and `isNotEmpty` take no values, `between` takes two, `inList` and `notInList` take 1 to 999, and every other constraint takes one. `like`, `beginsWith`, `notBeginsWith`, `contains`, `notContains`, `endsWith` and `notEndsWith` apply only to text fields.
  - Allowed values: `equal`, `notEqual`, `greaterThan`, `greaterThanOrEqual`, `lessThan`, `lessThanOrEqual`, `between`, `inList`, `notInList`, `like`, `isNull`, `isNotNull`, `beginsWith`, `notBeginsWith`, `contains`, `notContains`, `endsWith`, `notEndsWith`, `isEmpty`, `isNotEmpty`
- `values` (list of any, optional) — The values the constraint tests against. How many it takes depends on `constraint`.

## Examples

**Request**

```json
{
  "type": "balance"
}
```

**Response**

```json
{
  "valid": true,
  "persisted": false
}
```

**SDK Code**

```python
import requests

url = "https://api.fynapse.com/api/v1/saved-queries/validate"

payload = { "type": "balance" }
headers = {
    "Authorization": "<token>.",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.fynapse.com/api/v1/saved-queries/validate';
const options = {
  method: 'POST',
  headers: {Authorization: '<token>.', 'Content-Type': 'application/json'},
  body: '{"type":"balance"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.fynapse.com/api/v1/saved-queries/validate"

	payload := strings.NewReader("{\n  \"type\": \"balance\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "<token>.")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.fynapse.com/api/v1/saved-queries/validate")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = '<token>.'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"type\": \"balance\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.fynapse.com/api/v1/saved-queries/validate")
  .header("Authorization", "<token>.")
  .header("Content-Type", "application/json")
  .body("{\n  \"type\": \"balance\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.fynapse.com/api/v1/saved-queries/validate', [
  'body' => '{
  "type": "balance"
}',
  'headers' => [
    'Authorization' => '<token>.',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.fynapse.com/api/v1/saved-queries/validate");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "<token>.");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"type\": \"balance\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "Authorization": "<token>.",
  "Content-Type": "application/json"
]
let parameters = ["type": "balance"] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.fynapse.com/api/v1/saved-queries/validate")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```