Skip to content

REST API Reference

Viglet Turing exposes a REST API for integrating search and AI capabilities into any application. All endpoints use JSON. Public endpoints (search, chat, autocomplete) require no authentication. Administrative endpoints require a Key header.

For authentication details, see Authentication. For the interactive API explorer, visit http://localhost:2700/swagger-ui.html.


Protected endpoints require an API Token in the Key request header:

Key: <YOUR_API_TOKEN>

Create tokens in Administration → API Tokens. See Authentication for step-by-step instructions.

A POST, PUT or DELETE that carries a valid Key needs no CSRF token: the key is what authenticates it, so a server-to-server call has no XSRF-TOKEN cookie to send back. Requests authenticated by a browser session still need one.

Example, authenticated API call:

Terminal window
curl "http://localhost:2700/api/sn/Sample/search?q=cloud&_setlocale=en_US" \
-H "Key: <YOUR_API_TOKEN>" \
-H "Accept: application/json"

Several clients can share one SN site, each writing its own documents with a field that tells them apart, for example site_key. Bind each client’s token to its partition: in Administration → API Tokens, set Partition of a shared site to the field and the value (site_key and a.example.org), or send partitionField and partitionValue to /api/dev/token.

The server then confines that key, whatever the request asks for:

EndpointWith a partitioned key
Search: GET/POST /api/sn/{site}/search, /search/query, /search/list, /search/list/queryOnly the partition’s documents are returned, and facets count only them. The similar-documents widget and duplicate clusters are left out, and the search cache is not used.
/search/locales, /search/sort-options, POST /clickAs for any key
GET /api/sn/{site}/search/similarOnly the partition’s documents are returned, for a seed document in the partition (another seed answers []). The lexical (MLT) path answers, even on a site whose vector mode would otherwise answer.
GET /api/sn/{site}/search/documentOnly the partition’s documents are returned, and 404 when none of the ids is in it
GET /api/sn/{site}/ac, POST /ac/querySuggestions are the titles of the partition’s documents that match the prefix, instead of the engine’s site-wide term suggester
GET /api/sn/{site}/{locale}/spell-checkNo correction: the dictionary is built from the whole site
GET /api/sn/{site}/{locale}/related-termsAs for any key: the thesaurus is site configuration
GET /api/sn/{siteId}/metrics/failed-searches, /content-gaps, /clicksRead from the partition’s searches and clicks only. The search and click logs record the partition from this release on, so earlier searches are not counted
POST /api/sn/import, /import/bulkEach document gets the partition value stamped on. A CREATE or DELETE whose id already belongs to another partition, or to a document stored without one, is refused with 403, and so is a document that carries a different value. A delete by field gets the partition added, so it stays inside the partition; a delete by type is refused.
Every other endpoint, including chat, answer, duplicates, the other metrics, the zip import and MCPRefused with 403 (401 on MCP) until it enforces partitions

A key without a partition, and a browser session, are not affected. The partition field must be a stored field, because the import reads it back from the stored document before it replaces or deletes one.

Public Endpoints (no authentication required)

Section titled “Public Endpoints (no authentication required)”

Certain endpoints are publicly accessible, allowing client applications to perform searches and chat without managing sessions:

