REST & GraphQL

Instant APIs over exactly the data you choose

Expose a table, view or stored procedure and endpoints exist immediately: REST for simple reads and writes, GraphQL for joins and nested reads. Security is enforced in the database, so the same key behaves the same way from an app, the CLI or an AI agent.

# read rows the key is allowed to see
$ curl -H "X-API-Key: acme:dlk_..." \
  ".../auth/acme/api/pizza_menu_items?$first=20"
{ "value": [ { "Sku": "MARG-12", "Price": 9.5 } ] }

# the same entity over GraphQL
$ curl -X POST .../auth/acme/graphql -d @query.json

What do you get per tenant?

In plain wordsYour apps can read and write your business data over the web straight away, without anyone writing an API layer first.

REST

CRUD endpoints per exposed entity with OData-style filtering, sorting and cursor paging. Stored procedures execute on POST only. PATCH is the preferred upsert: send only the columns that changed.

GraphQL

A typed schema generated from your entities and the relationships you declare with set_relationship. One round trip for nested reads and aggregates; preferred whenever you would otherwise join.

Events & webhooks

Row-change events by server-sent events, polling, or signed webhooks with retries and dead-letter handling, so downstream systems react to changes instead of polling tables.

Read-only SQL and exports

For what GraphQL cannot express: a SELECT-only query endpoint capped at 10,000 rows, and background CSV/Parquet exports to a signed URL, with a hard 50,000,000-row cap.

How access works

Send the API key in a header and the platform exchanges it for you. The key decides everything: an unscoped key acts as the shared data-user role; a scoped key gets its own role limited to the entities, actions and fields you listed, enforced fail-closed in the database. Row-level security then filters rows on every query. Revoking a key takes effect immediately.

  • X-API-Key: <tenant>:dlk_... on every request (simplest, what dlake itself does).
  • Or exchange explicitly: POST /api/auth/apikeys/exchange returns a bearer JWT valid about 60 minutes.
  • Send X-MS-API-ROLE: datalake_user for an unscoped key, key_<apiKeyId> for a scoped one, so a wrong role fails loudly.
  • Never ship a key to a browser. Put it in a small server-side proxy that adds the headers.
$ curl -X POST ".../auth/acme/graphql" \
  -H "X-API-Key: acme:dlk_..." \
  -d '{"query":"{ orders(first:5){ items{ Id Total } } }"}'
$ curl -X POST ".../auth/acme/api/usp_PlaceOrder" \
  -H "X-API-Key: acme:dlk_..." -d @order.json
201  { "value": [ { "OrderId": 1042 } ] }
$ curl -X POST ".../api/auth/apikeys/exchange" \
  -H "X-API-Key: acme:dlk_..."
{ "token": "eyJhbGci...", "expiresIn": 3600 }
# then send it as a bearer token for ~60 minutes

Writes, transactions and concurrency

Data API writes are per statement, not transactional: a header, its line items and a status row can partially succeed. When you need all-or-nothing, put the work in a stored procedure that wraps BEGIN TRAN / COMMIT / ROLLBACK, expose it, and call it as one request. It returns the created rows and is reachable on POST only.

Tables with Concurrency Protection require every update to echo the row's dl_expected_ts; a stale value is rejected so two writers cannot silently overwrite each other. Direct deletes are blocked in favour of a soft-delete flag. Never send computed, dl_* or rowversion columns in a write body.

Base URLs

SurfaceBase URL
Data API (REST)https://datalake-ms-dab.commercient.com/auth/{tenant}/api/{entity}
GraphQLhttps://datalake-ms-dab.commercient.com/auth/{tenant}/graphql
Auth API (key exchange)https://datalake-ms-auth-api.commercient.com
DDL API (exports, aggregates, events)https://datalake-ms-ddl-api.commercient.com

{tenant} is the short slug (dot-free lowercase), not the portal hostname.