REST API reference

The Workspace exposes its capabilities over a REST API rooted at your workspace URL. Most enterprise integrations will use only a small slice of this surface — the custom endpoints you write and a handful of admin/auth endpoints.

This page describes the contract for the built-in endpoints. For your own endpoints, see Custom Endpoints.

Base URL

https://<your-workspace-host>/api/

For local development that is typically http://localhost:8080/api/.

Authentication

All authenticated endpoints accept a bearer token:

Authorization: Bearer <token>

See Token scopes for the three token types (session JWT, API token, endpoint token) and how to choose between them.

Unauthenticated endpoints — at the moment, only the login routes — accept and return JSON without an Authorization header.

Content type

Request and response bodies are JSON unless explicitly noted. Use:

Content-Type: application/json
Accept: application/json

Standard response envelopes

Successful responses are bare JSON values shaped per endpoint. Errors share the envelope documented in Error codes.

Pagination

List endpoints accept skip and take query parameters and return:

{
  "items": [ /* ... */ ],
  "total": 1248,
  "skip": 0,
  "take": 50
}

Always treat total as advisory — for very large traversals it may be a lower bound. Iterate by checking items.length === take until it isn't.


Authentication endpoints

POST/api/login/create

Exchange a username/password for a session JWT.

Body
{ "user": "...", "password": "..." }
Returns
{ "token": "...", "expiresAt": "..." }
GET/api/login/check

Verify a session JWT is still valid.

Auth
Bearer (session JWT)
Returns
{ "user": { "uid": "...", "name": "..." } } or 401.
POST/api/login/password/change

Change the calling user's password.

Auth
Bearer (session JWT)
Body
{ "oldPassword": "...", "newPassword": "..." }
POST/api/login/password/reset

Start a password-reset flow for an email address.

Body
{ "email": "..." }

SSO endpoints

Each SSO provider exposes the same shape under a provider-specific prefix. The provider names are microsoftsso, googlesso, oktasso, auth0sso.

GET/api/{provider}/config

Read the current SSO config (admin only).

POST/api/{provider}/config

Update the SSO config (admin only).

GET/api/{provider}/get-login-url

Returns the URL clients should redirect to when starting an SSO login.

GET/api/{provider}/completed-login-attempt

Callback the identity provider redirects back to after the user authenticates. Configure this exact path as the redirect URI in your IdP.

SAML follows a different pattern:

GET/api/saml/createrequest/{providerName}/{issuer}/{destination}

Builds an AuthnRequest for the configured provider.

POST/api/saml/acs

Assertion Consumer Service endpoint — the SAML callback.

See the individual SSO guides (Microsoft, Google, Okta, Auth0) for end-to-end setup.

Custom endpoint invocation

POST/api/endpoints/run/{name}

Invoke an endpoint by name as the authenticated user. Used by the workspace's own front-end and by any caller using a user session JWT.

Auth
Bearer (session JWT, or API token with the endpoints:run scope)
Body
the request shape your endpoint expects (ParseBody<T>()).
Returns
whatever the endpoint returns (typed JSON).
POST/api/endpoints/token/run/{name}

Invoke an endpoint with an endpoint token. The endpoint's CurrentUser is the token's associated user (or null if the token is system-scoped).

Auth
Bearer (endpoint token)
Body / Returns
same as /run/{name}.
POST/api/cce/{name}

Lower-level invocation alias used by the front-end. Same auth and behavior as /api/endpoints/run/{name}.

Example — calling a custom endpoint

curl -X POST "https://workspace.example.com/api/endpoints/run/similar-tickets" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "screen flicker after firmware update",
    "productSku": "PRO-14",
    "limit": 5
  }'
[
  {
    "UID": "abc123…",
    "Type": "Ticket",
    "Id": "T-9182",
    "Subject": "Screen flicker after 5.1.2 firmware",
    "Body": "…"
  }
]

AI chat and tool metrics

POST/api/chatai/chat

Send a turn to the chat assistant. Used by the built-in chat view.

GET/api/chatai/tools/metrics

Per-tool invocation metrics (counts, average latency, error rate). Admin only.

