Skip to content

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.

  • 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 (default main).
  • Idempotency: write endpoints accept an Idempotency-Key header so retries are safe.
  • Content type: application/json request 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, and docUrl fields.
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.

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.

Terminal window
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.nt

Four 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=false remains 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.

Terminal window
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 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.

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.

Terminal window
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 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.

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.

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-After means 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-After means 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 a 429 like 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.

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.

Terminal window
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.

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.

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.

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 /sparql with a form body, or an application/sparql-query raw body
  • Accept-negotiated SELECT/ASK results: SPARQL Results JSON/XML, or CSV/TSV
  • Accept-negotiated CONSTRUCT/DESCRIBE graphs: Turtle or N-Triples
Terminal window
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.

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=.

Terminal window
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.

Terminal window
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" .'