Formats, OpenAPI and errors¶
Response formats¶
Choose with the Accept header:
Accept |
Response |
|---|---|
application/json (default, also */*) |
JSON array of objects |
text/csv |
header row plus one line per row; embedded values are written as JSON |
application/vnd.pgrst.object+json |
a single JSON object, or 406 unless exactly one row matches |
any other concrete type, e.g. text/plain, application/octet-stream, image/png |
a single value as is: see below |
curl 'localhost:3000/books?select=id,title' -H 'Accept: text/csv'
curl 'localhost:3000/books?id=eq.1' -H 'Accept: application/vnd.pgrst.object+json'
Single values and files¶
Ask for any concrete media type that isn't in the table above, and the response body is the
value itself instead of JSON, with the Content-Type you asked for. The request must select
one column and match one row:
# the bytes of a BLOB SUB_TYPE BINARY column, served as an image
curl 'localhost:3000/covers?select=image&book_id=eq.1' -H 'Accept: image/png' > cover.png
# a text column, as text
curl 'localhost:3000/books?select=summary&id=eq.1' -H 'Accept: text/plain'
# a function result, as text
curl 'localhost:3000/rpc/add_vat?amount=100' -H 'Accept: text/plain'
- Binary columns (
BLOB SUB_TYPE BINARY,CHARACTER SET OCTETS) are sent as their bytes; text columns as their text; numbers, booleans and dates as their JSON text (9.99,true).text/*,+jsonand+xmltypes get; charset=utf-8. NULLgives an empty204.- Types a browser would run or render as a document (
text/html, SVG, XML, JavaScript, PDF) are sent as downloads (Content-Disposition: attachment), so a stored value can never run as a page of the API. - More than one row gives
406(FBRST116), more than one column406(FBRST107). application/octet-streamworks for any column. A column's own type (image/png,application/pdf) is handy to serve files straight from the database; keep the type in another column if it varies per row, and read it first.- This also covers formats FirebirdREST doesn't produce itself: a function that builds GeoJSON
text can be requested with
Accept: application/geo+json. - It works for
/rpcfunctions, procedures with one output parameter, selectable procedures, and writes withPrefer: return=representation(for examplePOST /covers?select=book_id). - A built-in type anywhere in
Accept, or a wildcard, takes precedence: a browser'stext/html, */*still gets JSON.
Binary data and memory¶
In JSON, BLOB SUB_TYPE BINARY and CHARACTER SET OCTETS values are base64 strings, and are
sent the same way on input. Firebird returns a BLOB as a whole, so each BLOB in a response is
held in memory while the request runs: a raw single-value response holds it once, a JSON response
also holds its base64 form. Keep that in mind for large files:
- Select only the columns you need:
select=*includes BLOB columns. - Serve files with a raw single-value request (above) rather than through JSON.
- To show sizes without reading the data, expose
OCTET_LENGTH(image)through a computed field. db-max-rowsbounds the number of rows, not their size.
Query plans¶
To see how Firebird runs a request, ask for its plan instead of its data. This is off by default,
because plans show table and index names: set db-plan-enabled = true (for example in
development).
curl 'localhost:3000/books?select=title,authors(name)&price=lt.10' -H 'Accept: application/vnd.pgrst.plan'
-- statement 1
SELECT t0."TITLE", t0."AUTHOR_ID" FROM "BOOKS" t0 WHERE ((t0."PRICE" < ?))
Select Expression
-> Filter
-> Table "BOOKS" as "T0" Full Scan
-- statement 2
SELECT t0."NAME", t0."ID" FROM "AUTHORS" t0 WHERE (t0."ID" IN (?, ?))
Select Expression
-> Filter
-> Table "AUTHORS" as "T0" Access By ID
-> Bitmap
-> Index "RDB$PRIMARY1" List Scan (full match)
Here price has no index, so the books are read with a full scan.
- The response lists every statement the request runs: one per embed level, and the count with
Prefer: count=exact. The plan is Firebird's explained plan (MON$EXPLAINED_PLAN). application/vnd.pgrst.plan+jsonreturns[{"query": "...", "plan": "...", "tree": [...]}]: the plan as text and parsed into nodes, each{"node": "Filter", "children": [...]}, one tree per expression of the statement (a sub-query or a block's statements add trees).application/vnd.pgrst.planand+textreturn text.- Plans are available for every method. To know the statements of embedded resources the
request is run with its result discarded; a
POST,PATCH,PUTorDELETEruns in a transaction that is always rolled back, so nothing is written. Firebird has no plan for a plainINSERT(an empty one is shown); multi-row inserts show theirEXECUTE BLOCK, andUPDATEandDELETEshow how the rows are found. - With plans disabled, the plan media types get
406.
OpenAPI¶
GET / returns an OpenAPI 3 document describing the
tables, views, procedures and functions the caller's role may use, with their columns, types,
keys and computed fields, and only the methods the role is granted (SELECT → get, INSERT →
post, ...). Send the request with the token whose view you want; without one, the anonymous
role's. openapi-mode = "ignore-privileges" describes everything instead, and "disabled"
turns / off. Point Swagger UI, Postman or a code generator at it:
Behind a proxy, set openapi-server-proxy-uri to the public URL so the document's servers
entry is right.
Errors¶
Errors are JSON objects in PostgREST's shape:
{
"code": "23000",
"message": "violation of FOREIGN KEY constraint INTEG_20 on table ORDERS",
"details": "Foreign key reference target does not exist\nProblematic key value is (\"CUSTOMER_ID\" = 99)",
"hint": null
}
code is Firebird's SQLSTATE for database errors, or an FBRST code for API errors.
message is the first line of Firebird's message and details the rest.
| Status | When |
|---|---|
300 |
ambiguous embedding: several foreign keys connect the tables; add a !hint |
400 |
bad query syntax, unknown column, invalid value, constraint or validation error, PSQL EXCEPTION |
401 |
missing, invalid or expired JWT; or permission denied for an anonymous request |
403 |
permission denied for an authenticated role, or the role isn't granted to the database user |
404 |
unknown table, view, procedure or function |
405 |
method not allowed (e.g. PUT without primary key filters) |
406 |
requested media type unavailable, or a single object requested and the row count isn't 1 |
409 |
unique or foreign key violation, update conflict, deadlock |
416 |
invalid Range, or (with Prefer: count=exact) an offset beyond the last row |
503 |
the database can't be reached |
API error codes¶
| Code | Meaning |
|---|---|
FBRST000 |
unexpected server error (see the log) |
FBRST001 |
the database can't be reached (503) |
FBRST002 |
the request took longer than server-request-timeout (503) |
FBRST100 |
filter syntax or unknown operator |
FBRST101 |
order syntax |
FBRST102 |
invalid body or parameter value |
FBRST103 |
select syntax, or invalid Range |
FBRST104 |
unsupported cast |
FBRST105 |
PUT needs eq filters on the whole primary key |
FBRST106 |
Accept-Profile/Content-Profile names a schema that isn't exposed (or the server has no schemas) |
FBRST107 |
no acceptable media type |
FBRST108 |
a filter or order references an embed that isn't in select |
FBRST109 |
PATCH/DELETE without filters or limit (db-safe-updates) |
FBRST114, FBRST115 |
PUT body doesn't match the URL |
FBRST116 |
single object requested, row count isn't 1 |
FBRST117 |
method not allowed |
FBRST120 |
operation not available on this embed (e.g. !inner on an executable procedure) |
FBRST121 |
two output keys would have the same name |
FBRST123 |
aggregates are disabled |
FBRST124 |
upsert without primary key or on_conflict |
FBRST125 |
invalid path |
FBRST126 |
order=embed(column) on a to-many embed, or together with aggregates |
FBRST127 |
PATCH/DELETE with limit but no order, or offset without limit |
FBRST128 |
more than 1024 objects for a selectable procedure with params=multiple-objects, or arguments over 48 KB in all |
FBRST129 |
params=single-object on a routine without exactly one text parameter |
FBRST130 |
an unknown or invalid preference with Prefer: handling=strict |
FBRST131 |
a PATCH/DELETE would affect more rows than Prefer: max-affected |
FBRST134 |
the request body is larger than server-max-body-size (413) |
FBRST135 |
GET /events/... while db-events is empty (404) |
FBRST136 |
an event name that isn't in db-events (404) |
FBRST137 |
the role is not in db-events-roles (403, 401 when anonymous) |
FBRST133 |
a raw request body (text/plain, application/octet-stream...) sent to a routine that doesn't have exactly one parameter |
FBRST132 |
a query plan was asked for a method without statements (OPTIONS) |
FBRST200 |
no relationship between the tables |
FBRST201 |
several relationships, !hint needed |
FBRST202 |
unknown procedure or function, or unknown argument |
FBRST204 |
unknown column |
FBRST205 |
unknown table or view |
FBRST300 |
role not granted to the authenticator user, or no JWT secret configured |
FBRST301 |
invalid JWT |
FBRST302 |
anonymous access is disabled |