GET/api/endpoints/metrics

Per-endpoint invocation metrics. Admin only.

Search configuration

GET/api/search/synonyms/list/for?lang=…&query=…&includeSynonyms=1

Every term that has synonyms configured for a language. Pages with the X-MSK-SKIP / X-MSK-LIMIT headers and answers { Results, TotalCount }; with includeSynonyms=1 each row is a [term, linkedTo] pair. Requires the Can Manage Synonyms role.

GET/api/search/synonyms/for?token=…&lang=…&tolerance=…

The synonyms, alternates, blocked terms and suggested candidates for one term. tolerance (0.7–1.0) sets how similar a suggestion has to be. Requires the Can Manage Synonyms role.

PUT/api/search/synonyms/for?token=…&lang=…&ignoreCase=…&replace=…

Store the Synonyms, Alternates and Blocked lists for one term — all three are required, send [] for unused ones. Writes one direction only; mirror the call to link a pair. replace=false merges with what is configured. Requires the Can Manage Synonyms role, write access.

Language codes are the usual two-letter codes, with -- for "any language". See Synonyms for what each list does.

Assistant configuration

GET/api/search/settings/llm

Which node types and search filters the built-in search tool exposes to an assistant, plus every filter available for the enabled node types. Admin only.

PUT/api/search/settings/llm

Store the exposed node types and filters. An empty set restores the built-in defaults. Admin, write access.

GET/api/chatai/deep-research/settings

The deep research queue limits, the workspace's researchers, and the runs executing or queued right now. Admin only.

POST/api/chatai/deep-research/settings

Store the queue limits and per-run ceilings. A value of zero clears the override and restores the default. Admin, write access.

POST/api/chatai/deep-research/cancel?chatUID=…

Stop the deep research running in a conversation. Admin, write access.

See The search tool and Deep research for what these configure.

These are the operational metrics shown in Settings → Monitoring. There is currently no public Prometheus /metrics endpoint; see Monitoring for the roadmap and external-monitoring options.

Administration and diagnostics

The endpoints listed below require a system-administrator session; reads accept a read-only administrator, writes do not. The behaviour changes at the end of the section also cover endpoints an ordinary signed-in user can reach, and say so per entry.

GET/api/build/catalog

Everything the admin Build tree draws, in one call and with no source code in it. Compile-free — AI-tool health comes from each node's stored precompiled flag. Admin, read-only.

GET/api/build/compile-status

Which catalog items do not compile, read from the compile caches rather than by compiling on the request. Admin, read-only.

Returns
{ "FailingIds": [ "…" ], "Pending": true|false }. Pending: true means a cache has not been warmed yet and a background warm-up has just started — poll again rather than treating an empty FailingIds as "everything compiles".
GET/api/code/history/all?from=…&to=…&skip=…&take=…

The workspace change-history timeline, newest first, backing Manage → Operate → Change History. Admin, read-only.

GET/api/graph/stats/database

The per-node-type / per-edge-type database counters, as the aligned table the server logs periodically. Admin, read-only. text/plain.

GET/api/graph/stats/pool

The object-pool counters, as the table the server logs next to the database statistics. Pools are process-wide, so this is not per-graph. Admin, read-only. text/plain.

GET/api/graph/logs-files/download?query=…&fileNames=…

Every match of a historical log search, streamed as one .log file. Admin, read-only.

POST/api/admin-agent/sessions/{id}/uploads

Attach a file to an admin-assistant conversation. Admin, write access.

Body
multipart/form-data with one file.
Returns
the same file-entry result the chat composer uses — { "UID": "…", "Size": …, "ContentType": "…" }. Uploading the same name twice answers 409 carrying the entry already there, rather than making a second copy. The file is stored as an ordinary file entry in the administrator's upload folder, with an edge from the conversation, and the assistant reads it at /home/uploads/<name>. It is deleted with the conversation.

Streamed responses (server-sent events)

