REST API¶
Clients send SQL to Sluice over HTTP. The default address is :8080, set by rest.listen.
Routes¶
| Method | Path | Auth | What it does |
|---|---|---|---|
POST | /v1/query | API key or JWT | Runs one SQL statement. |
POST | /v1/sample | API key or JWT | Returns a random sample of rows from one table. |
GET | /v1/approvals/{id} | API key or JWT | Returns the state of an approval to its owner. |
POST | /v1/approvals/{id}/accept | approval token | Accepts the approval. A GET returns an HTML page with a button that sends the POST. |
POST | /v1/approvals/{id}/reject | approval token | Rejects the approval. A GET returns such a page too. |
GET | /v1/health | none | Returns 200 with status and version while the server runs. |
GET | /v1/ready | none | Returns 503 when a data source is unhealthy. |
GET | /v1/version | none | Returns version, commit, build_time, go and parser_version of the build. |
Send an API key as X-Api-Key: <id>.<secret> or a JWT as Authorization: Bearer <jwt>. See Subjects.
Example¶
A query with max_rows:
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_alice.world" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, name, price_cents FROM shop.main.products ORDER BY id", "max_rows": 2}'
{
"query_id": "<query-id>",
"columns": ["id", "name", "price_cents"],
"rows": [
[1, "Ceramic Mug", 1299],
[2, "A5 Notebook", 799]
],
"row_count": 2,
"truncated": true,
"warning": {
"code": "ERR_RESULT_TRUNCATED",
"message": "result truncated"
}
}
The table has 4 rows, so truncated is true.
Request fields¶
| Field | Type | Meaning |
|---|---|---|
sql | string | The SQL statement. |
max_rows | int | Row limit. Default limits.maxRows, at most limits.maxRowsCeiling. |
timeout_ms | int | Query timeout. Default limits.queryTimeout, at most limits.maxQueryTimeout. |
format | string | json (default) or csv. Without it the Accept header decides: text/csv gives CSV, application/json or */* gives JSON. |
meta | object | String pairs for the audit record (client_meta). At most 16 pairs, 64-byte keys, 256-byte values. |
Response fields¶
| Field | Type | Meaning |
|---|---|---|
query_id | string | Query id, also in the audit record. |
columns | array | Column names. |
rows | array | One array per row. |
row_count | int | Number of returned rows. |
truncated | bool | true when the row limit or limits.maxResultBytes cut the result. |
warning | object | Set when truncated, with code ERR_RESULT_TRUNCATED. |
With format: csv the body is CSV:
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_alice.world" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, name FROM shop.main.products ORDER BY id", "format": "csv"}'
Sample request fields¶
POST /v1/sample returns a random sample of one table. Sluice builds a SELECT from the request. The query then passes the same policies, limits, budgets and approvals as POST /v1/query. Row filters run before the sample, so the sample holds only rows the subject may see.
curl -s http://localhost:8080/v1/sample \
-H "X-Api-Key: sl_demo_alice.world" \
-H "Content-Type: application/json" \
-d '{"table": "shop.main.products", "rows": 2, "seed": 7}'
{
"query_id": "<query-id>",
"columns": ["id", "name", "price_cents"],
"rows": [
[1, "Ceramic Mug", 1299],
[3, "Black Pen", 199]
],
"row_count": 2,
"truncated": false
}
The response has the same fields and headers as for POST /v1/query.
| Field | Type | Meaning |
|---|---|---|
table | string | Required. A name of the form catalog.schema.table. |
columns | array | Column names to return. Empty returns all columns. |
rows | int | Number of rows. Set rows or rate, not both. |
rate | number | Share of rows, from 0.000001 to 1. |
method | string | See the table below. Default reservoir with rows, bernoulli with rate. |
stratify_by | array | Column names whose value mix the sample keeps. Only with stratified. |
seed | int | From 0 to 2147483647. Without it Sluice picks a random seed. |
where | string | A SQL condition on the table. Policies check it like the SQL of a query. |
format | string | json (default) or csv, as for POST /v1/query. |
| Method | Size | What it does |
|---|---|---|
reservoir | rows | Returns exactly rows rows, or all rows of a smaller table. |
bernoulli | rate | Keeps each row with the chance rate. The row count varies. |
system | rate | Keeps whole blocks of rows. It is fast but less random. On a small table it can return no rows. |
stratified | rows or rate | Gives each group of stratify_by its share of the sample. |
rowsabove the cap is lowered to the cap without an error. The cap is the smallest ofsample.maxRows,limits.maxRowsandlimits.maxRowsCeilingminus one.- A
ratesample has no cap of its own.limits.maxRowscuts the result. A cut sample holds the first sampled rows in scan order, so it is not uniform. stratifieddrops groups with fewer rows thansample.minGroupRows. Withrate, it keeps at least one row of each other group, unlesslimits.maxRowscuts the result. Withrows, it can drop whole groups when there are more groups thanrows.stratifiedis refused withERR_MASK_UNSUPPORTED_CONTEXTwhen a post-query mask applies to a returned column. Leave that column out ofcolumns.- With
stratified, a table with a column namedsluice_h,sluice_rn,sluice_n,sluice_torsluice_xneeds explicitcolumns. - With the same seed, data and policies,
reservoirandstratifiedreturn the same rows. wheremust stay one condition. A text that adds aUNIONor another clause givesERR_UNSUPPORTED_SYNTAX.- A wrong field value gives 400
ERR_SYNTAX. Names of up to 63 bytes are allowed. Names that start withsluice_are reserved. - The audit record carries
extras.action: sampleand the request inextras.sample. See Audit log.
Response headers¶
| Header | Set when |
|---|---|
X-Request-Id | Always. Your value, or a new one. |
X-Query-Id | The request got a query id. |
X-Sluice-Applied-Policies | A policy applied. Names, separated by commas. |
WWW-Authenticate | The credential is missing or wrong: 401 ERR_UNAUTHORIZED. |
X-Sluice-Row-Count, X-Sluice-Truncated | CSV only. HTTP trailers, sent after the body. |
X-Sluice-Warning | The same, when the result is truncated. |
Errors¶
An error is a JSON object. Error codes lists every code with its HTTP status.
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: sl_demo_alice.world" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT employee, amount FROM shop.main.salaries"}'
{
"code": "ACL_DENIED",
"message": "access denied",
"query_id": "<query-id>",
"policy": "deny-salaries"
}
| Field | Meaning |
|---|---|
code | Error code. |
message | Text for people. |
query_id | Query id, if the request got one. |
policy | Policy that refused the query, if one did. |
details | Extra data, for example approval_id. |
A 404 for an unknown path and a 405 are plain text. The approval routes answer 404 as {"error": "not found"}.
Approval routes¶
The routes exist when the server config sets approval.publicBaseUrl. Send the token as ?token= or as an X-Approval-Token header. The table shows the answers to a POST, and to a GET in the last row. See Approvals.
| Request | Answer |
|---|---|
| Right token | 200, note: recorded |
| Same decision again | 200, note: already decided |
| Other decision later | 409. After the query ran: 200, state consumed |
| Unknown id or wrong token | 404 |
GET or HEAD, any id and token | 200, the same HTML page, no change |
GET /v1/approvals/{id} returns the state:
curl -s "http://localhost:8080/v1/approvals/$(cat data/approval_id)" \
-H "X-Api-Key: sl_demo_alice.world"
Limits¶
- A body field outside the Request fields table gives 400
ERR_SYNTAX, exceptparams: Sluice ignores an array there. - A statement with a parameter such as
$1gives 400ERR_UNSUPPORTED_SYNTAX. Request lifecycle lists the statement rules. - An unknown
formatorAcceptvalue gives 400ERR_SYNTAX. A request for Arrow output gives 400ERR_UNSUPPORTED_SYNTAX. - A body over
rest.maxBodyBytesgives 413ERR_PAYLOAD_TOO_LARGE. A request longer thanrest.requestTimeoutgives 504ERR_TIMEOUT. limits.globalRpsandlimits.perIpRpslimit/v1/queryand/v1/samplebefore authentication: 429ERR_RATE_LIMITED.