Skip to main content

Response shape

The Response tab shows what a consumer actually receives when it calls the endpoint: the frozen columns of the live version, and the settings that decide how rows become JSON. Use it to rename keys, return positional arrays, drop the envelope, or build a custom document with a script.

The Response tab of Orders by status: frozen columns status, revenue and order_count, and the Output shape settings

The default response​

By default every call answers with a wrapped envelope. The rows are in data, and meta describes the call:

{
"status": "ok",
"message": null,
"data": [
{ "status": "F", "revenue": 211278198.4, "order_count": 1483 },
{ "status": "O", "revenue": 210258874.46, "order_count": 1506 },
{ "status": "P", "revenue": 12650639.01, "order_count": 76 }
],
"meta": {
"version": 2,
"cached": false,
"request_id": "78140630-b51e-4aa7-bcc2-79f42c988174",
"row_count": 3,
"columns": [
{ "name": "status", "ty": "varchar(1)", "required": true },
{ "name": "revenue", "ty": "double", "required": true },
{ "name": "order_count", "ty": "bigint", "required": true }
]
}
}
FieldMeaning
status"ok" on success, "error" on a refusal. Mirrors the HTTP status; it never replaces it.
messageAlways null on success. A string on a refusal.
dataThe rows. An array for a one-statement endpoint; an object keyed by statement name for several.
meta.versionThe published version that answered. Quote it when debugging: it identifies the exact frozen contract.
meta.cachedtrue when the cache answered.
meta.request_idIdentifies the call in Activity. Quote it to support.
meta.row_countThe number of rows.
meta.columnsThe column names and engine types of the statement. With several statements, meta.statements holds one entry per statement.

Frozen columns​

The first section, Frozen columns, lists the columns of the live version, not the draft. Publishing runs the SQL against the engine to learn the columns and freezes them. At serving time Egret checks every response against them and fails closed (503 schema_drift) if the engine's columns have changed, so a consumer never silently receives different columns than were published.

With several statements the tab shows one table per statement, headed by its name. Before the first publish it says This endpoint has no live version yet. Publish it to see the columns a consumer will receive.

A column marked May be absent comes from a branch that only some parameter values reach: on the calls that skip it, the key is missing from the row entirely, never null. For example an endpoint that adds revenue only when ?with_revenue=true:

{ "data": [{ "status": "F" }, { "status": "P" }, { "status": "O" }] }

versus, with ?with_revenue=true:

{ "data": [{ "status": "O", "revenue": 1.02837633121e+9 }, { "status": "P", "revenue": 6.333947532e+7 }] }

Every other column is in every response.

Response transform​

Build the response with a script​

Tick Build the response with a script to replace the standard data with a document you build. This is for a shape no SQL projection can express, such as a single object that joins a total with a list. Most endpoints do not need it: Shape and Naming already cover renaming keys and objects versus arrays.

The Response tab of Yearly revenue summary: two statements' frozen columns, the transform script (227 / 8,192 bytes) and three declared returns

The script is Starlark, a deterministic Python subset with no input or output, no network, no clock and no randomness. It must define transform(params, statements):

def transform(params, statements):
total = statements["total"]
return {
"year": params["year"],
"orders": total[0]["orders"] if len(total) > 0 else 0,
"breakdown": statements["by_status"],
}
ArgumentWhat it holds
paramsThe resolved parameter values, by name.
statementsThe rows of every statement, as a dictionary keyed by statement name, each a list of row objects. A one-statement endpoint is still keyed ("main"), so adding a second statement later does not break the script.

The script must return a JSON object. The size of the script is shown under the editor (227 / 8,192 bytes) and is capped at 8,192 bytes; publish refuses a larger one, because a transform reshapes what a statement already returned and is not where business logic belongs.

Declared returns​

A script's output cannot be inferred from the SQL: it may depend on the data. So you declare what data looks like, in the Declared returns table.

ColumnWhat it does
NameThe key in the object the script returns. Names must be non-blank, have no leading or trailing space, and be unique.
TypeA label for the column, for example bigint or array. It is written into the OpenAPI document. Egret checks which keys are present, not their types.
RequiredWhen ticked, the key must be in every response. Untick it for a key that may be absent.

Press Add return column to add a row. At most 64 columns can be declared, and at least one is required: No return columns declared … Preview and Publish both refuse this.

What Egret checks with returns:

  • At publish, the script is run on test inputs, including a case where every statement returns no rows, and refused if its output disagrees with the declaration. A script that assumes a row exists fails at publish: the empty-rows probe: the transform script failed while running … Index 0 is out of bound. Guard against empty results as in the example above.
  • At every request, the response is checked against returns, and a mismatch fails closed instead of reaching the consumer. An undeclared key, or a missing required key, is refused.
note

Preview in the Query tab does not run the script. It shows the statement's own rows and says so: The rows below are the statement's own — the script's input, not the response a consumer receives. Use Try it to see the transformed response; see Try it.

With a transform, the Shape and Naming settings are not shown: they describe how a SQL row becomes JSON, and a transform's output never goes through that step. Envelope still applies. Unticking the box brings the previous Shape and Naming back.

Output shape​

Shape​

OptionEach row isExample
Objects (default)A named object.{"order_status": "F", "order_count": 7304}
ArraysA positional array, smaller but dependent on column order.["F", 7304]

Naming​

How column names are written in the response.

OptionResult for order_count
As-is (default)order_count
camelCaseorderCount
snake_caseorder_count

Two columns that collapse onto one name make the endpoint refuse to serve until it is re-published: 503 output_naming_collision. The columns list in meta still shows the engine's own column names.

Envelope​

OptionResponse
Wrapped (default){status, message, data, meta} around the rows.
Bare — data onlyThe rows alone, with no version, cache flag or request id.

A bare response is the data alone:

[{"order_status":"F","order_count":7304},{"order_status":"O","order_count":7333},{"order_status":"P","order_count":363}]

Refused calls still answer with the full error body either way, because an unwrapped error would be a bare null and say nothing.

Shape changes break consumers

Changing Shape, Naming or Envelope changes every row or the wrapper. Publishing reports each as a breaking change (for example wrapped becomes bare, so the wrapper is added or removed) and asks you to acknowledge it. See Publishing.

OpenAPI document​

At the bottom of the tab, Download OpenAPI downloads this endpoint's contract for a service account you choose. The line next to it shows the base URL. See OpenAPI export.

Limits and known issues​

  • The script is capped at 8,192 bytes and 64 declared returns.
  • Types in Declared returns are documentation: they are not enforced against the script's output.
  • With Naming set to camelCase or snake_case, meta.columns keeps the engine's names.

Further reading​