Layer 4 — mechanical reference for every REST endpoint Sandbar exposes. Organized by resource group. For practical client patterns see
doc/guides/writing-a-rest-client.md.
http://<host>:<port>/api
Default port is :8080; configurable via :http-port in config/config.edn.
Accept header | Response shape |
|---|---|
application/edn (default) | Clojure EDN — keywords, sets, custom types |
application/json | Standard JSON |
application/transit+json | Transit-encoded JSON |
Request bodies (for POST / PATCH) use the same negotiation via Content-Type.
Clojure keywords map directly to URL paths via slash. Special characters are percent-encoded:
| Keyword | URL fragment |
|---|---|
:dt/Resource | dt/Resource |
:model/User | model/User |
:db.type/string | db.type/string |
:user/active? | user/active%3F |
Authorization: Bearer <service-account-token>
Read endpoints are typically open; write and sensitive-read endpoints require a token. See auth.md for token issuance.
| Status | Meaning |
|---|---|
200 OK | Successful read or update |
201 Created | Successful resource creation |
204 No Content | Successful delete or transition |
400 Bad Request | Malformed input |
401 Unauthorized | Missing or invalid bearer token |
403 Forbidden | Authenticated but not permitted |
404 Not Found | Target class / property / entity doesn't exist |
409 Conflict | Constraint violation (uniqueness, etc.) |
422 Unprocessable | Validation failed; response body has structured :errors |
500 Internal Error | Server-side error |
GET /api/statusReturns system health and version.
{:time #inst "2026-05-13T16:30:00.000Z"
:clojure {:major 1 :minor 12 :incremental 4}}
GET /api/store/schemaReturns an overview of the entire schema.
{:schema {:class-count 42
:property-count 58
:classes [:db.type/bigdec ... :model/User ...]
:properties [:db/cardinality ... :user/login ...]}}
GET /api/store/classesLists every class ident.
{:count 42
:classes [:dt/Class :dt/Property :dt/Resource ...]}
GET /api/store/classes/:ns/:nameFull class description.
{:class :model/User
:description {:db/doc "..." :db/ident :model/User ...}
:abstract? false
:context "model"
:label "User"
:parents [:dt/Ref]
:ancestors [:dt/Ref :dt/Resource]
:slots [...]
:direct-slots [...]
:required-slots [...]
:direct-subclasses []
:all-subclasses []}
GET /api/store/classes/:ns/:name/instancesAll instances (including subclass instances).
GET /api/store/classes/:ns/:name/instances/directDirect instances only — no subclass instances.
GET /api/store/classes/:ns/:name/slotsAll effective slots (inherited + direct).
GET /api/store/classes/:ns/:name/slots/directDirect slots only.
GET /api/store/classes/:ns/:name/slots/requiredRequired slots only.
GET /api/store/classes/:ns/:name/hierarchyFull class hierarchy — parents + ancestors + direct subclasses + transitive subclasses.
GET /api/store/classes/:ns/:name/subclassesAll transitive subclasses.
GET /api/store/classes/:ns/:name/subclasses/directDirect subclasses only.
GET /api/store/classes/:ns/:name/ancestorsAll ancestor classes.
GET /api/store/classes/:ns/:name/parentsDirect parents only.
GET /api/store/classes/:ns/:name/validateRuns validation against every instance.
{:class :model/User
:total 1247
:valid 1240
:invalid 7
:errors [{:entity 12345 :errors [...]} ...]}
For large classes, prefer the workflow-backed MCP verb sandbar.validation.start — it's cancellable and produces a queryable history.
POST /api/store/classes/:ns/:name/instancesCreate a new instance of the named class.
Request body:
{
"event.booking/title": "Weekly Sync",
"event.booking/starts-at": "2026-05-14T15:00:00Z",
"event.booking/owner": "model/user.alice"
}
Successful response (201 Created):
{:entity {:db/id 12345
:dt/type :event/Booking
...}}
Validation failure (422):
{:errors [{:slot :event.booking/owner :error :missing-required}
...]}
GET /api/store/propertiesLists every property ident.
GET /api/store/properties/:ns/:nameFull property description.
{:property :user/login
:description {:db/ident :user/login
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:dt/type :dt/Property
:dt/domain :model/User
:dt/range :db.type/string
:db/doc "User login identifier"}
:domain :model/User
:range :db.type/string
:cardinality :db.cardinality/one
:cardinality-one? true
:cardinality-many? false}
GET /api/store/properties/:ns/:name/domainReturns just the property's domain class.
GET /api/store/properties/:ns/:name/rangeReturns just the property's range type.
GET /api/store/entities/:ns/:nameLook up an entity by namespaced :db/ident.
{:entity {:db/id 12345
:db/ident :model/user.alice
:dt/type :model/User
:user/login "alice"
...}}
GET /api/store/entities/:ns/:name/validateValidates a single entity. Returns nil (200) if valid, or the error map (422).
PATCH /api/store/entities/:ns/:namePartial update — only the provided slots are written; others remain untouched.
Request body:
{"event.booking/location": "Conference Room A"}
Validation runs after the merge; returns 200 with the updated entity, or 422 with errors.
GET /api/store/types/instance-of/:class-ns/:class-name/:entity-ns/:entity-nameTests whether the named entity is an instance of the named class.
{:class :model/User
:entity :model/user.alice
:instance-of? true}
GET /api/store/types/subclass-of/:parent-ns/:parent-name/:child-ns/:child-nameTests whether :child-ns/:child-name is a subclass of :parent-ns/:parent-name.
{:parent :dt/Resource
:child :model/User
:subclass-of? true}
The four-axis retrieval surface's aggregation axis. See aggregation.md for theory.
GET /api/aggregate/countQuery params:
class (required) — class ident (e.g., :mm/Memory)where (optional) — EDN-string Datalog clausesReturns {:count <int>}.
GET /api/aggregate/count?class=:mm/Memory&where=%5B%5B%3Fe%20%3Amm.memory%2Fmemory-type%20%3Adecision%5D%5D
GET /api/aggregate/group-byQuery params:
class (required) — class identgroup-by (required) — slot ident to group bywhere (optional) — EDN-string Datalog clausesReturns {:groups {value count} :total <int>}.
GET /api/aggregate/rank-byQuery params:
class (required) — class identrank-by (required) — :degree / :backlink-density / :recency / :freshnesslimit (optional, default 20) — max returned hitstemporal-slot (required for :recency / :freshness) — slot ident carrying the temporal valueReturns {:hits [{:entity ... :rank-score ...}] :total <int> :returned <int>}.
The four-axis retrieval surface's navigation axis — path-grammar walker. See navigation.md for the surface and path-grammar.md for the algebra.
GET /api/navigate/pathQuery params:
from (required) — seed entity identvia (required) — EDN-string path expressionlimit (optional, default 0 = no cap) — max returned entitiesinclude (optional) — comma-separated projection opts; supports paths (deferred surfacing)Returns {:reachable [<entity-map>...] :total <int> :returned <int>}.
Important — URL-encoding + in path expressions: + in a query string decodes as space (per application/x-www-form-urlencoded). Use %2B for the literal + in :REP+.
Example:
GET /api/navigate/path?from=:dt/Property&via=%5B:REP%2B%20:dt/subclass-of%5D&limit=50
decodes to via=[:REP+ :dt/subclass-of].
The MCP transport is served at /mcp (not under /api/store/*). See doc/api/mcp-verbs.md for the full MCP verb catalog.
| Method | Endpoint | Description |
|---|---|---|
POST | /mcp | JSON-RPC envelope; reads result |
POST (with Accept: text/event-stream) | /mcp | Subscribe for SSE notifications |
For endpoints returning collections (/instances, /properties), pagination is via standard query parameters:
| Param | Meaning |
|---|---|
?limit=N | Return at most N items |
?offset=N | Skip the first N items |
?after=<ident> | Cursor-style pagination (preferred over offset) |
Without parameters, endpoints return all items. For very large classes, always paginate.
Some endpoints accept filter parameters. When present, the response shape is the same but filtered:
| Param | Endpoint | Meaning |
|---|---|---|
?since=<inst> | /api/store/classes/:ns/:name/instances | Only modifications since timestamp |
?type=<keyword> | various | Filter by specific subclass |
All error responses (4xx and 5xx) carry a structured body:
{:error {:code "validation-failed" ; stable string code
:message "Required slot missing"
:data {:slot :user/login :error :missing-required}}}
The :code is stable across releases and suitable for branching client logic. The :message is human-readable and may change.
The REST surface is versioned implicitly — there is no /v1/ prefix today. Breaking changes will introduce a /v2/ namespace alongside /api/. Until 1.0.0, treat the surface as evolving; pin to a release tag if stability matters.
doc/guides/writing-a-rest-client.md — practical patternsdoc/api/mcp-verbs.md — the MCP alternativedoc/api/dt-star.md — the in-process APIauth.md — bearer-token issuanceCan you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |