Splitgraph has been acquired by EDB! Read the blog post.


These are all the requests supported by Seafowl's HTTP API.

Query result format

Seafowl returns query results in the HTTP response body using the JSON Lines format, with one line per row:

{"text_column":"VALUE 1","float_column":12.34,"int_column":12}
{"text_column":"VALUE 2","float_column":56.78,"int_column":13}
{"text_column":"VALUE 3","float_column":91.01,"int_column":14}

It currently doesn't return any other (like type) information or support other return formats.


For the purposes of authorization, Seafowl distinguishes between read-only (SELECT) and write (all other statements) SQL queries. By default, write queries require authorization and read-only don't.

Where authorization is required, it is performed by passing an Authorization: Bearer (password) HTTP header.

You can change the authorization settings in the configuration.



Execute a read/write query.

Body: {"query": "(SQL query)"}


  • Authorization: Bearer (password): if authorization is required
  • Content-Type: application/json: required


  • 200 OK: if the query is successful
  • 400 Bad Request: error executing or planning the query, with the reason in the response body
  • 401 Unauthorized: no or invalid password
  • 403 Forbidden: tried to write as an anonymous user or using a read-only password

GET /q/(query_or_hash)[.extension]

Conditionally execute a read-only (SELECT) query if its result has changed.

  • query_or_hash: Either a URL-encoded SQL query, or a SHA-256 hash of the raw query. Required.
  • extension: Optional extension, like .csv. Ignored.


  • X-Seafowl-Query: Optional—when the query SHA-256 hash is supplied as the URL path parameter either this header or the GET request body are required. Contains the actual query, which must match the hash after decoding with decodeURIComponent().
  • If-None-Match: ETag denoting the query result cached by the client. Optional.
  • Authorization: Bearer (password): if authorization is required. This endpoint abides by the read access config setting only, since it doesn't support writes. Optional.

Body: {"query": "(SQL query)"}. Optional. Must match the SHA-256 hash. Either the request body or the X-Seafowl-Query header are required when the query hash is supplied as the URL path parameter.


  • 200 OK: If no ETag has been passed or the passed ETag doesn't match the current ETag, generated from the latest versions of all tables involved in the query: the query result with a header ETag: [current ETag].
  • 301 Not Modified response: if the ETag matches the current version.
  • 400 Bad Request: error executing or planning the query, with the reason in the response body, or a mismatch between the query SHA and the SHA of the query body
  • 401 Unauthorized: cached read-only endpoint is disabled because reads require a password

POST /upload/(schema_name)/(table_name)

Upload a CSV/Parquet file to a certain table. If the table already exists, attempt to insert the new data, if the schemas match.

Body: A multipart/form-data body with the following attributes:

  • schema: Optional. Instead of Seafowl inferring the schema automatically, allows the user to provide an explicit JSON representation of the Arrow columnar data. Applicable only for CSV uploads.
  • has_headers: Optional. Whether an explicit schema is passed through schema or one is being referred, by default it is assumed that the headers are present, to denote otherwise set to false (has_headers=false). Applicable only for CSV uploads.
  • data: the file that's being uploaded


  • Authorization: Bearer (password): if authorization is required


  • 200 OK: successful upload
  • 400 Bad Request: missing or invalid body or schema
  • 401 Unauthorized: no or invalid password
  • 403 Forbidden: tried to write as an anonymous user or using a read-only password


CORS pre-flight request (see the MDN).

Response headers:

  • Access-Control-Allow-Origin: same value as the inbound Origin header
  • Access-Control-Allow-Methods: GET, POST
  • Access-Control-Allow-Headers: X-Seafowl-Query, Authorization, Content-Type

Response Headers

Seafowl returns a variety of headers. Let's take a look at a simple query:

curl -i -H "Content-Type: application/json" \
-X POST "http://localhost:8080/q" -d@- <<EOF
{"query": "SELECT 1"}
HTTP/1.1 200 OK
content-type: application/json; arrow-schema=%7B%22fields%22%3A%5B%7B%22children%22%3A%5B%5D%2C%22name%22%3A%22Int64%281%29%22%2C%22nullable%22%3Afalse%2C%22type%22%3A%7B%22bitWidth%22%3A64%2C%22isSigned%22%3Atrue%2C%22name%22%3A%22int%22%7D%7D%5D%2C%22metadata%22%3A%7B%7D%7D
x-seafowl-query-time: 6
vary: Authorization, Content-Type, Origin, X-Seafowl-Query
transfer-encoding: chunked
date: Fri, 23 Jun 2023 10:16:04 GMT


  • x-seafowl-query-time seeks to measure the total processing time, as delineated by the interval between when Seafowl finished reading the HTTP request body, and when it started writing the HTTP response body.

  • content-type - at the time of writing, Seafowl returns the HTTP response body in ndjson format aka JSON Lines, so this is set to application/json. After that comes the Arrow schema, URL encoded.

    The above example decodes into:

      "fields": [
          "children": [],
          "name": "Int64(1)",
          "nullable": false,
          "type": { "bitWidth": 64, "isSigned": true, "name": "int" }
      "metadata": {}
  • vary helps browsers more easily cache if the origin changes. More info

  • cache-control is user-configurable and may be helpful if you front Seafowl with a CDN. More in the docs. At the time of writing defaults to max-age=43200, public