List & read
List rows with limit, offset and sort, and read one row by primary key - the exact routes, parameters, defaults and response shapes of a generated API.
Each table is served at /api/<schema>/<table>, for example /api/public/artist (PostgreSQL
default schema public, SQL Server dbo, MySQL: the database name, Oracle: the connecting user, upper-cased). Column names in
responses are exactly the table's column names.
| Route | Returns |
|---|---|
GET /api/<schema>/<table> |
JSON array of rows |
GET /api/<schema>/<table>/<id> |
One row as a JSON object, or 404 |
The examples assume the setup from Authentication
(RESTWARD_API_URL, RESTWARD_API_KEY).
List rows
curl -s "$RESTWARD_API_URL/api/public/artist?limit=3" \
-H "X-API-Key: $RESTWARD_API_KEY"const url = new URL("/api/public/artist", process.env.RESTWARD_API_URL);
url.searchParams.set("limit", "3");
const rows = await (await fetch(url, { headers: { "X-API-Key": process.env.RESTWARD_API_KEY } })).json();rows = session.get(f"{api}/api/public/artist", params={"limit": 3}).json()var rows = await http.GetFromJsonAsync<List<Dictionary<string, JsonElement>>>("/api/public/artist?limit=3");[
{ "artist_id": 1, "name": "AC/DC" },
{ "artist_id": 2, "name": "Accept" },
{ "artist_id": 3, "name": "Aerosmith" }
]The response is the array itself. Add count=true to also get the total number of matching rows
(see Total count), and see
Pagination & sync for walking a whole table.
Query parameters
| Parameter | Meaning | Default |
|---|---|---|
limit |
Rows to return, clamped to 1–1000 |
100 |
offset |
Rows to skip (negative values count as 0) |
0 |
sort |
Comma-separated columns; prefix - for descending: sort=artist_id,-title |
Primary key |
fields |
Comma-separated columns to return: fields=title,artist_id |
All columns |
count |
true adds the total number of matching rows |
false |
<column>=<value>, <column>[op]=<value> |
Filters, see Filtering | none |
Rows are always in a stable order: without sort they are ordered by the primary key, and with
sort the primary key breaks ties (sort=artist_id returns albums by artist, then by album_id).
Only tables without a primary key have no guaranteed order.
A limit above 1000 returns 1000 rows. Non-numeric limit or offset values are ignored and
the defaults apply. An unknown column in sort is a 400 naming the sort parameter.
curl -s "$RESTWARD_API_URL/api/public/album?sort=artist_id,-title&limit=3" \
-H "X-API-Key: $RESTWARD_API_KEY"const url = new URL("/api/public/album", process.env.RESTWARD_API_URL);
url.search = new URLSearchParams({ sort: "artist_id,-title", limit: "3" });
const albums = await (await fetch(url, { headers })).json();albums = session.get(f"{api}/api/public/album", params={"sort": "artist_id,-title", "limit": 3}).json()var albums = await http.GetFromJsonAsync<List<Dictionary<string, JsonElement>>>(
"/api/public/album?sort=artist_id,-title&limit=3");[
{ "album_id": 4, "title": "Let There Be Rock", "artist_id": 1 },
{ "album_id": 1, "title": "For Those About To Rock We Salute You", "artist_id": 1 },
{ "album_id": 3, "title": "Restless and Wild", "artist_id": 2 }
]Choosing columns
fields returns only the listed columns, in the order you list them, for lists and for reading
one row. The primary key is not added automatically: include it if you need it.
curl -s "$RESTWARD_API_URL/api/public/album?fields=title,artist_id&limit=2" -H "X-API-Key: $RESTWARD_API_KEY"
# [{"title":"For Those About To Rock We Salute You","artist_id":1},{"title":"Balls to the Wall","artist_id":2}]
curl -s "$RESTWARD_API_URL/api/public/album/1?fields=title" -H "X-API-Key: $RESTWARD_API_KEY"
# {"title":"For Those About To Rock We Salute You"}An unknown column is a 400: {"error":"Unknown column 'genre' in fields on 'album'.","parameter":"fields"}.
Total count
count=true runs a second COUNT query with the same filters and changes the response to an
object with the total and the page, plus an X-Total-Count header:
curl -si "$RESTWARD_API_URL/api/public/album?artist_id=90&limit=2&count=true" \
-H "X-API-Key: $RESTWARD_API_KEY"const url = new URL("/api/public/album", process.env.RESTWARD_API_URL);
url.search = new URLSearchParams({ artist_id: "90", limit: "2", count: "true" });
const { total, items } = await (await fetch(url, { headers })).json();page = session.get(f"{api}/api/public/album", params={"artist_id": 90, "limit": 2, "count": "true"}).json()
total, items = page["total"], page["items"]var page = await http.GetFromJsonAsync<JsonElement>("/api/public/album?artist_id=90&limit=2&count=true");
var total = page.GetProperty("total").GetInt64();HTTP/1.1 200
X-Total-Count: 21
{
"total": 21,
"items": [
{ "album_id": 94, "title": "A Matter of Life and Death", "artist_id": 90 },
{ "album_id": 95, "title": "A Real Dead One", "artist_id": 90 }
]
}Counting costs an extra query that scans every matching row, so on large tables ask for it only
when you need it (for example on the first page), not on every page. count must be true or
false.
Read one row
Pass the primary key as the last path segment. For a composite primary key, pass the values
comma-separated in key-column order: /api/public/playlist_track/1,3402.
curl -s "$RESTWARD_API_URL/api/public/album/1" \
-H "X-API-Key: $RESTWARD_API_KEY"const res = await fetch(`${process.env.RESTWARD_API_URL}/api/public/album/1`, { headers });
const album = res.status === 404 ? null : await res.json();res = session.get(f"{api}/api/public/album/1")
album = None if res.status_code == 404 else res.json()var res = await http.GetAsync("/api/public/album/1");
var album = res.StatusCode == HttpStatusCode.NotFound
? null
: await res.Content.ReadFromJsonAsync<Dictionary<string, JsonElement>>();{ "album_id": 1, "title": "For Those About To Rock We Salute You", "artist_id": 1 }| Situation | Response |
|---|---|
| No row with that key | 404 with an empty body |
Key of the wrong type (/artist/abc on an integer key) |
422 {"error":"Value 'abc' is not valid for column 'artist_id' of type 'integer'.","column":"artist_id"} |
| Composite key with the wrong number of values | 400 with an error naming the key columns |
| Unknown table | 404 with an empty body |
Tables without a primary key are listable, but cannot be read, updated or deleted by key.
Caching
If the endpoint has a cache TTL (set on the dashboard's Endpoints page), GET responses are
served from a cache for that many seconds. Each response says which:
X-Cache: HIT
Age: 12
Cache-Control: private, max-age=18A write through the API to the same table clears its cached responses immediately; changes made directly in the database show up once the TTL has passed.
Data types
Values are returned as the database driver reads them: numbers as JSON numbers, text as
strings, NULL as null, and timestamps as ISO 8601 strings, for example
"invoice_date": "2022-02-18T00:00:00" for a PostgreSQL timestamp column.