Praxsuite

Query

Vincent Depassier · September 17, 2026

POST /{workspaceId}/query

The one data endpoint. Reads and writes both go through it, and which one you get is decided by the body, not by the path or the method.

Auth: API key (sk_live_ or pk_live_), or an end-user JWT.

Request

POST https://gateway.praxsuite.com/{workspaceId}/query
Authorization: Bearer sk_live_...
Content-Type: application/json
{
  "refs": { "Customers": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
  "query": {
    "from": "Customers",
    "select": ["Name", "Email"],
    "where": [{ "field": "Status", "op": "eq", "value": "Active" }],
    "orderBy": [{ "field": "Name", "dir": "asc" }],
    "limit": 50
  },
  "includeTotalCount": false
}

Field

Type

Notes

refs

object

Your aliases → table GUIDs. Required, 1–20 entries

query

object

A read. Mutually exclusive with mutation

mutation

object

A write. Mutually exclusive with query

includeTotalCount

boolean

Fills meta.total. Costs a second pass — ask for it only when drawing a page count

refs is also the complete list of what the request is allowed to touch, which is what lets the gateway check it against your scopes before doing any work.

query

Field

Type

Notes

from

string

An alias from refs

select

array

Column names, relation objects, or aggregate objects. Omit for every readable column

where

array

Conditions, AND-ed at the same level

orderBy

array

`{ "field": …, "dir": "asc" \

"desc" }`

groupBy

array

Column names, for aggregate queries

having

array

Conditions applied after grouping

limit

number

Default 50, ceiling 1000, and a table scope may cap it lower

offset

number

Rows to skip

An aggregate select item is { "field": "Total", "fn": "sum", "alias": "TotalRevenue" }.

select: "*" is not an error when your column scope is narrow — it returns the columns you may read, and nothing tells you the others exist. Naming a table you have no scope for is an error: 403 SCOPE_VIOLATION.

mutation

Field

Type

Notes

type

string

insert, update or delete

table

string

An alias from refs

values

array

For insert: row objects, column name → value

set

object

For update: column name → new value

where

array

Required for update and delete. There is no unscoped mutation

returning

bool or array

For insert: true for every readable column, or a list of column names. Omitted returns nothing

notify

object

Announce the write on an Event Bus bus once it commits

notify carries no payload of its own: { "bus": "{topic}:{instance}" }. The event body is built from what the database returned, never from the request, so a write cannot be used to relay arbitrary content. The topic must be declared, enabled, and publishable by the caller — all checked before the mutation runs, so a write never commits and then fails to announce. If the check fails you get 403 NOTIFY_DENIED and nothing is written.

How a request is checked

Your scopes apply in both directions, on reads and on writes alike. Nothing your client sends can remove either one — which is why a delete can never reach rows the credential could not have read.

A request is authorised before anything runs, so a table outside your scope is 403 rather than an empty result; the scope's row filter is then ANDed into every where; and the response is filtered again on the way out, dropping unreadable columns and applying masking rules. A column you may filter on but not read still narrows the rows.

Response — read

{
  "data": [
    { "Name": "Acme Corp", "Email": "hello@acme.com" }
  ],
  "meta": {
    "count": 1,
    "limit": 50,
    "offset": 0,
    "total": null,
    "durationMs": 4
  }
}

meta.total is null unless you asked for includeTotalCount. Read meta.limit rather than assuming yours was honoured.

Response — write

{
  "affectedRows": 1,
  "data": [{ "Id": "…", "Name": "Acme Corp" }],
  "meta": { "type": "insert", "table": "Customers", "durationMs": 7 }
}

data appears only for an insert that asked for returning.

Response headers

Header

When

X-Cache, X-Cache-Credential, ETag, Cache-Control

The credential has caching enabled

X-Api-Usage-Overage, X-Api-Usage-Current, X-Api-Usage-Included

The workspace is past its included calls on pay-as-you-go

With caching on, send a previous ETag back as If-None-Match to get 304 Not Modified instead of the body.

Errors

The structured envelope, with a code. See Errors and limits for the full table — the ones specific to this endpoint are INVALID_REQUEST, INVALID_REFS, INVALID_QUERY, INVALID_MUTATION, SCOPE_VIOLATION, NOTIFY_DENIED, QUERY_TIMEOUT and MUTATION_TIMEOUT.

Every call — including the ones that fail — is written to the query log, with the body, the caller, the source IP and the duration. API Gateway → Logs.

Going deeper

The grammar itself — operators, relations, aggregations, mutation semantics, the security model — is documented in full under API Gateway → PraxQL in the documentation, and can be tried without writing any code in the Playground.