HTTP API & SPARQL protocol
Every SDK is a thin wrapper over one HTTP API. If there is no SDK for your language, or you want the native SPARQL Protocol, call it directly. The first-party TypeScript and Python SDKs are generated from the same API contract, so they expose matching request and response shapes.
Conventions
Section titled “Conventions”- Base URL:
https://0abc1def--production.db.eu.littlebigbrain.com(hosted data plane). See endpoints. - Auth:
Authorization: Bearer <stack-api-key>on every request. - Scope:
?graph=<graph>&branch=<branch>query params (defaultmain). - Idempotency: write endpoints accept an
Idempotency-Keyheader so retries are safe. - Content type:
application/jsonrequest and response bodies, except the RDF and SPARQL routes, which take their own media types. - Errors: structured JSON with stable
status,type,code,message,param,requestId, anddocUrlfields.
Core endpoints
Section titled “Core endpoints”| Method & path | Purpose |
|---|---|
GET /version |
Public build identity for diagnostics |
POST /v1/graph/import/rdf |
Bulk N-Triples, Turtle, N-Quads, or TriG ingest. ?batch= defaults to the 1,000,000-triple cap, so a fully-buffered request commits once; lower it to opt into smaller internal commits. ?build=false defers eager base reconciliation; the server still coalesces a safety reconciliation at the bounded query-tail watermark. 64 MiB per request |
GET /v1/graph/export/rdf |
Export the pinned snapshot as text/turtle (default), ?format=nt, ?format=nquads, or ?format=trig |
POST /v1/graph/commit |
Write statements as typed triplets |
POST /v1/graph/import |
Synchronous bulk NDJSON ingest |
POST /v1/graph/import-jobs |
Submit a durable streamed NDJSON job (requires Idempotency-Key) |
GET /v1/graph/import-jobs?job_id=... |
Poll upload and grouped-commit progress and the terminal result |
DELETE /v1/graph/import-jobs?job_id=... |
Request cooperative cancellation |
POST /v1/graph/retract |
Retract statements; purge_entity_properties: true also clears an entity’s stored scalar properties |
POST /v1/graph/branch |
Fork the scoped branch from another branch ({"from_branch":"main"}) |
POST /v1/graph/branch/merge |
Validate then merge a child branch back onto its fork parent (the scoped branch): one commit, event ids preserved, SHACL-validated first, supersedure_race conflicts reported, optional delete_source. Requires an Idempotency-Key |
DELETE /v1/graph/branch?confirm=... |
Delete one branch; the final live branch is protected |
POST /v1/graph/query |
Structured or SPARQL-text query |
GET /v1/graph/summary |
Graph summary, including exact observed source-type/relation/target-type counts in relation_adjacency |
GET /v1/graph/schema-summary |
Eventual, watermark-bearing observed RDF schema attached to an immutable F3 base; class populations, resource- and literal-valued predicate counts (resource_predicate_counts / literal_predicate_counts), and bounded OWL/RDFS statements for fast Explorer/Ontology startup |
GET /v1/graph/read-snapshot |
Branch-owned RDF query base plus same-epoch head and exact delta lag |
GET /v1/graph/publication-status |
RDF reconciler lifecycle (current, queued, compacting, building, publishing, blocked) plus head, target, base watermark, lag, stage, progress time, and retry guidance |
GET /v1/graph/metadata |
Head and maintenance diagnostics plus a first-publication readiness poll. Before a root exists it returns 200, no snapshot.served_at_seq, and index_caught_up: false; /v1/graph/read-snapshot gives detailed diagnostics once a root exists |
GET /v1/graph/entities/sample |
Bounded class sample from the branch-owned RDF view |
GET /v1/graph/entity/neighborhood |
Bounded one-hop read from the branch-owned RDF view |
GET /v1/graph/changes |
Delta reads since a commit seq (?since=) |
GET /v1/ontology |
Ontology view. entity_type_defs includes each class’s frozen stable_id, canonical query iri, and direct super_types stable ids for hierarchy rendering. Add ?counts=true for exact relation_defs[].edge_count values served from branch-owned F3 predicate statistics |
POST /v1/ontology/define |
Put the graph on an imported ontology. Creates the graph when it does not exist, applies an additive difference when it does (new types and relations, a wider relation domain or range, a new property field), and answers changed: false when the ontology already matches. ?dry_run=true or body dry_run: true previews the exact result without creating a graph or changing its head |
GET /v1/ontology/conformance |
Immutable branch-owned SHACL sidecar keyed by the selected F3 checksum and schema identity. The response exposes validated_at_seq, ontology_version, and shapes_version; ?consistency=strong waits with retryable strong_read_pending until an exact-head sidecar exists. ?limit= bounds result rows (default 200, max 2,000), while result_count and conforms stay exact |
POST /v1/ontology/drafts |
Build a durable, snapshot-pinned ontology proposal from isolated connector samples |
POST /v1/ontology/drafts/validate |
Revalidate a draft’s exact operations at its pinned snapshot |
POST /v1/ontology/drafts/promote |
Atomically publish a validated draft (Idempotency-Key required) |
POST /v1/ontology/drafts/reject |
Persist an auditable rejection without changing the ontology |
GET /v1/schema |
Read the active ontology and shapes bundle plus the enforcement mode |
POST /v1/schema/publish |
Atomically publish ontology and shapes content; conformance is produced off-path as an independent branch-owned sidecar |
POST /v1/graph/export/job |
Durable, bounded full-fidelity NDJSON export |
POST /v1/graph/fork?src=...&dst=...&confirm=... |
Copy a graph to a new graph id in the same stack. Runs as a durable job; dst must not exist. Poll GET /v1/graph/metadata?graph=<dst> |
POST /v1/graph/delete?confirm=... |
Idempotently mark every branch head deleted. Physical object reclamation is asynchronous; a same-name recreation receives a new epoch and cannot be erased by old work |
POST /v1/query/conflicts |
ACL-first snapshot conflict grouping with bounded evidence IDs and explicit truncation |
GET/POST /sparql |
Native SPARQL 1.1 Protocol query endpoint |
POST /update |
Native SPARQL 1.1 Update endpoint |
POST /rdf-graph-store |
Bounded append-only Graph Store mutation |
GET /v1/status |
Snapshot and index status |
GET /v1/usage |
Usage counters |
GET /metrics |
Operator-only Prometheus text |
This is a representative subset of the RDF API surface.
GET /version is unauthenticated diagnostic metadata. Applications target the
single API contract documented here, and a route failure is returned as an error.
Load RDF
Section titled “Load RDF”POST /v1/graph/import/rdf accepts N-Triples, Turtle, N-Quads, and TriG using
the matching content type or a ?format= value. The server keeps source
predicate IRIs, named-graph labels, and literal term metadata on fixed-schema
RDF_TRIPLE edges, so SPARQL projects the original RDF terms back without
creating one ontology relation per predicate. The console’s Data view exposes
N-Triples and Turtle directly, including .ttl file loading.
curl -sS -X POST "https://0abc1def--production.db.eu.littlebigbrain.com/v1/graph/import/rdf?graph=main" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Idempotency-Key: catalog-chunk-1" \ -H "Content-Type: application/n-triples" \ --data-binary @chunk-1.ntFour rules govern a large load:
- One commit per request by default.
?batch=is the number of triples per internal commit and it defaults to the 1,000,000-triple cap, so a fully buffered request is written as a single internal commit. Lower?batch=to opt into smaller commits when you want intermediate progress. - Defer eager reconciliation while streaming. Normal writes advance one
coalesced desired fence and do not require client-side coordination.
?build=falseremains an explicit large-stream control; the server may still compact the exact-query suffix at its safety watermark so later chunks remain writable. - 64 MiB per request. Keep each chunk under the import body cap.
- Split on line boundaries. N-Triples and N-Quads are line-oriented, so a chunk boundary must fall on a line boundary. Turtle and TriG are document grammars, so each request must be a complete parseable document.
Duplicate statements within one request are suppressed across internal batch
boundaries and reported separately as resource-object and literal-object
duplicates. Blank-node labels are preserved by default, so the same label keeps
the same identity when a line-oriented document is split across requests or a
chunk is retried. If separate source documents reuse labels such as _:b0, give
every chunk of each document the same bounded ?blank_node_scope=<document-id>.
The published RDF tier serves the default graph only, and the importer refuses
named-graph statements rather than storing data no query can see. A quad-format
document (N-Quads or TriG) may only use the default graph: each named-graph
statement is a counted, reported error, and ?strict=true fails the whole
request on the first one. The graph_uri parameter is rejected for the same
reason.
Use the default edge_idempotency=skip_unchanged for re-runnable imports, and
edge_idempotency=append for a fresh one-shot load where the Idempotency-Key
protects request retries.
Write typed statements
Section titled “Write typed statements”curl -sS -X POST "https://0abc1def--production.db.eu.littlebigbrain.com/v1/graph/commit?graph=main" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Idempotency-Key: import-2026-06-13" \ -H "Content-Type: application/json" \ -d '{ "triplets": [{ "source": {"type": "SERVICE", "name": "auth-service"}, "relation": "WRITES_TO", "target": {"type": "DATABASE", "name": "user-db"}, "confidence": 0.93, "evidence": "auth-service writes identity records to user-db" }] }'Entity identity & updates. An entity’s identity is (type, key) when you
give it an external key, otherwise (type, name). It is a deterministic hash
of the type and the key or name only, never of the property set. name is
therefore a display label, and re-emitting the same key with only the fields
you changed updates that entity in place: property writes merge by default
(omitted fields are kept; pass property_merge: "replace" on
/v1/graph/commit to make the sent list the whole set). Two mistakes this
avoids. Re-emitting a keyed entity without its key, or with a different name,
resolves to a different entity, and the new fields are written to that second
entity, so always keep the key. And a backfill that holds an entity’s resolved
entity_id (32-char hex) but not its key should address it by entity_id on
the entity_properties record, which bypasses (type, key, name) resolution
entirely. property_merge only controls which fields are kept; it never changes
which entity a record resolves to.
Read consistency
Section titled “Read consistency”Read consistency defaults to eventual. Eventual SPARQL pins the immutable F3
base recorded in the branch head and serves its explicit
snapshot.served_at_seq. It never folds the truth tail or substitutes another
watermark.
Pass ?consistency=strong (or "consistency": "strong" in a JSON body) to
request exact head state. Every accepted write persists a queryable F3 delta;
strong reads apply the bounded exact delta suffix over the immutable base. They
do not wait for background compaction and never return stale success.
For read-after-write, pass the committed sequence a write returned back as the
min_indexed_seq floor on the SPARQL-text route. Strong mode serves that fence
immediately from base plus deltas. A floor beyond the durable head is rejected;
clients never need to poll an index family.
Writes also advance one server-managed reconciliation fence. Poll
/v1/graph/publication-status only when a workflow specifically needs the F3
base compacted, or /v1/graph/read-snapshot to inspect the base and delta lag.
This maintenance watermark is not a read-after-write barrier.
Time travel
Section titled “Time travel”SPARQL accepts an exact transaction-time cursor on both the native protocol and
JSON text endpoints. as_of_commit_seq selects an exact retained checkpoint and
its bounded immutable delta suffix. SPARQL valid-time pins are not reconstructed
in the request path; use the typed temporal state/history APIs when the question
is about world time.
curl -sS "https://0abc1def--production.db.eu.littlebigbrain.com/sparql?graph=main&as_of_commit_seq=42" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Accept: application/sparql-results+json" \ --data-urlencode 'query=SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'A pin past the current head is an invalid_input error. A watermark that is not
an exact retained generation returns non-retryable
historical_generation_unavailable; the server never substitutes a nearby
snapshot or starts a publication polling loop.
Exact multi-hop queries use SPARQL 1.1 property paths through /sparql or
POST /v1/graph/query. Predicate, inverse, sequence, alternative, and closure
paths execute over the published RDF family with the same snapshot and filter
semantics as other queries. Entity inspection remains a bounded one-hop F3
read; there is no standalone generic traversal endpoint.
Publication convergence
Section titled “Publication convergence”Publication convergence is RDF base compaction, not query readiness. GET /v1/graph/publication-status reports the desired target, base lag, active
reconciler stage, last progress time, and retry interval. current means the
head-owned F3 base has absorbed every delta and matches the head’s ontology.
GET /v1/graph/read-snapshot returns that base identity, the same-epoch head,
and exact delta lag. Strong reads remain exact while the state is queued,
compacting, building, or publishing; eventual reads deliberately remain pinned
to the older base. There is no CURRENT pointer or independently served family
watermark.
Bulk NDJSON ingest
Section titled “Bulk NDJSON ingest”For large entity and edge loads use POST /v1/graph/import-jobs with
newline-delimited JSON. The serving node streams the request to immutable object
storage and returns a job ID; ingest workers checkpoint bounded grouped commits
and enqueue one final publication. Submit with an explicit Idempotency-Key,
poll with GET, and cancel with DELETE. Progress identifies whether the worker is
reading the payload, committing a bounded group, persisting its checkpoint, or
enqueueing publication; while a group is active it also reports that group’s
index, record count, and bytes. A successful job reports its final committed
sequence, while publication remains asynchronous and visible through
/v1/graph/metadata. Poll until index_caught_up is true and
snapshot.served_at_seq covers that sequence; wait once after the final batch.
Exact-head duplicate, cardinality, and scoped-SHACL preflight reuses the
immutable F3 base plus only the later commit suffix instead of reconstructing graph
history. A terminal failure reports its safe code, retryability, last stage,
attempt count, and opaque diagnostic ID; exhausted infrastructure retries are
never mislabeled as deterministic input errors. POST /v1/graph/import runs the
same commit loop synchronously inside the request.
Whole-graph entity, filter, edge, and observation collection reads are intentionally absent. Use bounded entity samples plus point reads or published SPARQL.
See Load RDF data for the end-to-end walkthrough.
Back-pressure
Section titled “Back-pressure”There are two back-pressure classes, and both are retryable with the same
idempotent request. The bodies never carry engine internals, so retry on the HTTP
status plus Retry-After rather than on the code string:
429 server_busy+Retry-Aftermeans the serving node is briefly busy: hard memory admission (expensive writes deferred before allocation), the 128 MiB aggregate buffered-body budget occupied, or this graph’s write home too busy to take a proxied write. An oversized cold SPARQL preparation also uses this response when its estimated snapshot or projection cannot fit the node’s complete transient budget; warm prepared and object-tier queries are unaffected. The request is safe to retry unchanged. It succeeds once the node drains or the immutable prepared tier exists, and a busy write home never executed a mutation locally.429 ingest_busy+Retry-Aftermeans the graph’s ingest pipeline is catching up and no node can accept the write until it drains: log backpressure (the tail hit its hard cap, self-healing), commit contention (many writers racing the same graph’s single compare-and-swap head), or a distinct full snapshot build holding the heavy-build slot. It is a429like every other pressure class, with a distinct code for graph-scoped ingest backpressure, and it is safe to retry unchanged. Prefer one streamed import over many concurrent single-record commits. Reads are never blocked by writes.
A successful write may also carry an X-LBB-Throttle-Ms header (and
throttle_ms / drain_pressure body fields) when the ack absorbed meaningful
queueing. Reduce write concurrency as these rise, to avoid server_busy and
ingest_busy.
Define your own ontology
Section titled “Define your own ontology”POST /v1/ontology/define imports an ontology document and puts the scoped
graph on it, so commits are validated against your entity types and relations
instead of the built-in vocabulary. The body carries source (the document
text), format (auto by default, which sniffs the content, and also accepts
spec, lbb_json, json_ld, turtle, rdf_xml, csv, and tsv), and
merge_default, which layers your types on top of the built-in vocabulary so
both stay available.
curl -X POST "https://<stack-host>/v1/ontology/define?graph=support" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "source": "{\"entity_types\":[{\"name\":\"Customer\"},{\"name\":\"Ticket\"}],\"relation_types\":[{\"name\":\"OPENED\",\"source\":\"Customer\",\"target\":\"Ticket\"}]}", "format": "spec", "merge_default": false }'The call is safe to repeat, so the script that creates a graph and defines its
schema can run on every deploy. On a graph that already exists the imported
document is compared against the active ontology, and the response says what
happened: graph_created is true only when this call created the graph head,
and changed is true only when a new ontology version was written. changed
reports what was written, so a call that applied something never comes back
changed: false.
| Difference from the active ontology | Result |
|---|---|
| none | 200, changed: false. Nothing is written and ontology_version stands still |
additive: new entity types, new direct rdfs:subClassOf parent links, new relations, renames that keep an existing type’s identity, a wider relation domain or range, a new property field |
200, changed: true, with the applied entries in changes. Stored records keep resolving, because every existing type and relation keeps its identity |
| a relation’s declared domain or range is narrower | 400, code ontology_restrictive_change. The message lists the narrowed relations. Confirm a narrowing with POST /v1/schema/publish and confirm_restrictive: true, or apply it one operation at a time with POST /v1/ontology/evolve and narrow_relation, which reports the records it would affect before publishing |
| an entity type or relation the graph still defines is missing | 400, code ontology_identity_breaking_change. The message lists the dropped subjects. Stored records resolve through those definitions, so define keeps them. Publish an ontology that keeps them (merge_default: true keeps the built-in vocabulary underneath your own), use a different graph id, or delete the graph with POST /v1/graph/delete?confirm=<graph_id> and define it again |
| a stated change no additive operation expresses: a property field the graph declares given a new type or a new allowed-value set, a vector field, or a declared relation domain or range opened up to any type | 400, code ontology_unsupported_change. The message lists the subjects. Apply them with POST /v1/ontology/evolve, which names each change as its own operation |
Every refusal writes nothing, so a rejected call leaves the active ontology as it
was. To grow a live schema one change at a time, with a dry run and a report of
affected records, use POST /v1/ontology/evolve.
The ordered evolution form names a hierarchy change as add_super_types, with
entity_type and a list of direct parent names in super_types. Declare any
new classes earlier in the same request with add_entity_type. Repeating links
is a no-op; removing a parent is not an additive change.
Two things are read as absence rather than as intent, and are kept. A property
field the graph holds and your document omits stays, because no operation
removes a field. Fields enter a document through the native lbb_json format
or through RDF data properties: an owl:DatatypeProperty (or an untyped
property whose every rdfs:range is a datatype) imports as a property field
with the matching value type, never as a relation. A required flag
that disagrees on a field the graph already declares is left alone, because it
is advisory and nothing in the engine branches on it.
Ontology drafts
Section titled “Ontology drafts”For a new connector whose records are not yet in the graph, create an ontology
draft from 1 to 100 supplied samples. POST /v1/ontology/drafts never ingests
those samples; it persists stable evidence references, proposed additive
operations, competency-question analyses and query outlines, coverage, pitfalls,
and confidence. Validate the result at its pinned commit and ontology version
with /validate, then promote it with an Idempotency-Key or reject it with a
reason. A stale draft fails validation rather than silently rebasing.
Portable graph export
Section titled “Portable graph export”Use POST /v1/graph/export/job for a durable, bounded full-fidelity NDJSON
export and poll GET /v1/graph/export/job?job_id=… for completion. Synchronous
whole-graph export is intentionally absent.
For an RDF document of the current snapshot, GET /v1/graph/export/rdf returns
text/turtle by default, with ?format=nt, ?format=nquads, or ?format=trig.
Every format exports the default graph, which is the only graph the engine
serves. This endpoint errors above 100,000 triples and ?max_triples= may only
lower that ceiling, so use the NDJSON export job for a whole-graph dump.
Native SPARQL 1.1 Protocol
Section titled “Native SPARQL 1.1 Protocol”Your stack serves the native SPARQL 1.1 Protocol at /sparql, so standard
SPARQL clients (YASGUI, Protégé, RDFLib’s SPARQLWrapper) connect directly:
GET /sparql?query=<encoded>POST /sparqlwith a form body, or anapplication/sparql-queryraw bodyAccept-negotiated SELECT/ASK results: SPARQL Results JSON/XML, or CSV/TSVAccept-negotiated CONSTRUCT/DESCRIBE graphs: Turtle or N-Triples
curl -sS "https://<stack-host>/sparql" \ -H "Authorization: Bearer $LBB_API_KEY" \ --data-urlencode 'query=SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 5' \ -H "Accept: application/sparql-results+json"Entity types, relations, and properties created by little big brain are addressed
as <https://littlebigbrain.com/{r,class,p}/name> with lowercase local
names. IRIs you loaded through import/rdf keep their original form. For
in-process use, the TypeScript and
Python SDKs return parsed rows so you never zip
head.vars with bindings by hand.
/sparql accepts the ?consistency=strong|eventual, ?min_indexed_seq=<seq>,
?as_of_commit_seq=<seq>, and ?as_of_valid_time=<timestamp> extension params.
It uses the same eventual default as the JSON APIs; request strong
explicitly for head-exact results. An unknown ?consistency= is a typed 400.
The default is asserted-only (entailment=none, non-reasoned).
?entailment=subclass|rdfs|owl applies that regime at query time from the
pinned generation’s schema triples (see
reasoning); an unknown value is a typed 400, and
?reason=true returns a typed error because stored rules already run at
publish time. A strong read pins the branch head and evaluates its immutable F3
base plus exact delta suffix.
CONSTRUCT and DESCRIBE are materialized with a hard 100,000-triple ceiling. Pass
max_graph_triples to lower it. Exceeding the ceiling returns a 400 error, and
the RDF graph is never silently truncated.
SPARQL Update and graph documents
Section titled “SPARQL Update and graph documents”Send a raw application/sparql-update body to /update (or /v1/update). A
pure INSERT DATA request becomes one append-only log batch and one head
compare-and-swap without projecting the existing graph. Other Update forms fail
explicitly until they have bounded native plans over immutable published runs;
the server never falls back to whole-graph preparation. Idempotency-Key makes
retries safe and malformed keys are rejected. /sparql accepts the same update
binding and 64 MiB body limit, but rejects a form containing both query= and
update=.
curl -sS "https://<stack-host>/update" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Content-Type: application/sparql-update" \ -H "Idempotency-Key: catalog-v1" \ --data-binary 'INSERT DATA { <https://example.com/a> <https://example.com/name> "A" }'The bounded append-only Graph Store mutation is POST /rdf-graph-store with
?default, sending Turtle or N-Triples. A named target (?graph=<IRI>) is
refused because the engine serves the default graph only. Graph Store reads,
replacement, and deletion are not exposed; use SPARQL for reads and the native
graph mutation APIs for retractions. Use graph_id and branch to select a
non-default little big brain database graph or branch.
curl -X POST "https://<stack-host>/rdf-graph-store?default" \ -H "Authorization: Bearer $LBB_API_KEY" \ -H "Content-Type: text/turtle" \ --data-binary '@prefix ex: <https://example.com/> . ex:a ex:name "A" .'