REST API Reference
The SemiLayer REST API is available at https://api.semilayer.com. All endpoints
accept and return JSON. Authentication is via API key in the Authorization header.
Authentication
Use sk_ keys for server-side calls. Use pk_ keys for client-side calls — they
enforce Lens access rules.
For per-user access control, also pass the user JWT:
Base URL
Search
Search the vector index for a given query. Does not hit your source database.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
query | string | ✓ | Search query |
limit | number | Max results. Default: 10 | |
minScore | number | Minimum similarity score (0-1). Default: 0 | |
mode | string | semantic | keyword | hybrid. Default: lens config then semantic |
Response
Example
Similar
Find records similar to a given record using its stored vector.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | Source record primary key (as string) |
limit | number | Max results. Default: 10 | |
minScore | number | Minimum similarity score. Default: 0 |
Response
Same shape as Search: { results: SearchResult[], meta: {...} }
Example
Query
Read directly from your source database through the Bridge.
Must be explicitly enabled in the Lens config with rules.query.
Returns 403 Forbidden if not enabled.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
where | object | Equality filters. Keys are output field names | |
orderBy | object | array | { field, dir? } or array of same | |
limit | number | Default: 100 | |
offset | number | Row offset | |
cursor | string | Opaque pagination cursor from previous response | |
select | string[] | Field names to include in response |
Response
Example
Paginate with cursor:
Ingest Webhook
Trigger an ingest job from your application. Use an ik_ key.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
mode | string | ✓ | incremental | full | records |
Mode incremental — time-windowed sync using changeTrackingColumn:
Mode full — re-index all records from scratch:
Mode records — sync specific records:
Response
Example
Count
Count rows on a lens matching a where predicate. Sibling primitive of
query — same RBAC, same access rules, same operator vocabulary, but
returns a scalar instead of paying for the rows themselves. Bridges with
native count push it down (SELECT count(*)); streaming-only bridges run
the same reduce in the bridge process.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
where | WhereClause | Same operator grammar as query. Supports $eq, $in, $gt(e), $lt(e), $ne, $ilike, $like, $exists, $or, $and, $not. |
Response
meta.strategy is "native" when the bridge supports count(*) directly,
or "streaming" when the count was reduced from a row scan. Some bridges
return an approximate count; in that case meta.approximate is true.
Example
grants.query gates count for pk_ keys (count is a query-class read).
sk_ keys bypass.
Analyze
The analyze facet exposes five HTTP routes per lens. Each named
analysis is reachable as analyze/:lens/:name.
List analyses
Returns metadata for every named analysis declared on the lens.
Run an analysis
Request body
| Field | Type | Description |
|---|---|---|
where | WhereClause | Per-call narrowing on top of the spec's candidates.where. |
cursor | string | Page cursor when the bucket count exceeded limit. |
cache | 'no-cache' | 'force' | Override cache directive. |
Body cap is 8 KB. Larger bodies return 400 analyze_input_too_large.
Response
Every successful response sets:
X-SemiLayer-Cache: hit | miss | bypass— cache layer statusX-SemiLayer-Candidates-Clamped: <N>— tier-resolved candidate ceiling
Drill-down rows
The full body / response shape lives in the drill-down docs. Quick reference:
| Field | Type | Required | Description |
|---|---|---|---|
bucketKey | string | ✓ | Signed token from a bucket on the parent analyze. 24h TTL. |
cursor | string | Pagination cursor returned by the previous response. | |
limit | number | Default 100, max 1000. | |
search | string | Free-text query inside the bucket. Empty = no filter. | |
searchMode | 'auto' | 'simple' | 'semantic' | 'hybrid' | Routing override. Default 'auto'. | |
orderBy | RowOrderBy | One or many { field, dir? }. PK is appended as a stable tiebreaker. |
Response shape: { rows, cursor?, total?, crossSource?, meta?: { searchMode, orderBy } }.
Streaming export — drill bucket
Same body as /rows minus cursor (server walks it internally), plus
format: 'ndjson' | 'csv' (default 'ndjson'). Response is
Content-Type: application/x-ndjson or text/csv; charset=utf-8,
chunked, with Content-Disposition: attachment; filename="<lens>.<analysis>.export.<format>".
Tier-aware row caps (Free 10k / Pro 100k / Team 1M / Scale 10M / Enterprise unlimited). When the cap halts the stream, the response sets HTTP trailers:
See Exports for the full streaming contract.
Streaming export — query rows
Same streaming contract as the drill export, with the lens-wide query body shape:
| Field | Type | Description |
|---|---|---|
where | WhereClause | Predicate. Same grammar as query. |
orderBy | RowOrderBy | Sort. |
select | string[] | Field projection — also seeds the CSV column order. |
format | 'ndjson' | 'csv' | Default 'ndjson'. |
Tier cap column is query_export_rows_max (independent from the analyze
export cap, same ladder by default).
Explain (sk-only)
Same body as the run route. Service keys only — pk_ returns
403 forbidden. Response carries the full plan, bridges-involved, sketch
choices, expected accuracy, expected cost, plus the cache reason and the
limits envelope (resolved tier ceilings).
Index advisor (sk-only)
Returns DDL recommendations the customer can paste into the source DB to
make this analyze faster. Pure analysis — no side effects. Dialects: pg,
mysql, sqlite, clickhouse, mongo. The CLI surface is semilayer analyze plan.
Errors
| Status | Code | When |
|---|---|---|
400 | analyze_input_too_large | Body > 8 KB. |
400 | bucket_key_required | Drill / export body missing bucketKey. |
400 | bucket_key_invalid | Signature failed or token expired (24h). |
400 | unknown_field | orderBy.field isn't on the lens. |
400 | unsupported_search_mode | searchMode: 'simple' against a lens with no text fields. |
403 | forbidden | grants.analyze.<name> denied, or explain/plan called with a pk_ key. |
404 | lens_not_found / analysis_not_found | Resource doesn't exist on this env. |
413 | scan_budget_exceeded | Streaming/hybrid scan exceeded analyzeCandidatesMax. |
413 | parent_set_too_large | Cross-source parent set exceeded analyzeParentSetMax. |
413 | exact_reducer_budget_exceeded | Exact percentile / count_distinct buffered more than analyzeExactValuesMax. |
502 | (bridge-classified) | Bridge error. Body carries code / message / detail. |
Feed
The feed facet exposes three HTTP routes per lens. Each named feed
on the lens is reachable as feed/:lens/:name.
List feeds
Returns metadata for every named feed declared on the lens.
Fetch a page
Request body
| Field | Type | Description |
|---|---|---|
context | Record<string, unknown> | Free-form context (cap 8KB). Lens config declares which paths to read. |
cursor | string | Opaque cursor from the previous page. Omit for first page. |
pageSize | number | Override the configured pageSize (max 100). |
Response
cursor: null signals there are no more pages. evolved: true means the supplied
context's hash differs from the cursor's previous hash — UI cue that ranking
shifted.
Errors
| Status | Code | When |
|---|---|---|
400 | feed_context_too_large | Context payload > 8 KB. Response includes actualBytes, maxBytes, hint. See Signals for the three patterns. |
400 | invalid_cursor | Cursor signature failed or expired (>1h). |
400 | seed_record_id_missing | recordVector mode but context lacks the configured path. |
400 | seed_record_not_found | recordVector seed id not in the lens. |
400 | context_embed_failed | Embedding provider returned an error embedding the context text. |
403 | forbidden | Access rule (rules.feed.<name>) denied. |
404 | not_found | Lens not found. |
404 | feed_not_found | Feed name not declared on this lens. |
409 | lens_error_state | Lens is in error state and not queryable. |
502 | seed_lookup_failed / candidate_fetch_failed | Bridge / vector store error. |
Per-record explain (sk-only)
Service keys only — pk_ keys get 403 forbidden.
Request body
| Field | Type | Description |
|---|---|---|
recordId | string | Source row id of the record to explain (required). |
context | Record<string, unknown> | Same context shape as fetch — same scoring. |
Response
See Explain for use cases.
Error Responses
All errors return a JSON body with an error field:
| Status | When |
|---|---|
400 | Invalid request body |
401 | Missing or invalid API key |
403 | Key does not have permission for this operation |
404 | Lens not found |
429 | Rate limit exceeded (SaaS only) — Retry-After header included. SMART_SYNC_QUOTA_EXHAUSTED body for manual /sync routes when the monthly smart_sync_jobs cap is reached (manual button + CLI + scheduled runs share the meter). |
502 | Embedding provider error |
Streaming (WebSocket)
See HTTP & WebSocket for the full WebSocket protocol reference, including connection setup, op frames, event frames, heartbeat handling, and close codes.