Two endpoints that apply definitions to a workspace answer with a text/event-stream instead of a single JSON body, because compiling and rebuilding what a change touches outlasts what a browser — or a proxy in front of it — holds a plain request open for:

  • POST /api/admin-agent/sessions/{id}/commit/approve
  • POST /api/graph/import-definitions

The event sequence is the same for both: one event: log per step, then one event: result carrying exactly the payload the endpoint used to answer with, then event: done. A keepalive SSE comment (:) is written when nothing has happened for ten seconds. A caller that only wants the outcome can read the last result event and ignore the rest.

Two things to keep in mind when calling either directly:

  • A refusal before the stream opens is still an ordinary status code. import-definitions checks the upload's content type first and answers a plain 400 for anything that is not a zip; the approval endpoint answers 404 for a session belonging to someone else.
  • Closing the connection does not stop the work. Neither apply is tied to the request's cancellation — the approval runs on the server's shutdown token and the import takes no token at all — so a client that goes away loses the log, not the change.

Behaviour changes

  • Access groups (signed-in user). PUT /api/groups/group, PUT /api/groups/group/{groupUID} and POST /api/groups/group/{groupUID}/delete answer 403 for a non-administrator caller when teams are disabled for the workspace, with the reason in the response body. An administrator with write access is unaffected — turning teams off takes the feature away from users, it does not freeze the groups the workspace is built on.
  • Migration jobs (admin, write access). POST /api/migrations/cancel cancels the run and waits up to 30 seconds for it to stop, so a caller can rewrite or reset the task straight afterwards. It answers 409 if the migration's own code never honours its cancellation token. POST /api/migrations/reset-task-status answers 409 while a job is still running, rather than throwing a 500.
  • Synthetic sample downloads (any signed-in, non-guest user). POST /api/node/download/synthetic/csv and POST /api/node/download/synthetic/jsonl no longer take a baseURL query parameter — the export now carries only the generated nodes' own fields and timestamp, with no Link, UID, Type or EdgeCount column. Drop the parameter from existing callers. See WithoutGraphMetadata().

User record field visibility

A _User node read by another signed-in user now returns only UID, Type, Timestamp, UserName, FirstName, LastName, FullName and Email. The administrative fields — the system-admin flag, roles, active, confirmed, one-time-password state, metadata and interface preferences — are withheld.

The projection applies to every generic reader:

  • GET /api/node/{uid}
  • POST /api/node/query
  • POST /api/node/many/full
  • POST /api/node/many/field (which answers BLOCKED for a field the caller may not read, on the plain and the interpolated branch alike)
  • GET /api/groups/group/{groupUID}/users
  • search results
  • the CSV / JSON Lines / Excel downloads (POST /api/node/download/csv, download/jsonl, download/zip)

The user themselves and any system administrator — read-only included — still read the whole record, as do the self-or-admin endpoints under /api/users/user/{uid}.

Two consequences for integrations:

  • Exports previously bypassed field-level access control entirely. It is now applied per node, and a cell the reader may not see is blanked. An integration parsing those exports may see fewer populated columns than before.
  • A user is always granted access to their own user node, so node-type access configuration cannot lock someone out of their own record.

The search engine is reachable from custom endpoints via Graph.CreateSearchAsync / CreateSearchAsUserAsync. There is no public HTTP route for ad-hoc search — wrap your own retrieval logic in a custom endpoint instead. This makes it impossible to bypass scoping or ranking via raw HTTP, and gives you a stable contract you can version.

Graph

The graph is reachable via the Q() chain from inside custom endpoints, the admin Shell, and Curiosity.Library in connectors. As with search, raw graph traversal is not exposed over HTTP for safety; expose what you need via endpoints.

Versioning

The Workspace is delivered as a versioned container image using calendar versioning (curiosityai/curiosity:26.6.66474YY.M.build, so 26.6 is June 2026 and 66474 is the running build number). The built-in endpoints listed on this page are part of that contract: any breaking change is called out in the release notes and a migration note in the changelog for that release.

Your custom endpoints are versioned by you. The recommended pattern is to put the version in the endpoint name (similar-tickets-v2) and keep the old endpoint live during the deprecation window.

Where to go next

© 2026 Curiosity. All rights reserved.