meta.api Contract¶
The meta.api block in a dbt model's configuration is the contract between dbt-cerebro and cerebro-api. It defines how the API endpoint behaves: which HTTP methods are allowed, what filters clients can use, how pagination works, and the sort order of results.
This page is the canonical reference for all meta.api fields and validation rules.
Overview¶
The meta.api object is placed inside the dbt model's config() block under the meta key:
{{
config(
materialized='view',
tags=['production', 'consensus', 'api:blob_commitments', 'granularity:daily'],
meta={
"api": {
"methods": ["GET", "POST"],
"allow_unfiltered": false,
"require_any_of": [],
"parameters": [...],
"pagination": {...},
"sort": [...]
}
}
)
}}
Legacy endpoints
Models without a meta.api block are treated as legacy endpoints. They support GET only, accept no query parameters, have no pagination or sorting, and return the full result set. All new models should include meta.api.
Top-Level Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
methods | list of strings | ["GET"] | Allowed HTTP methods. Accepted values: GET, POST. |
allow_unfiltered | boolean | false | When true, clients can call the endpoint with no business filters. When false, at least one declared parameter must be provided. |
require_any_of | list of strings | [] | Parameter names where at least one must be present in the request. Each name must match a declared parameter. |
parameters | list of objects | [] | Declared filter parameters. See Parameter Fields. |
pagination | object | Disabled | Pagination configuration. See Pagination Fields. |
sort | list of objects | [] | Server-side sort order. See Sort Fields. |
Parameter Fields¶
Each entry in the parameters list declares one filter that clients can use.
| Key | Required | Type | Description |
|---|---|---|---|
name | Yes | string | API field name used by clients in query strings (GET) or JSON bodies (POST). Must be unique within the endpoint. |
column | Yes | string | The column in the model's final SELECT projection that this parameter filters on. Must exist in the model's columns. |
operator | Yes | string | SQL comparison operator. One of: =, >=, <=, ILIKE, IN. |
type | Yes | string | Parameter data type. One of: string, date, string_list. |
description | No | string | Human-readable description displayed in OpenAPI/Swagger documentation. |
case | No | string | Case normalization applied to input values before filtering. One of: lower, upper. Only valid for string and string_list types. |
max_items | No | integer | Maximum number of values allowed in a list parameter. Only valid for string_list type. Must be a positive integer. |
Parameter Types¶
A single string value. Used with operators = and ILIKE.
GET: ?symbol=GNO
POST: "symbol": "GNO"
A date value in YYYY-MM-DD format. Used with operators >= and <= for range queries.
GET: ?start_date=2024-01-01
POST: "start_date": "2024-01-01"
A list of string values. Used with the IN operator for multi-value matching.
GET (repeated): ?address=0x1&address=0x2
GET (CSV): ?address=0x1,0x2
POST: "address": ["0x1", "0x2"]
Operators¶
| Operator | SQL Equivalent | Compatible Types | Use Case |
|---|---|---|---|
= | column = value | string | Exact match on a single value |
>= | column >= value | date | Start of a date range (inclusive) |
<= | column <= value | date | End of a date range (inclusive) |
ILIKE | column ILIKE value | string | Case-insensitive pattern matching (supports % wildcards) |
IN | column IN (values) | string_list | Match any value in a list |
Operator-type constraints
The IN operator can only be used with string_list type. Attempting to use IN with string or date produces a validation error at manifest load time.
Case Normalization¶
When a parameter declares a case option, input values are normalized before filtering:
"case": "lower"-- Converts0xABCto0xabc"case": "upper"-- ConvertsabctoABC
This ensures consistent matching regardless of how clients format their input. The case option applies to both string and string_list types.
Max Items¶
The max_items field limits how many values a string_list parameter can accept. If a client provides more values than allowed, the API returns a 400 error:
Pagination Fields¶
The pagination object controls whether an endpoint supports limit and offset parameters.
| Key | Required | Type | Description |
|---|---|---|---|
enabled | Yes | boolean | Must be true to enable pagination. When false or omitted, limit and offset parameters are not accepted. |
default_limit | Yes (when enabled) | integer | Default number of rows returned when the client omits limit. Must be a positive integer. |
max_limit | Yes (when enabled) | integer | Hard upper bound for the limit parameter. Requests exceeding this value receive a 400 error. Must be >= default_limit. |
When pagination is enabled, two additional parameters become available to clients:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Maximum rows to return. Defaults to default_limit. Cannot exceed max_limit. |
offset | integer | Number of rows to skip. Defaults to 0. |
Pagination disabled by default
If pagination is not declared or enabled is false, passing limit or offset returns a 400 error: Unsupported query parameters: limit, offset.
Sort Fields¶
Each entry in the sort list defines a column and direction for the server-side ORDER BY clause.
| Key | Required | Type | Description |
|---|---|---|---|
column | Yes | string | Column name that must exist in the model's final SELECT projection. |
direction | Yes | string | Sort direction. One of: ASC, DESC. |
Sort order is fixed per endpoint and cannot be overridden by clients. Multiple sort entries are applied in order (primary sort, secondary sort, etc.).
Validation Rules¶
The API validates meta.api at manifest load time. Models that fail validation are skipped (not registered as endpoints) and an error is logged. The following rules are enforced:
Column References¶
- Every
parameters[].columnmust exist in the model's column list - Every
sort[].columnmust exist in the model's column list - Column names are validated against the dbt manifest's column metadata
Parameter Constraints¶
- Parameter
namevalues must be unique within a single endpoint INoperator requiresstring_listtype (and vice versa:string_listshould useIN)caseis only valid forstringandstring_listtypesmax_itemsis only valid forstring_listtype and must be a positive integerrequire_any_ofentries must reference declared parameter names
Filter Policy¶
- If
allow_unfilteredisfalse, at least one parameter must be declared require_any_ofnames must all be present in theparameterslist
Pagination Constraints¶
default_limitmust be a positive integer when pagination is enabledmax_limitmust be a positive integer when pagination is enableddefault_limitmust be less than or equal tomax_limit
Method Constraints¶
methodsmust be a non-empty list- Each method must be either
GETorPOST - Duplicate methods are silently deduplicated
Behavior Differences: Legacy vs Metadata-Driven¶
| Behavior | Legacy (no meta.api) | Metadata-Driven (meta.api present) |
|---|---|---|
| HTTP Methods | GET only | Declared in methods |
| Query Filters | None -- any parameter returns 400 | Only declared parameters accepted |
| Pagination | Disabled | Enabled via pagination |
| Sort | None (database default) | Explicit ORDER BY from sort |
| Unfiltered requests | Always allowed | Controlled by allow_unfiltered |
| POST support | No | Yes, if POST is in methods |
| Response | Complete table contents | Filtered, paginated, sorted |
Complete Examples¶
Minimal Metadata-Driven Endpoint¶
The simplest meta.api configuration -- enables the metadata-driven pipeline with no filters and allows unfiltered access:
{{
config(
materialized='view',
tags=['production', 'consensus', 'tier0', 'api:network_summary', 'granularity:latest'],
meta={
"api": {
"allow_unfiltered": true,
"pagination": {
"enabled": true,
"default_limit": 10,
"max_limit": 100
},
"sort": [
{"column": "date", "direction": "DESC"}
]
}
}
)
}}
SELECT date, active_validators, participation_rate
FROM {{ ref('int_consensus_network_summary_daily') }}
Result: GET /v1/consensus/network_summary/latest -- Public, paginated, sorted by date descending, no filters required.
Date Range Filters with Required Filter¶
{{
config(
materialized='view',
tags=['production', 'execution', 'tier1', 'api:gas_usage', 'granularity:daily'],
meta={
"api": {
"methods": ["GET"],
"allow_unfiltered": false,
"parameters": [
{
"name": "start_date",
"column": "date",
"operator": ">=",
"type": "date",
"description": "Start date (inclusive)"
},
{
"name": "end_date",
"column": "date",
"operator": "<=",
"type": "date",
"description": "End date (inclusive)"
}
],
"pagination": {
"enabled": true,
"default_limit": 100,
"max_limit": 10000
},
"sort": [
{"column": "date", "direction": "DESC"}
]
}
}
)
}}
SELECT date, total_gas_used, avg_gas_price, transaction_count
FROM {{ ref('int_execution_gas_daily') }}
Result: GET /v1/execution/gas_usage/daily -- Requires at least start_date or end_date. Returns error if no filters provided.
Multi-Value Filters with POST Support¶
{{
config(
materialized='view',
tags=['production', 'execution', 'tier2', 'api:token_transfers', 'granularity:daily'],
meta={
"api": {
"methods": ["GET", "POST"],
"allow_unfiltered": false,
"require_any_of": ["token_address", "from_address", "to_address"],
"parameters": [
{
"name": "token_address",
"column": "token_address",
"operator": "IN",
"type": "string_list",
"case": "lower",
"max_items": 50,
"description": "Token contract address(es)"
},
{
"name": "from_address",
"column": "from_address",
"operator": "IN",
"type": "string_list",
"case": "lower",
"max_items": 200,
"description": "Sender address(es)"
},
{
"name": "to_address",
"column": "to_address",
"operator": "IN",
"type": "string_list",
"case": "lower",
"max_items": 200,
"description": "Recipient address(es)"
},
{
"name": "start_date",
"column": "date",
"operator": ">=",
"type": "date"
},
{
"name": "end_date",
"column": "date",
"operator": "<=",
"type": "date"
}
],
"pagination": {
"enabled": true,
"default_limit": 100,
"max_limit": 5000
},
"sort": [
{"column": "date", "direction": "DESC"},
{"column": "token_address", "direction": "ASC"}
]
}
}
)
}}
SELECT
date,
token_address,
from_address,
to_address,
transfer_count,
total_value
FROM {{ ref('int_execution_token_transfers_daily') }}
Result:
GET /v1/execution/token_transfers/dailyandPOST /v1/execution/token_transfers/daily- tier2 access required
- At least one of
token_address,from_address, orto_addressmust be provided - Addresses are case-normalized to lowercase
- Sorted by date descending, then token address ascending
- Maximum 50 token addresses and 200 sender/recipient addresses per request
Pattern Matching with ILIKE¶
{{
config(
materialized='view',
tags=['production', 'contracts', 'tier1', 'api:verified_contracts'],
meta={
"api": {
"methods": ["GET"],
"allow_unfiltered": false,
"parameters": [
{
"name": "name",
"column": "contract_name",
"operator": "ILIKE",
"type": "string",
"description": "Contract name (case-insensitive, supports % wildcards)"
},
{
"name": "address",
"column": "contract_address",
"operator": "=",
"type": "string",
"case": "lower",
"description": "Exact contract address"
}
],
"pagination": {
"enabled": true,
"default_limit": 50,
"max_limit": 500
},
"sort": [
{"column": "contract_name", "direction": "ASC"}
]
}
}
)
}}
SELECT contract_address, contract_name, compiler_version, verified_at
FROM {{ ref('int_contracts_verified') }}
Result: GET /v1/contracts/verified_contracts -- Supports ?name=%uniswap% for pattern matching and ?address=0x... for exact lookup.
Error Responses¶
When meta.api validation rules are violated at request time, the API returns 400 errors:
| Scenario | Error Message |
|---|---|
| Undeclared GET parameter | Unsupported query parameters: {param_name} |
| Undeclared POST body field | Unsupported body fields: {field_name} |
No filters when allow_unfiltered=false | At least one business filter is required for this endpoint. |
Missing require_any_of filter | At least one of [{names}] is required for this endpoint. |
limit exceeds max_limit | 'limit' must be <= {max_limit}. |
limit/offset without pagination | Unsupported query parameters: limit, offset |
string_list exceeds max_items | Parameter '{name}' allows at most {max_items} values. |
| Query params on POST | POST endpoints accept filter and pagination fields in JSON body only. |
| Invalid JSON body | Request body must be valid JSON. |
| Legacy endpoint with params | This endpoint does not declare API parameters. Add meta.api to the dbt model to enable filters or pagination. |
Next Steps¶
- Adding API Endpoints -- Step-by-step guide using
meta.api - Filtering & Pagination -- Client-facing documentation for using filters
- Error Handling -- Full error response reference