OData API

On this page

Complete OData v4 query reference. For setup and tool connections, see Serve Data via OData.

Endpoints

MethodPathResponse
GET/odataService document (lists all exposed entities)
GET/odata/$metadataCSDL XML (schema for all entities)
GET/odata/{entity}Query collection
GET/odata/{entity}(key)Single entity by key
GET/odata/{entity}/$countRow count (plain text)
POST/odata/$batchJSON batch (multiple requests in one call)

Entity names use underscores: mart.revenue becomes mart_revenue in the URL.

Query Parameters

ParameterExampleEffect
$select$select=customer,amountReturn only these columns
$filter$filter=amount gt 100Filter rows
$orderby$orderby=amount descSort results
$top$top=10Limit rows
$skip$skip=20Offset
$count$count=trueInclude total count in response
$search$search=aliceFull-text search across all string columns
$expand$expand=raw_ordersInline related entities
$apply$apply=aggregate(amount with sum as total)Aggregation
$compute$compute=amount mul 2 as doubledComputed properties (col op literal as alias)

Filter Operators

OperatorExample
eq, necustomer eq 'Alice'
gt, ge, lt, leamount gt 100
and, or, notamount gt 100 and not (active eq false)
incustomer in ('Alice','Bob')
hasEnum flag bitwise check
eq nullorder_date eq null

Filter Functions

CategoryFunctions
Stringcontains, startswith, endswith, tolower, toupper, length, trim, concat, indexof, substring
Dateyear, month, day, hour, minute, second, now, date, time
Mathround, floor, ceiling
Arithmeticadd, sub, mul, div, mod
Typecast, isof
PatternmatchesPattern (regex)
Geogeo.distance, geo.intersects, geo.length (requires DuckDB spatial)
Othercase expressions, fractionalseconds, totaloffsetminutes, totalseconds, maxdatetime, mindatetime

$apply Aggregation

# Sum
$apply=aggregate(amount with sum as total)

# Group by + aggregate
$apply=groupby((customer),aggregate(amount with sum as total))

# Supported functions: sum, avg, min, max, count, countdistinct

$expand Navigation

Relationships between @expose models are auto-discovered when one entity has a column matching another entity’s key column (FK→PK pattern). Both models must have @expose with explicit key columns.

# Inline related orders for each customer
curl "http://localhost:8090/odata/raw_customers?\$expand=raw_orders"

Navigation properties appear in $metadata automatically. The response nests related entities inline.

$batch

Multiple requests in one HTTP call (JSON batch format):

curl -X POST http://localhost:8090/odata/\$batch \
  -H "Content-Type: application/json" \
  -d '{"requests":[
    {"id":"1","method":"GET","url":"mart_revenue?$top=5"},
    {"id":"2","method":"GET","url":"mart_revenue/$count"}
  ]}'

Examples

Collection and entity:

curl "http://localhost:8090/odata/mart_revenue"
curl "http://localhost:8090/odata/mart_revenue(1)"
curl "http://localhost:8090/odata/mart_revenue?\$select=customer,amount"

Filtering:

curl "http://localhost:8090/odata/mart_revenue?\$filter=amount%20gt%20100"
curl "http://localhost:8090/odata/mart_revenue?\$filter=customer%20in%20('Alice','Bob')"
curl "http://localhost:8090/odata/mart_revenue?\$filter=contains(customer,'Ali')"
curl "http://localhost:8090/odata/mart_revenue?\$filter=year(order_date)%20eq%202026"
curl "http://localhost:8090/odata/mart_revenue?\$filter=amount%20add%2050%20gt%201000"

Sorting, pagination, count:

curl "http://localhost:8090/odata/mart_revenue?\$orderby=amount%20desc&\$top=10"
curl "http://localhost:8090/odata/mart_revenue?\$top=10&\$skip=20"
curl "http://localhost:8090/odata/mart_revenue?\$filter=amount%20gt%20100&\$count=true"

Search, compute, expand:

curl "http://localhost:8090/odata/mart_revenue?\$search=alice"
curl "http://localhost:8090/odata/mart_revenue?\$compute=amount%20mul%202%20as%20doubled"
curl "http://localhost:8090/odata/raw_customers?\$expand=raw_orders"

Aggregation:

curl "http://localhost:8090/odata/mart_revenue?\$apply=aggregate(amount%20with%20sum%20as%20total)"
curl "http://localhost:8090/odata/mart_revenue?\$apply=groupby((customer),aggregate(amount%20with%20sum%20as%20total))"

Data Types

DuckDBODataJSON
VARCHAREdm.String"text"
TINYINTEdm.Byte1
SMALLINTEdm.Int16100
INTEGEREdm.Int3242
BIGINTEdm.Int649999999999
DOUBLEEdm.Double3.14
FLOATEdm.Single1.5
DECIMALEdm.Decimal150.99
BOOLEANEdm.Booleantrue
DATEEdm.Date"2026-01-15"
TIMESTAMPEdm.DateTimeOffset"2026-01-15T08:30:00Z"

NULL values are JSON null. Empty strings are preserved as "".

Security

  • Read-only: only SELECT queries, no writes
  • Column validation: queries only access columns that exist in the schema
  • No SQL injection: column names validated against information_schema, string values escaped
  • Localhost only: binds to 127.0.0.1 by default