EndpointPurpose
GET /api/sn/*/searchSemantic Navigation search
POST /api/sn/*/search/querySemantic Navigation search, JSON body (POST-native search)
POST /api/sn/*/search/list/querySelect-list projection, JSON body
GET /api/sn/*/chatGenAI chat on an SN Site
GET /api/sn/*/acAutocomplete
POST /api/sn/*/ac/queryAutocomplete, JSON body
POST /api/sn/*/nl-filtersA visitor’s description as search filters (Natural-language filters)
POST /api/genai/chatDirect GenAI chat
POST /api/ocr/**OCR text extraction
POST /api/v2/integration/**External integration endpoints
GET /api/v2/guest/**Guest access endpoints
POST /graphqlGraphQL queries
GET /api/loginLogin endpoint

All other endpoints require authentication, including the full administration API, user management, site configuration, and AI Agent management.


Explore and test every endpoint interactively:

InterfaceURL
Swagger UIhttp://localhost:2700/swagger-ui.html
OpenAPI 3.0 spechttp://localhost:2700/v3/api-docs

The core search endpoint. Returns results, facets, spotlights, spell-check suggestions, and pagination.

GET http://localhost:2700/api/sn/{siteName}/search
POST http://localhost:2700/api/sn/{siteName}/search

Query Parameters (GET):

ParameterRequiredDescription
q✅Search query
_setlocale✅Locale code (e.g., en_US, pt_BR)
pPage number (default: 1)
rowsResults per page (overrides site default)
sortSort field and direction (e.g., date desc)
fq[]Filter query: apply a facet filter (e.g., fq[]=type:news)
groupGroup results by a field value

Example (GET):

Terminal window
curl "http://localhost:2700/api/sn/Sample/search?q=enterprise+search&p=1&_setlocale=en_US&rows=10" \
-H "Accept: application/json"

The search response is a self-describing navigational JSON: every link a user might follow (apply a filter, remove a filter, paginate, filter by metadata) is pre-built and ready to use. The front-end does not need to construct query strings or manage filter state.

{
"pagination": [
{ "type": "CURRENT", "text": "1", "href": "...", "page": 1 },
{ "type": "NEXT", "text": "Next", "href": "...", "page": 2 }
],
"queryContext": {
"count": 42,
"index": "Sample",
"limit": 10,
"offset": 0,
"page": 1,
"pageCount": 5,
"pageEnd": 10,
"pageStart": 1,
"responseTime": 23,
"query": { ... },
"defaultFields": { ... },
"facetType": "AND",
"facetItemType": "AND"
},
"results": {
"document": [
{
"source": "TURING",
"elevate": false,
"fields": { "title": "...", "url": "...", ... },
"metadata": [
{ "name": "type", "values": [ { "label": "article", "link": "..." } ] }
]
}
]
},
"widget": {
"facet": [ ... ],
"secondaryFacet": [ ... ],
"facetToRemove": { ... },
"similar": [ ... ],
"spellCheck": { ... },
"spotlights": [ ... ],
"locales": [ ... ],
"cleanUpFacets": "...",
"selectedFilterQueries": [ ... ]
},
"groups": [ ... ]
}

Top-level sections:

SectionDescription
paginationPre-built page links (FIRST, PREVIOUS, CURRENT, NEXT, LAST), the front-end follows them directly
queryContextResult count, page info, response time, active facet operators, and default field mappings
results.documentArray of matched documents. Each contains fields (all indexed values), metadata (facet values with pre-built filter links), source, and elevate flag
widget.facetPrimary facet groups with counts and pre-built filter/clear links per value. Enabled by Facet = true
widget.secondaryFacetSeparate facet groups for fields promoted to Secondary Facet, independent from facet, for different UI treatment (e.g., tabs)
widget.similarMore Like This results. Enabled by MLT = true
widget.spellCheckSpelling correction with original, corrected text, and usingCorrectedText flag. Enabled by Spell Check = true
widget.spotlightsCurated documents injected at configured positions when the query matches a spotlight term
widget.localesAll configured locales with pre-built links: use as valid values for _setlocale
widget.cleanUpFacetsPre-built link to clear all active facet filters
groupsGrouped results when the group parameter is used

Self-describing navigation operates at three levels:

  1. Facet navigation: Each facet value carries pre-built links for applying, removing, or clearing filters
  2. Document metadata navigation: Each result’s metadata values carry filter links (e.g., click a type:article tag to filter all articles)
  3. Pagination: Next/previous/specific page links are included; no offset calculation needed

A Turing search UI can be built as a pure rendering layer: render results, facets, tags, pagination, and locale switchers directly from the response. Adding a new facet to the SN Site configuration propagates automatically, with no client code changes.

Locale selection:

GET /api/sn/{siteName}/search?q=annual+report&_setlocale=pt_BR

The POST endpoint accepts the same query parameters plus additional fields in the JSON body:

FieldTypeDescription
userIdstringUser identifier (for metrics and latest searches)
querystringSearch query (alternative to q query param)
localestringLocale code (alternative to _setlocale query param)
pageintegerPage number
rowsintegerResults per page
sortstringSort field and direction
groupstringGroup results by field
fqstring[]Filter queries
fqAndstring[]AND filter queries
fqOrstring[]OR filter queries
fqOperatorstringFilter query operator between facets
fqItemOperatorstringFilter query operator within facet values
fieldListstring[]Restrict which fields are returned
disableAutoCompletebooleanDisable autocomplete suggestions (default: false)
populateMetricsbooleanEnable search metrics recording (default: true)
targetingRulesstring[]Targeting rules: see Targeting Rules
targetingRulesWithConditionmapTargeting rules with conditions
targetingRulesWithConditionANDmapTargeting rules with AND conditions
targetingRulesWithConditionORmapTargeting rules with OR conditions

Example (POST with targeting rules):

Terminal window
curl -X POST "http://localhost:2700/api/sn/Sample/search" \
-H "Content-Type: application/json" \
-d '{
"query": "benefits",
"locale": "en_US",
"rows": 10,
"targetingRules": ["department:HR", "department:Finance"]
}'

For the full Targeting Rules reference (rule types, Solr query generation, fallback clause, and practical examples) see Semantic Navigation → Targeting Rules.


POST /api/sn/{siteName}/search above is the authenticated endpoint: it accepts the impersonation fields (userId, populateMetrics, the conditional targeting maps) and returns 401 without a principal.

For public, client-side search there is a second, anonymous POST endpoint that takes a clean read-only JSON body:

POST http://localhost:2700/api/sn/{siteName}/search/query

Use it when a filter value can contain a character that means something in a query string: &, =, +, #, % or a space. A JSON body has no query-string grammar, so a value like section:RAG & Chat cannot be mis-read as query structure. Nothing is deprecated: GET /search and its links stay, and are still the right choice for deep-linkable, bookmarkable, SEO-crawlable and no-JS search pages.

FieldTypeDescription
q (alias query)stringSearch query (default *)
localestringLocale code, e.g. en_US
page (alias p)integerPage number (default 1)
rowsintegerResults per page
sortstringrelevance, newest, oldest or a custom sort name
groupstringGroup results by field
fq / fqAnd / fqOrstring[]Filter queries, each a raw field:value
fqOperator / fqItemOperatorstringOperators between / within facet groups
fieldList (alias fl)string[]Restrict which fields are returned
targetingRulesstring[]Targeting rules (no conditional maps, read-only)
autoCorrectionDisabledbooleanSkip spelling auto-correction (default true)
responseFormatstringLINKS (default) or STRUCTURED (see below)
includeSortOptionsbooleanfalse. When true, a STRUCTURED response also carries sortOptions
modestringstandard (default) or instant. Send instant from a search box that updates while the visitor types: the last term of q matches as a prefix (work finds “Workshop”), and the query is not recorded in the search metrics. A query that already uses syntax (a phrase, field:value, a wildcard, AND/OR/NOT) and a last term shorter than 2 characters run as typed. request.q echoes what was typed

The endpoint is anonymous and CSRF-exempt, so a browser client needs no /csrf priming round-trip.

Terminal window
curl -X POST "http://localhost:2700/api/sn/Sample/search/query" -H "Content-Type: application/json" -d '{
"q": "chat",
"locale": "en_US",
"fq": ["section:RAG & Chat"],
"responseFormat": "STRUCTURED"
}'

responseFormat: "LINKS" (the default) returns the same link-bearing body as the GET endpoint, so a client can move to POST without changing how it reads the response.

responseFormat: "STRUCTURED" returns a link-free body instead. The contract is: take the echoed request, apply one structured change, send it back.

{
"request": { "q": "chat", "page": 1, "locale": "en_US", "fq": ["section:RAG & Chat"] },
"icon": "tabler:building-store",
"queryContext": { "count": 42, "facetItemType": "OR" },
"results": {
"document": [
{
"elevate": false,
"fields": { "id": "doc-1", "title": "Grounding a chat on your own content" },
"metadata": [
{
"field": "author",
"value": "Ada",
"filterQuery": "author:Ada",
"text": "Ada",
"selected": false,
"action": "ADD"
}
]
}
]
},
"pagination": [
{ "type": "CURRENT", "text": "1", "page": 1, "current": true },
{ "type": "NEXT", "text": "Next", "page": 2, "current": false }
],
"facets": [
{
"name": "section",
"label": "Section",
"multiValued": true,
"clearFilterQueries": ["section:RAG & Chat"],
"items": [
{
"field": "section",
"value": "RAG & Chat",
"filterQuery": "section:RAG & Chat",
"label": "RAG & Chat",
"count": 12,
"selected": true,
"action": "REMOVE"
}
]
}
],
"selectedFilterQueries": ["section:RAG & Chat"],
"spellCheck": { "correctedText": false, "usingCorrectedText": false },
"locales": [{ "locale": "en_US", "selected": true }],
"similar": [{ "id": "doc-2", "title": "Tuning your retrieval", "url": "/tuning" }],
"spotlights": [{ "id": "sp-1", "position": 1, "title": "Start here", "link": "/start" }]
}

A few fields carry state rather than navigation, and are present only when the site or the request produced them:

FieldWhen it appears
iconthe site configured an icon. A plain Iconify name (tabler:building-store). Render it as the search header’s brand mark, falling back to your own logo when absent
similarthe request named a seed document: the “more like this” neighbours, each { id, title, type, url }
spotlightsthe site pinned documents for this query: each { id, position, title, type, referenceId, content, link }, position being the 1-based slot to inject it at
sortOptionsthe request asked for includeSortOptions (see below)

url and link above are the documents’ own destinations, the same kind of value results[].fields.url carries, not search actions. Every navigation that used to be a URL is a value to put on the next request:

Instead of following…Do this
a facet item’s linkapply item.action (ADD / REMOVE / REPLACE) to request.fq using item.filterQuery
a document’s metadata[].hrefapply the chip’s own action to request.fq using its filterQuery, exactly as for a facet item, because it is the same action (chips are {field, value, filterQuery, text, selected, action} here)
a facet’s cleanUpLinkremove facet.clearFilterQueries from request.fq
the widget’s cleanUpFacetsclear request.fq (see selectedFilterQueries)
a pagination hrefset request.page = page.page
a locale linkset request.locale
a spell-check linkset request.q = spellCheck.correctedTextValue

An applied facet stays in facets whatever the site’s facet item type. Its selected value has selected: true and the facet’s clearFilterQueries names what to remove. The LINKS response keeps the older behaviour, where a site whose item type is not OR drops an applied facet and lists it under widget.facetToRemove.

Both JavaScript SDKs do this for you: see @viglet/turing-sdk (toggleFacet, clearFacet, clearAllFilters, goToPage, applySpellCheck) and @viglet/turing-react-sdk.

The sort option list has its own endpoint (GET /api/sn/{siteName}/search/sort-options) and is not part of the response by default: the control is already rendered on every request after the first. To paint it on the first frame without a second call, ask for it:

{ "q": "chat", "responseFormat": "STRUCTURED", "includeSortOptions": true }

The response then carries a sortOptions array of { "value", "label" }, exactly what the dedicated endpoint serves. Put the chosen value on request.sort.

The flag is not echoed back on request, so re-sending the echoed object asks for results without re-fetching the list. It has no effect on a LINKS response.

responseFormat is a plain request field, not an Accept header, so the GET search endpoint honours it too, handy for comparing the two shapes against one query, or for a server-rendered page that wants facet data without parsing links:

GET http://localhost:2700/api/sn/{siteName}/search?q=chat&responseFormat=STRUCTURED

The value is case-insensitive (structured works). Omitting it returns the regular link-bearing response, unchanged; an unrecognised value returns 400 Bad Request rather than silently falling back.

The same body works on the other two read endpoints, so a whole search surface can be POST-only:

POST http://localhost:2700/api/sn/{siteName}/ac/query
POST http://localhost:2700/api/sn/{siteName}/search/list/query

Both return exactly what their GET counterparts return (a list of suggestions and a select-list projection). Neither carries links, so responseFormat does not apply to them.

Terminal window
curl -X POST "http://localhost:2700/api/sn/Sample/ac/query" -H "Content-Type: application/json" -d '{ "q": "enter", "locale": "en_US", "rows": 10 }'

Returns suggestions for the given prefix. Ideal for search-as-you-type UIs.

GET http://localhost:2700/api/sn/{siteName}/ac
ParameterRequiredDescription
q✅Prefix to complete
_setlocale✅Locale
rowsMaximum number of suggestions

Example:

Terminal window
curl "http://localhost:2700/api/sn/Sample/ac?q=enter&_setlocale=en_US"

Response:

["enterprise", "enterprise search", "enterprise AI", "entries"]

A POST /api/sn/{siteName}/ac/query variant takes the same JSON body as POST-native search: use it when a filter narrowing the suggestions contains a query-string character.


Returns the most recent search terms for a given user: useful for personalised search history UIs.

POST http://localhost:2700/api/sn/{siteName}/search/latest
ParameterLocationDescription
qQueryCurrent query
rowsQueryMax results (default: 5)
_setlocaleQueryLocale
userIdBody (JSON)User identifier

Example:

Terminal window
curl -X POST "http://localhost:2700/api/sn/Sample/search/latest?q=cloud&rows=5&_setlocale=en_US" \
-H "Key: <API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "userId": "user123" }'

Response:

["cloud computing", "cloud storage", "cloud security"]

Lists all configured locales for a Semantic Navigation Site.

GET http://localhost:2700/api/sn/{siteName}/search/locales

Response:

[
{ "locale": "en_US", "link": "/api/sn/Sample/search?_setlocale=en_US" },
{ "locale": "pt_BR", "link": "/api/sn/Sample/search?_setlocale=pt_BR" }
]

Corrects a query against the site’s indexed vocabulary.

GET http://localhost:2700/api/sn/{siteName}/{locale}/spell-check
ParameterRequiredDescription
q✅Text to check

Example:

Terminal window
curl "http://localhost:2700/api/sn/Sample/en_US/spell-check?q=entirprise"

Turns a visitor’s description into the q and fq of the site’s own search, so a page can show the ranked, faceted results for “part-time job near the subway in Sao Paulo” instead of a chat answer.

POST http://localhost:2700/api/sn/{siteName}/nl-filters
Body fieldRequiredDescription
text✅The visitor’s request
localeSearch locale; when left out, the site’s first locale

Example:

Terminal window
curl -X POST "http://localhost:2700/api/sn/jobs/nl-filters" \
-H "Content-Type: application/json" \
-d '{"text": "meio periodo em sao paulo, sem experiencia", "locale": "pt_BR"}'
{
"available": true,
"text": "meio periodo em sao paulo, sem experiencia",
"q": "sem experiencia",
"filters": [
{ "field": "city", "values": ["São Paulo"], "rangeStart": null, "rangeEnd": null,
"sourceWords": ["sao paulo"], "confidence": 1.0, "grounded": true },
{ "field": "workload", "values": ["Part-time"], "rangeStart": null, "rangeEnd": null,
"sourceWords": [], "confidence": 0.6, "grounded": true }
],
"fq": ["city:São Paulo", "workload:Part-time"],
"dropped": []
}

Pass q and fq to the search (POST /api/sn/{site}/search/query) as they are. Turing checks every value against the values the site actually holds and spells it the way the site indexes it (sao paulo becomes São Paulo). A value the site does not hold is left out of fq and listed in dropped with the reason (NOT_IN_SITE, or UNKNOWN_FIELD for a field the site does not have).

Relative dates are resolved against the server’s date, in English and Portuguese: “last year”, “this month”, “last month”, “the last 7 days”, “since March”, “since 2024” (“ano passado”, “neste mês”, “desde março”, …). “Last year” asked on 2026-10-24 becomes [2025-01-01 TO 2025-12-31]. A date range the text neither phrases that way nor dates with a year is a guess, so it is left out of fq and listed in dropped as UNGROUNDED_DATE.

Response fieldMeaning
qWhat is left to match as free text
fqfield:value per value and field:[start TO end] per range, the form the search takes
filters[].sourceWordsThe words of the request each value was read from
filters[].confidence1 when the request spells the value, 0.6 when it was inferred (a synonym, a translation), 0.3 when the engine could not list the field’s values to check it
availablefalse when no LLM is configured: nothing is filtered and q is the whole request

The call spends one LLM completion, so it is gated like the site chat: a site in API-key mode requires its key, and anonymous calls share the chat’s rate limit. From the SDKs, call postNlFilters (JavaScript SDK).

The site chat and the catalog copilot read list-style questions (“quais vagas em São Paulo?”) through the same extraction, so their answers are narrowed by the same checked values. To receive those filters in the chat stream, send "includeFilters": true in the body of POST /api/sn/{siteName}/chat/conversation: a list-style question then streams one filters event, carrying the same JSON as above, before the answer. It is off by default, because a client that predates the event would show it as answer text.


Sends a question to the GenAI engine of a Semantic Navigation Site. Returns a streaming SSE response grounded in the site’s indexed content.

GET http://localhost:2700/api/sn/{siteName}/chat?q={question}

Example:

Terminal window
curl "http://localhost:2700/api/sn/Sample/chat?q=What+are+the+main+features?" \
-H "Key: <YOUR_API_TOKEN>"

Sends a message to a specific AI Agent. Returns a streaming SSE response.

POST http://localhost:2700/api/v2/llm/agent/{agentId}/chat

Example:

Terminal window
curl -X POST "http://localhost:2700/api/v2/llm/agent/my-agent/chat" \
-H "Key: <YOUR_API_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "message": "Summarise the latest quarterly report" }'

See Chat for the full chat interface documentation.

Which AI features a site can use, read from its configuration. No model is called, so a page can ask before it shows an AI block. It needs no login; a site in API_KEY mode needs its key, as for search.

GET http://localhost:2700/api/sn/{siteName}/ai/capabilities
{
"site": "Sample",
"chat": { "enabled": true, "reason": "NONE" },
"answer": { "enabled": true, "reason": "NONE" },
"nlFilters": { "enabled": true, "reason": "NONE" },
"copilot": { "enabled": true, "reason": "NONE" },
"similarByVector": { "enabled": false, "reason": "VECTORLESS" },
"summaries": { "enabled": true, "reason": "NONE" }
}

chat and answer always agree, and carry the same reason as GET /api/sn/{siteName}/chat/enabled (NO_GENAI, NO_AGENT, RAG_DISABLED, MISSING_EMBEDDING, …). The features that only need a default LLM report MISSING_DEFAULT_LLM when none is set.

similarByVector is the one answer that looks past configuration: when the site is set up for RAG but its vector store holds none of its content, it reports NOT_EMBEDDED. That happens when the content was indexed before generative AI was turned on; reindex the site to fix it. Chat stays enabled meanwhile, but it will not find the content. The check lists one stored chunk per locale, never calls a model, and is cached for a minute; a store that cannot list chunks reports the site as ready. From @viglet/turing-sdk: fetchAiCapabilities(client, site).


Returns token consumption statistics for a given month.

GET http://localhost:2700/api/v2/llm/token-usage?month=YYYY-MM

Example:

Terminal window
curl "http://localhost:2700/api/v2/llm/token-usage?month=2025-01" \
-H "Key: <YOUR_API_TOKEN>"

Authentication required. See Token Usage for details.


What visitors searched for and did not find. Both endpoints take the site’s id and the same window, and need authentication (a Key header or a session).

GET /api/sn/{siteId}/metrics/failed-searches?from=&to=&period=&rows=
GET /api/sn/{siteId}/metrics/content-gaps?from=&to=&period=&rows=
ParameterMeaning
from, toISO-8601 instants. to defaults to now; without from the report covers all time
periodShorthand for from: today, this-week, this-month or all-time
rowsHow many terms, 1 to 50 (default 10)

failed-searches lists the queries that returned nothing every time they ran. content-gaps lists the queries searched often whose average result count is under 3: what to write next. Both answer { from, to, terms: [{ term, searches, avgResults, lastSeen }], metricsEnabled }, most searched first; terms searched equally often come back most recently searched first, so a new gap is not cut off by older ones. These are the same reports the MCP tools top_failed_searches and content_gaps read.

metricsEnabled is false when the server does not record searches (turing.search.metrics.enabled is off). An empty report then says nothing about the site’s traffic, so show the user that metrics are off rather than an empty list.

Terminal window
curl "http://localhost:2700/api/sn/<SITE_ID>/metrics/failed-searches?period=this-month&rows=20" \
-H "Key: <YOUR_API_TOKEN>"

From @viglet/turing-sdk: fetchFailedSearches(client, siteId, { period: "this-month" }) and fetchContentGaps(...), with a client created with an apiKey.

The clicks your search page reports to POST /api/sn/{site}/click are read back here. The window and authentication are the same as above, plus an optional locale (pt_BR or pt-BR) that narrows both the searches and the clicks to one locale.

GET /api/sn/{siteId}/metrics/clicks?from=&to=&period=&locale=&rows=
{
"from": "2026-09-26T00:00:00Z", "to": "2026-10-26T00:00:00Z", "locale": null,
"searches": 1240, "clicks": 410, "ctr": 0.3306,
"terms": [
{ "term": "workshop", "searches": 96, "clicks": 41, "ctr": 0.4271, "avgPosition": 1.8, "avgResults": 12.0 }
],
"neverClicked": [
{ "term": "pricing", "searches": 30, "clicks": 0, "ctr": 0.0, "avgPosition": null, "avgResults": 4.5 }
],
"metricsEnabled": true
}

ctr is clicks per search, so it can pass 1 when visitors open several results. terms are the most searched terms first. neverClicked lists terms that found results nobody opened: a relevance problem, unlike failed-searches, which is a content problem. The console’s Top search terms page shows the same numbers as a click-through column.

Clicks are recorded only when turing.search.metrics.enabled is on (env TURING_SEARCH_METRICS_ENABLED=true). When it is off, POST /click drops the click and answers 204 with the header X-Turing-Metrics: disabled, so a client can tell why nothing is counted. When it is on, the answer is 202. The reports say the same in their body: metricsEnabled on clicks, failed-searches and content-gaps.

From the vanilla SDK: fetchClickThrough(client, siteId, { period: "this-month", locale: "pt-BR" }).


The Integration API provides a reverse-proxy endpoint that forwards requests to a configured external integration instance (e.g., an AEM connector or Web Crawler). All HTTP methods are supported. The proxy validates the target host and path to prevent SSRF attacks.

GET|POST|PUT|DELETE http://localhost:2700/api/v2/integration/{integrationId}/**

The {integrationId} maps to an Integration Instance configured in Administration. The remainder of the path (**) is forwarded to the instance’s endpoint URL.

Example:

Terminal window
curl -X POST "http://localhost:2700/api/v2/integration/my-aem-instance/index" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/content/page.html" }'

This endpoint is public (no authentication required).

Manage integration instances. Authentication required.

MethodEndpointDescription
GET/api/integrationList all integration instances
GET/api/integration/{id}Get an integration instance
POST/api/integrationCreate an integration instance
PUT/api/integration/{id}Update an integration instance
DELETE/api/integration/{id}Delete an integration instance
GET/api/integration/vendorList available integration vendors

Manage files and the RAG Knowledge Base. All endpoints require authentication.

MethodEndpointDescription
GET/api/asset?prefix=List files and folders in a prefix (folder)
POST/api/assetUpload one or more files (multipart/form-data)
GET/api/asset/download?objectName=Download a file (Content-Disposition: attachment)
GET/api/asset/preview?objectName=Preview a file inline (Content-Disposition: inline)
GET/api/asset/metadata?objectName=Retrieve file metadata (size, type, date)
POST/api/asset/folder?path=Create a new folder
DELETE/api/asset?objectName=Delete a file or folder
POST/api/asset/trainStart batch AI Training
GET/api/asset/train/statusPoll current training status
GET/api/asset/train/records?objectNames=Get training timestamps for specific files

After upload, an asynchronous event triggers AI indexing for each file. When a file is deleted, its embeddings are automatically removed from the vector store.

For full details, see Assets.


Manage AI Agents. Authentication required.

MethodEndpointDescription
GET/api/ai-agentList all agents (ordered by title)
GET/api/ai-agent/structureGet empty structure template for a new agent
GET/api/ai-agent/{id}Get a specific agent
POST/api/ai-agentCreate a new agent
PUT/api/ai-agent/{id}Update an existing agent
DELETE/api/ai-agent/{id}Delete an agent
MethodEndpointDescription
POST/api/v2/ai-agent/{agentId}/chatStream chat response (SSE). Request body: { llmInstanceId, messages[] }
GET/api/v2/ai-agent/{agentId}/chat/context-infoGet LLM context window size. Query param: llmInstanceId
MethodEndpointDescription
GET/api/native-toolList all available tool groups with tool names and descriptions

For full details, see AI Agents.


The following endpoints manage SN Sites, fields, and spotlights. All require authentication via the Key header. For full details, explore the Swagger UI at http://localhost:2700/swagger-ui.html.

MethodEndpointDescription
GET/api/snList all SN Sites
GET/api/sn/{id}Get an SN Site
POST/api/snCreate an SN Site
PUT/api/sn/{id}Update an SN Site
DELETE/api/sn/{id}Delete an SN Site (removes index cores)
GET/api/sn/{id}/monitoringSite monitoring status (document count, queue size)
GET/api/sn/{id}/exportExport an SN Site configuration
GET/api/sn/exportExport all SN Site configurations

Example: list sites:

Terminal window
curl "http://localhost:2700/api/sn" \
-H "Key: <YOUR_API_TOKEN>"
MethodEndpointDescription
GET/api/sn/{snSiteId}/fieldList fields for a site
GET/api/sn/{snSiteId}/field/{id}Get a field
POST/api/sn/{snSiteId}/fieldCreate a field
PUT/api/sn/{snSiteId}/field/{id}Update a field
DELETE/api/sn/{snSiteId}/field/{id}Delete a field
MethodEndpointDescription
GET/api/sn/{snSiteId}/spotlightList spotlights for a site
GET/api/sn/{snSiteId}/spotlight/{id}Get a spotlight
POST/api/sn/{snSiteId}/spotlightCreate a spotlight
PUT/api/sn/{snSiteId}/spotlight/{id}Update a spotlight
DELETE/api/sn/{snSiteId}/spotlight/{id}Delete a spotlight
MethodEndpointDescription
GET/api/intentList all intents
GET/api/intent/enabledList enabled intents (sorted by display order)
GET/api/intent/{id}Get an intent with its actions
POST/api/intentCreate an intent
PUT/api/intent/{id}Update an intent
DELETE/api/intent/{id}Delete an intent

For full details, see Intent.

MethodEndpointDescription
POST/api/importImport SN Site configurations from a ZIP file
POST/api/sn/importImport content (JSON job items)
POST/api/sn/import/zipImport content from a ZIP with file attachments

For full details, see Import & Export.

MethodEndpointDescription
GET/api/loggingServer logs (paginated, filterable)
GET/api/logging/indexingIndexing pipeline logs
GET/api/logging/aemAEM connector logs

For full details, see Logging.


Turing exposes a GraphQL endpoint that provides the same Semantic Navigation search capabilities as the REST API using GraphQL queries. The endpoint is public (no authentication required).

POST http://localhost:2700/graphql
QueryDescription
siteNamesReturns all configured SN Site names as a string array
siteSearch(siteName, searchParams, locale)Performs a search against an SN Site

An interactive GraphQL IDE is available at /graphiql with schema introspection, auto-completion, and query validation.

Example:

Terminal window
curl -X POST "http://localhost:2700/graphql" \
-H "Content-Type: application/json" \
-d '{
"query": "query { siteSearch(siteName: SAMPLE, searchParams: { q: \"enterprise search\", rows: 10 }, locale: \"en_US\") { queryContext { count } results { document { fields { title url } } } } }"
}'

For the full schema reference, query examples, dynamic fields, filter operators, and response types, see GraphQL API.


Extracts text from documents via OCR. Two endpoints are available, one for file uploads and one for URLs. Both are public (no authentication required).

POST http://localhost:2700/api/ocr/file

Accepts a multipart/form-data request with a file parameter.

Example:

Terminal window
curl -X POST "http://localhost:2700/api/ocr/file" \
-F "file=@/path/to/document.pdf"
POST http://localhost:2700/api/ocr/url

Accepts a JSON body with a url field pointing to a remote document.

Example:

Terminal window
curl -X POST "http://localhost:2700/api/ocr/url" \
-H "Content-Type: application/json" \
-d '{ "url": "https://example.com/report.pdf" }'

Both endpoints return a JSON object containing the extracted text and file metadata.


PageDescription
AuthenticationHow to create and use API Tokens
Semantic NavigationSN Site configuration and the search response structure
ChatFront-end chat interface and GenAI API
AI AgentsAgent composition, tool selection, and MCP Servers
AssetsFile management and RAG Knowledge Base
IntentChat prompt suggestions API
Import & ExportSite configuration and content import
LoggingApplication and indexing log API
Token UsageToken consumption reporting