JavaScript API
Quick Start
import PlaidClient from 'plaid-client';
const client = await PlaidClient.login('http://localhost:8080', 'user@example.com', 'password');
// Create a project
const project = await client.projects.create('My Project');
// Create a document
const doc = await client.documents.create(project.id, 'Document 1');
// List a project's documents (paginated; the client returns the full list)
const docs = await client.projects.listDocuments(project.id);
// Batch multiple operations atomically
const results = await client.batched(async (b) => {
b.tokens.create(tokenLayerId, textId, 0, 5);
b.tokens.create(tokenLayerId, textId, 6, 11);
});
Client
constructor(baseUrl, token, [options], [options.timeout], [options.batchTimeout])Create a new PlaidClient instance
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The base URL for the API |
token |
string |
The authentication token |
options optional |
object |
Client options |
options.timeout optional |
number |
=30000] - Per-request timeout in ms (0 or null disables it) |
options.batchTimeout optional |
number |
=180000] - Timeout for batch submissions in ms (0 or null disables it) |
health(baseUrl, [options]) staticLiveness, version and database size, with NO authentication and no client instance — for a launcher or status page checking whether a server is up before anyone logs in.
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The API base URL |
options optional |
object |
info(baseUrl, [options]) staticThe limits this server enforces, with NO authentication and no client instance. See the instance method for the shape.
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The API base URL |
options optional |
object |
inviteUrl(appUrl, code) staticBuild the link to hand someone for an invite code. The server never sees an app URL, so the app that minted the invite is the one that names it. Both SPAs use HashRouter, so the code rides in the fragment — which also keeps it out of server access logs.
| Parameter | Type | Description |
|---|---|---|
appUrl |
string |
Where the SPA lives, e.g. "https://plaid.example.org/igt/" |
code |
string |
The code returned by invites.create() |
lookupInvite(baseUrl, code, [options]) staticDescribe an invite code, with NO authentication — this is what a signup page calls before the redeemer has an account. Returns the kind of link ("signup" or "password-reset"), its status ("active", "used", "expired", "revoked"), and the project it grants access to, if any.
Throws a 404-shaped error if the code is unknown. A known-but-dead code resolves normally with a non-"active" status, so the page can say why.
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The API base URL |
code |
string |
The invite code |
options optional |
object |
redeemInvite(baseUrl, code, credentials, [credentials.email], [credentials.displayName], credentials.password, [options]) staticRedeem an invite code, with NO authentication.
For a signup invite, pass email and password to create the account (and optionally displayName); the invite's grants are applied in the same transaction. For a password reset link, pass password only. Resolves to a logged-in client, exactly like login() — the redeemer just chose these credentials, so there is no reason to send them to a login form to retype them.
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The API base URL |
code |
string |
The invite code |
credentials |
object |
|
credentials.email optional |
string |
The new account's email address, which becomes its id and login (signup only) |
credentials.displayName optional |
string |
How the new user is shown in the UI; defaults to the local part of the email (signup only) |
credentials.password |
string |
Desired password (min 8 characters) |
options optional |
object |
login(baseUrl, userId, password, [options]) staticAuthenticate and return a new client instance with token. This is the single auth entry point — there is no client.login resource.
| Parameter | Type | Description |
|---|---|---|
baseUrl |
string |
The base URL for the API |
userId |
string |
User ID for authentication |
password |
string |
Password for authentication |
options optional |
object |
Client options forwarded to the constructor (e.g. { timeout }) |
enterStrictMode(documentId)Enter strict mode for a specific document, requiring document version headers so that conflicting concurrent writes are rejected.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The ID of the document to track versions for |
exitStrictMode()Exit strict mode and stop tracking document versions for writes.
batch()Open a batch: a view of this client with the same bundles, on which every write of project data queues instead of going out. submit() sends the queued operations as ONE atomic request (larger than the server's cap, as consecutive requests with the results concatenated in queue order, each atomic on its own, so a failure in a later one leaves the earlier ones committed, and the error it throws carries committed, the count saved, and committedResults, their results) and resolves to one result per operation; abort() drops them. A call made on the client itself is never touched by an open batch, and a read or an out-of-band signal made on the batch goes over the wire now (see the note at the top of http.js).
const b = client.batch(); b.tokens.bulkCreate(sentenceOps); b.tokens.bulkCreate(wordOps); const [sentRes, wordRes] = await b.submit();
Server-side a batch runs sequentially in one transaction: a child op sees parents created earlier in the same batch, and any op's failure rolls the whole batch back. A batch is not nestable. Prefer batched(), which submits or aborts for you.
batched(fn)Run fn with a batch, then submit all queued ops as submit() does (ONE atomic request up to MAX_BATCH_OPS operations, consecutive requests past it), or abort the batch if fn throws. fn receives the batch and makes its writes on it; it must NOT call submit() itself. Resolves to the batch results array ([] if fn queued nothing).
const [sentRes, wordRes] = await client.batched(async (b) => {
b.tokens.bulkCreate(sentenceOps);
b.tokens.bulkCreate(wordOps);
});A write made on client inside fn is not part of the batch: it goes over the wire at once, as it would anywhere else. See batch().
| Parameter | Type | Description |
|---|---|---|
fn |
(batch: PlaidClient) => (void | Promise |
beginOperation(message, [opts])Begin a LOGICAL OPERATION: a user-meaningful action ("Merge morphemes", "Re-transcribe") implemented as many low-level writes, possibly across several batches and even a service round-trip. Until endOperation(), every write is stamped with a client-minted ?group-id= (and the message) so the audit log shows the whole run as ONE expandable entry labeled message, with each write's own description underneath.
Grouping is orthogonal to batches: a batch is a transaction boundary, an operation is an intent boundary. An operation is NOT atomic — if write 3 of 5 fails, writes 1–2 stay committed (and logged under the group). Use a batch inside the operation for any step that must be all-or-nothing.
Nesting flattens: a beginOperation while one is open is a no-op that joins the outer operation (the outer label wins), and the matching endOperation is likewise a no-op. The label is recorded on the FIRST write, so an operation that is never ended (crash, closed tab) is still labeled in the log.
"Every write" is the writes of project data. Reads never join, and neither do the out-of-band signals shaped like a write (a document lock taken or renewed, a stopped service request, a service reporting itself, an admin control), and neither does a broadcast message (messages.sendMessage): none of them is audited, so there would be nothing under the label.
kind says what kind of operation this is, for a program reading the log: one of assistant-plan, service-run, import, bulk-edit, guess-adoption, repair or review (the server refuses any other). ref is a short string naming what the operation came from, in the shape its kind documents (the core manual, "Kinds of operation"). Both are recorded from the first write like the label, and a nested operation keeps the outer one's.
keys (from keySeed()) makes a run of the operation send the same requests as an earlier run with the same keys: the nth keyed request that joins it takes the Idempotency-Key <seed>.<n> and the document-version its first run claimed, so a request that landed is answered from its first send and writes nothing again. The count starts at 0 at each outermost begin. A nested operation joins the outer one's keys, unless it brings its own: then it numbers from 0 under its own seed until its matching end, and the outer numbering resumes after it.
| Parameter | Type | Description |
|---|---|---|
message |
string |
Human label for the operation. |
opts optional |
object |
Optional { id, kind, ref, keys }. id adopts an existing group id instead of minting one (a service joining the requester's operation; requestService propagates an open operation to the service automatically). kind, ref and keys are described above. |
endOperation([message])End the current logical operation. With no argument this is purely local (no request). Pass a refined message to relabel the group now that the outcome is known (e.g. endOperation('Merged 3 morphemes')) — that sends one PATCH, skipped if the operation never wrote anything. A refine from a nested (flattened) endOperation is ignored; the outer label wins.
| Parameter | Type | Description |
|---|---|---|
message optional |
string |
Optional refined label. |
withOperation(message, fn, [opts])Run fn as one logical operation (see beginOperation), ending it when fn settles — including on throw. fn receives a setMessage(msg) callback to refine the label once the outcome is known.
await client.withOperation('Merge morphemes', async (setMessage) => {
await client.batched(async (b) => { ... });
setMessage(`Merged ${n} morphemes`);
});The kind and reference (see beginOperation) come after fn:
await client.withOperation('Import ELAN corpus', run, { kind: 'import', ref: 'format:elan' });| Parameter | Type | Description |
|---|---|---|
message |
string |
Human label for the operation. |
fn |
function |
The work to run; receives setMessage(msg) to refine the label once the outcome is known. |
opts optional |
object |
Optional { kind, ref, id, keys }, as for beginOperation. |
Admin
admin.server()Everything about the server in one read: version and JVM uptime, database size and per-table row counts, media directory usage, backup configuration and the backups on disk, and the settings an operator gets asked to confirm. Carries no secrets. Runs a count per table and walks the media directory, so open it, do not poll it. Admin only.
admin.backup()Take a database backup right now, outside the nightly schedule. Resolves to the backup block with ok reporting whether the snapshot succeeded. Uses VACUUM INTO, which only reads, so it is safe while people are working. Admin only.
admin.locks()Documents currently held by an editing lock, with who holds each and when it expires on its own. Admin only.
admin.releaseLock(documentId)Drop the lock on a document whoever holds it. Idempotent. For a client that went away without releasing one. Admin only.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
admin.rateLimits()Live login and invite rate-limit buckets: the address, the account where there is one, failures inside the window, the limit, and whether it is currently blocking. Admin only.
admin.clearRateLimits([opts], [opts.ip], [opts.userId])Forget recorded rate-limit failures. With ip, clears that address, narrowed to one account with userId. With neither, clears every bucket. Only ever unblocks. Admin only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.ip optional |
string |
Clear only this address |
opts.userId optional |
string |
Narrow to one account on that address |
admin.logs([opts], [opts.limit], [opts.q], [opts.level], [opts.status], [opts.user], [opts.method])What the server has logged, structured and filtered, from an in-memory buffer kept whether or not a log file is configured. requests is one entry per HTTP request (method, path, status, duration, and who made it), events is everything else, with a stack trace where there was one. They are buffered separately so a burst of requests cannot evict an error. Both come newest first, matched counts what passed the filters, and request stats describe the filtered set. Covers Plaid's own log stream since the last restart: for library messages and older history, see logFile. Admin only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.limit optional |
number |
Max entries per kind (default 200, max 2000) |
opts.q optional |
string |
Substring of any field, case-insensitive |
opts.level optional |
string |
Minimum level for events, e.g. "warn" |
opts.status optional |
string |
"2xx".."5xx", an exact code, or "failures" |
opts.user optional |
string |
Only requests made by this account |
opts.method optional |
string |
Only requests with this HTTP method |
admin.logFile([opts], [opts.lines])The tail of the configured log file, as text lines. The only place third-party library messages and anything from before the last restart can be read. Resolves with an error string instead of lines when no log file is configured or it does not exist yet. Admin only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.lines optional |
number |
How many lines (default 200, max 2000) |
admin.userData([opts], [opts.prefix], [opts.pattern], [opts.includeValues])Private user-data entries across every account, each carrying its userId. /users/:userId/data has always been owner-or-admin; this is the same reach across accounts at once. Transparently follows pagination cursors and returns the full flat array. Admin only.
Narrow with prefix (the literal head of a key) and/or pattern, a GLOB over the whole key (* any run, ? one character) — the way to ask for a key convention identified by a segment in the middle, e.g. igt:assistant:*:meta:*. Values come only with includeValues, and are recased like any other body (see userData.put).
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.prefix optional |
string |
Only keys starting with this |
opts.pattern optional |
string |
Only keys matching this GLOB |
opts.includeValues optional |
boolean |
Also return each entry's value |
admin.userDataPage([opts], [opts.prefix], [opts.pattern], [opts.includeValues], [opts.limit], [opts.cursor])One page of private user-data entries across accounts, ordered by (user, key). Admin only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.prefix optional |
string |
Only keys starting with this |
opts.pattern optional |
string |
Only keys matching this GLOB |
opts.includeValues optional |
boolean |
Also return each entry's value |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
Api Tokens
apiTokens.list(userId)List a user's named API tokens. Never includes the signed token string itself — that is only returned once, by create(). Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID who owns the tokens |
apiTokens.listPage(userId, [opts], [opts.limit], [opts.cursor])Fetch a single page of a user's named API tokens.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID who owns the tokens |
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
apiTokens.iterPages(userId, [opts], [opts.pageSize])Async-iterate a user's named API tokens page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID who owns the tokens |
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
apiTokens.create(userId, name)Mint a named API token for a user. The returned token is the signed credential and is shown ONLY here — store it immediately. API tokens do not expire and survive password changes / logout; revoke to kill.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID who will own the token |
name |
string |
A human label, e.g. "Stanza parser" |
apiTokens.revoke(userId, tokenId)Revoke a named API token (soft-revoke; idempotent).
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID who owns the token |
tokenId |
string |
The token ID to revoke |
Audit
audit.list([opts], [opts.startTime], [opts.endTime], [opts.opTypes], [opts.kinds])The audit log across every project, oldest first. Same fold, window and op-type filter as the per-project read, with the entity scope dropped. Admin only. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.startTime optional |
string |
Only operations at or after this instant |
opts.endTime optional |
string |
Only operations at or before this instant |
opts.opTypes optional |
string[]|string |
Only these op types (e.g. ['span-layer/create']). An entry appears when one of its operations matches, carrying only the ones that did. |
opts.kinds optional |
string[]|string |
Only the entries of operations of these kinds (e.g. ['review']), each whole |
audit.listPage([opts], [opts.order], [opts.limit], [opts.cursor])Fetch a single page of the instance-wide audit log. Admin only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.order optional |
"asc"|"desc" |
"desc" pages newest-first, which is what a feed wants. A cursor belongs to the direction that made it. |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
audit.iterPages()Async-iterate the instance-wide audit log page by page. Admin only.
Documents
documents.checkLock(documentId)Check the lock status of a document.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
documents.acquireLock(documentId, [auditMessage], [newLockId])Acquire a document lock as a new holder.
The answer's lockId names the holder: renewLock and releaseLock take it. While the lock is held, a second acquire is refused with HTTP 423, whoever makes it, this user included.
newLockId names the holder from the client's side (a fresh UUID). An acquire whose answer never arrived can then be sent again, which answers 200 while that holder has the lock, or released. Without it the server mints the id, and a lost answer leaves a lock nobody can release until it expires.
outOfBand: the lock is a signal, not project data (see the note at the top of http.js). Queued on a batch it would be taken only at submit, after every write it was meant to guard, and until then answer success to a caller that does not hold it and cannot see the 423 saying somebody else does.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
auditMessage optional |
string |
|
newLockId optional |
string |
A holder id this client minted |
documents.renewLock(documentId, lockId)Renew the lock lockId holds while it is live. HTTP 423 if another holder has it, and also once it has expired or been dropped, even when nobody holds the document now: a renewal never takes a free document. outOfBand, for the same reason as acquireLock.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
lockId |
string |
The lockId acquireLock answered with |
documents.releaseLock(documentId, lockId)Release the lock lockId holds. Idempotent: a lock that holder no longer has is left alone. outOfBand, for the same reason as acquireLock: queued, the lock would be held until the batch submits, and not released at all if it aborts.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
lockId |
string |
The lockId acquireLock answered with |
documents.locked(documentId, fn, [options])Hold this document's server-enforced lock for the length of fn, releasing it on the way out (including on error). Resolves to what fn returned.
await client.documents.locked(docId, async () => {
// delete + recreate tokens
});Wrap any multi-step, server-side mutation of a document that must not interleave with a human editor or another service, e.g. a parser or tokenizer that deletes and recreates a document's tokens, spans and relations. A single atomic call does not need it. While the lock is held, writes to the document by ANOTHER user are refused with HTTP 423; the holder's own writes pass and renew it. If anyone already holds it, another block of this same user included, this rejects with a readable 423 and fn does not run.
The lock is renewed for as long as fn runs, so work that computes for minutes before it writes holds the lock the whole time rather than only for its first minute. If a renewal fails the lock is gone: every later write from this client throws DocumentLockLost, and a block that got to the end anyway ends with that error rather than reporting success. fn receives a lock handle and may read lock.lost (or call lock.raiseIfLost()) to give up sooner.
The lock is per HOLDER: each block is its own, named by lock.lockId, and only that id renews or releases it. NOT re-entrant: a nested locked() block on one document is a second holder and gets the 423. Lock at exactly one level per call path. The block mints its holder id and sends it with the acquire, so an acquire whose answer was lost is sent again (up to three tries) and, if none is answered, released rather than left to expire. A lost lock is recorded on the CLIENT, like strict mode, so it stops every write the client makes (on any batch of it too) and not only the ones this block makes.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
fn |
(lock: DocumentLock) => any |
The work to run while holding it |
options optional |
object |
{ keepAlive }: renew on a timer (default true) |
documents.getMedia(documentId)Get the media file for a document. Media is not versioned, so there is no as-of form: the route refuses the parameter. Prefer the document's own mediaUrl, which carries the file's version for caching.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
documents.uploadMedia(documentId, file, [auditMessage], [options])Upload a media file for a document. Uses Apache Tika for content validation.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
file |
File |
The file to upload |
auditMessage optional |
string |
Custom audit-log message for this write |
options optional |
object |
{ onProgress }: called with { loaded, total } (bytes) as the file goes up. In a browser the upload then travels by XMLHttpRequest, and the request timeout only fires when the upload stalls, not on total time. |
documents.setMetadata(documentId, documentId, body)Delete media file for a document
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID / // The upload above is multipart and cannot be batched, but a DELETE // carries no blob, so the batch transport takes it. It is a write // of the document's own data and queues like any other. Note that the // file removal happens outside the server's transaction, so a batch that // aborts after this op does not bring the file back. deleteMedia: (documentId, auditMessage) => this._request("DELETE", /api/v1/documents/${documentId}/media, { auditMessage, }), /** Replace all metadata for a document. |
documentId |
string |
The document ID |
body |
any |
The request body |
documents.deleteMetadata(documentId)Remove all metadata from a document.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
documents.patchMetadata(documentId)Edit metadata for a document with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
documents.audit(documentId, [startTime], [endTime], [opTypes], [kinds])Get audit log for a document. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
startTime optional |
string |
Start of time range |
endTime optional |
string |
End of time range |
opTypes optional |
string[]|string |
Only return operations of these types, spelled as in an entry's op/type (e.g. ['span-layer/create', 'span-layer/delete']). An entry appears when one of its operations matches, carrying only the ones that did. |
kinds optional |
string[]|string |
Only the entries of operations of these kinds (e.g. ['review', 'guess-adoption']), each whole |
documents.auditPage(documentId, [opts], [opts.order], [opts.limit], [opts.cursor])One page of the same log, newest-first with order: "desc". Use this rather than audit() wherever the caller wants the recent end of a log that may be long: audit() walks every page before it resolves.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
opts optional |
object |
|
opts.order optional |
"asc"|"desc" |
"desc" pages newest-first |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
documents.restore(documentId, asOf, [options], [auditMessage])Restore a document to its state at an earlier time, as one operation: what was deleted since then comes back under its original id, what was added since is removed, and what changed is set back, across every layer. A layer deleted since then, or a vocabulary entry that no longer exists, is skipped and reported under skipped. Resolves to a summary of the changes. Maintainers only.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
asOf |
string |
The moment to go back to (ISO-8601 instant), typically a history entry's endTime |
options optional |
object |
{ dryRun }: with dryRun true nothing is written and the summary says what would change |
auditMessage optional |
string |
Custom audit message for this operation |
documents.get(documentId, [includeBody], [asOf], [layers])Get a document. Set includeBody to true to include all data.
layers narrows a body read to the layers you name (ids of any kind: text, token, span, or relation). A layer comes back when it is named or is an ancestor of a named layer, and carries its own texts/tokens/spans/relations/vocabs only when it is itself named — so name the text layer too if you also want the text body. An id that is not a layer of this document's project is an error, not a quietly smaller response. Requires includeBody.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
includeBody optional |
boolean |
Include document body data |
asOf optional |
string |
Temporal query timestamp |
layers optional |
string[]|string |
Layer ids to restrict a body read to |
documents.delete(documentId)Delete a document and all data contained.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
documents.update(documentId, name)Update a document's name.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document ID |
name |
string |
The name |
documents.create(projectId, name, [metadata])Create a new document in a project.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project ID |
name |
string |
The name |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
documents.copy(documentId, name, [options], [auditMessage])Copy a document and everything in it into a new document of the same project, as one operation. The copy shares the source's layers and the vocabulary entries its links name, and holds the source's texts, tokens, spans, relations and vocab links under fresh ids, with their metadata. Comments do not travel. The media file does, unless includeMedia is false. Resolves to { id }, plus mediaError when the source had media the copy could not take with it.
| Parameter | Type | Description |
|---|---|---|
documentId |
string |
The document to copy |
name |
string |
The new document's name |
options optional |
object |
{ includeMedia }: false leaves the media file behind |
auditMessage optional |
string |
Custom audit message for this operation |
Events
events.record(type, fields, fields.projectId, [fields.documentId], [fields.targetId], [fields.data])Record one research-telemetry event (core manual, "Research telemetry"). Fire and forget: the event is buffered and sent with others every ten seconds, at fifty events, and when the page is hidden. A batch that fails is dropped, nothing is retried, and nothing is ever thrown. Sends nothing for a project whose switch (config.plaid.research.telemetry) is off, which it reads from the project itself. suggestion.shown is recorded once per target, field and value in a page session.
| Parameter | Type | Description |
|---|---|---|
type |
'suggestion.shown'|'suggestion.adopted'|'suggestion.dismissed'|'plan.opened' |
The event type |
fields |
object |
|
fields.projectId |
string |
The project it happened in |
fields.documentId optional |
string |
The document it happened in |
fields.targetId optional |
string |
What it is about: a token, a plan |
fields.data optional |
object |
Its details, keyed by single lowercase words (value, source, field, written, conversation) |
events.flush()Send what the recorder holds now, rather than at its next interval.
events.setEnabled(projectId, on)Tell the recorder a project's switch at once, e.g. right after a maintainer changed it. Turning it off drops what is buffered.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project |
on |
boolean |
Whether telemetry is on |
events.create(projectId)Record events directly, with no buffering. Requires write access, and the server refuses with a 403 while the project's switch is off. All or nothing: one bad event refuses the request with a 400 naming it.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project the events happened in |
events.list(projectId, [filters], [filters.types], [filters.startTime], [filters.endTime])A project's events in arrival order. Maintainer or admin only. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
filters optional |
object |
|
filters.types optional |
string|string[] |
Only these types |
filters.startTime optional |
string |
Only events the server stamped at or after this instant |
filters.endTime optional |
string |
Only events the server stamped at or before this instant |
events.listPage(projectId, [opts], [opts.limit], [opts.cursor], [opts.types], [opts.startTime], [opts.endTime])One page of a project's events. Maintainer or admin only.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
opts.types optional |
string|string[] |
Only these types |
opts.startTime optional |
string |
Only events at or after this instant |
opts.endTime optional |
string |
Only events at or before this instant |
events.iterPages(projectId, [opts], [opts.pageSize], [opts.types], [opts.startTime], [opts.endTime])Async-iterate a project's events page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
opts.types optional |
string|string[] |
Only these types |
opts.startTime optional |
string |
Only events at or after this instant |
opts.endTime optional |
string |
Only events at or before this instant |
Guidelines
guidelines.create(projectId, title, [opts], [opts.body], [opts.pinned])Create a guideline in a project. title is the handle an assistant asks for one by, and the only thing a person has to keep current. It is NOT required to be unique, so a title already in use is written like any other: warn about it, do not refuse it. A pinned guideline is one the assistant is given in full on every turn.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project the guideline belongs to |
title |
string |
The handle an assistant asks for one by (1..100 characters) |
opts optional |
object |
|
opts.body optional |
string |
The Markdown text (up to 20000 characters; may be empty) |
opts.pinned optional |
boolean |
Send this one to the assistant in full on every turn |
guidelines.get(id)Read one guideline, Markdown body included.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The guideline id |
guidelines.update(id, [changes], [changes.title], [changes.body], [changes.pinned], [changes.expectedUpdatedAt])Update a guideline. Every field is optional and an omitted one is left alone, so an edit to the body need not restate the title.
Pass expectedUpdatedAt (the updatedAt you last read) when a person has been editing prose: the write then fails with 409 rather than overwriting somebody who saved in between. Leave it off for a pin toggle or a script, which have nothing of anyone's to lose.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The guideline id |
changes optional |
object |
|
changes.title optional |
string |
The new handle |
changes.body optional |
string |
The new Markdown text |
changes.pinned optional |
boolean |
Whether the assistant always gets it in full |
changes.expectedUpdatedAt optional |
string |
Write only if this is still the stored updatedAt |
guidelines.delete(id)Delete a guideline.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The guideline id |
guidelines.list(projectId, [opts], [opts.includeBodies])List a project's guidelines, by title. Transparently follows pagination cursors and returns the full flat array.
Without includeBodies each entry carries bodyChars, the length of its body, so a caller can budget before fetching any. Pinned guidelines are NOT sorted first: pinned is on every entry and grouping is the caller's.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
opts optional |
object |
|
opts.includeBodies optional |
boolean |
Return each body instead of its length |
guidelines.listPage(projectId, [opts], [opts.limit], [opts.cursor], [opts.includeBodies])Fetch a single page of a project's guidelines.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
opts.includeBodies optional |
boolean |
Return each body instead of its length |
guidelines.iterPages(projectId, [opts], [opts.pageSize], [opts.includeBodies])Async-iterate a project's guidelines page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project to read |
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
opts.includeBodies optional |
boolean |
Return each body instead of its length |
Invites
invites.list([opts], [opts.projectId], [opts.all])List invites you minted, oldest first. With projectId, lists that project's invites instead (including co-maintainers'), which requires maintainer or admin on it. Never includes invite codes — the code is returned once, by create(), and is not recoverable afterward. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.projectId optional |
string |
List this project's invites instead of your own |
opts.all optional |
boolean |
List every invite on the server (admin only) |
invites.listPage([opts], [opts.projectId], [opts.all], [opts.limit], [opts.cursor])Fetch a single page of invites.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.projectId optional |
string |
List this project's invites instead of your own |
opts.all optional |
boolean |
List every invite on the server (admin only) |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
invites.iterPages([opts], [opts.projectId], [opts.all], [opts.pageSize])Async-iterate invites page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.projectId optional |
string |
List this project's invites instead of your own |
opts.all optional |
boolean |
List every invite on the server (admin only) |
opts.pageSize optional |
number |
Per-request page size |
invites.create([opts], [opts.projectId], [opts.projectRole], [opts.grantAdmin], [opts.targetUserId], [opts.maxUses], [opts.ttlDays], [opts.note])Mint an invite. The returned code is shown ONLY here — it is never stored and cannot be recovered, so build and hand off the link now. Use PlaidClient.inviteUrl() to turn it into one.
Admins may mint anything. A project maintainer may mint role grants on projects they maintain, and nothing else: no admin grant, no grantless invite, no password resets — so for a non-admin, projectId/projectRole are required in practice (403 without them).
EVERY option is optional; create() with no arguments mints a single-use signup link granting nothing but an account. Two pairing rules the server enforces with a 400: projectId and projectRole must be given TOGETHER, and targetUserId may not be combined with projectId, grantAdmin, or a maxUses above 1.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.projectId optional |
string |
Project the redeemer joins (requires projectRole) |
opts.projectRole optional |
string |
"reader" | "writer" | "maintainer" (requires projectId) |
opts.grantAdmin optional |
boolean |
Make the new account a global admin (admin only) |
opts.targetUserId optional |
string |
Password reset for that user instead of a signup; admin only, single-use, grants nothing |
opts.maxUses optional |
number |
How many accounts this link may create (default 1) |
opts.ttlDays optional |
number |
Days until it expires (default 14, max 365) |
opts.note optional |
string |
Human label shown in your invite list |
invites.revoke(id)Revoke an invite, killing the link immediately. Idempotent. Allowed for the creator, an admin, or a maintainer of the invite's project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The invite ID |
Messages
messages.listen(projectId, onEvent, [path])Open a Server-Sent Events stream for a project.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project to listen to |
onEvent |
function |
Callback function that receives (eventType, data). If it returns true, listening will stop. |
path optional |
string |
Stream path under baseUrl (defaults to the project /listen bus; service channels pass their own). |
messages.sendMessage(projectId, data)Send a message to project listeners.
data may be any JSON value and is sent VERBATIM (rawBody): a message payload is opaque application data, like metadata and config, so its keys must not be re-cased on the way out. Without this a key such as case-marker would reach listeners as caseMarker in JS and case_marker in Python. listen restores it verbatim on the way in.
A message is not saved and writes nothing to History, so it never joins an open operation (withOperation). Made on a batch it still queues, so a message queued after the writes goes out after them.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project to send to |
data |
any |
The message data to send |
messages.discoverServices(projectId)Discover the services seen on a project (synchronous GET). Currently connected services carry online: true; previously-seen offline ones carry online: false plus a lastSeenAt stamp.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project to query |
messages.discardService(projectId, serviceId)Forget a previously-seen (offline) service. Maintainer-only; 409 if the service is currently connected.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project |
serviceId |
string |
The ID of the service to forget |
messages.serve(projectId, serviceInfo, onServiceRequest, [extras], [onStatus])Register as a service and handle incoming work requests.
The registration reopens its channel whenever it drops, so a server restart needs no service restart.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project to serve |
serviceInfo |
Object |
Service information {serviceId, serviceName, description} |
onServiceRequest |
function |
Callback (data, responseHelper) |
extras optional |
Object |
Optional additional service metadata |
onStatus optional |
function |
Optional callback (event, projectId, detail) for connection transitions: 'registered', 'reconnected', 'disconnected', and 'stopped' when the server refuses the channel for good, which ends the registration |
messages.requestService(projectId, serviceId, data, [timeout], [onProgress], [signal], [opts])Request a service to perform work and await its result.
The request outlives this call: after a timeout, an abort, or a dropped connection the service goes on, and attachServiceRequest collects the result given the request id, which opts.onAccepted receives as soon as the server has taken the request. Pass opts.requestId (a UUID you mint) to know the id before submitting; submitting an id that names a request you already made rejoins it instead of starting another.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project |
serviceId |
string |
The ID of the service to request |
data |
any |
The request data |
timeout optional |
number |
=10000] - Timeout in ms |
onProgress optional |
function |
Called with each progress payload {percent, message} |
signal optional |
AbortSignal |
Abort to stop waiting; rejects with an AbortError |
opts optional |
Object |
{requestId, onAccepted, projectIds, noOperation} |
messages.attachServiceRequest(projectId, requestId, [timeout], [onProgress], [signal])Rejoin a service request made earlier and await its result: the latest progress is replayed, then the result comes, or at once if the request already finished. Only the user who submitted it (or an admin). Rejects with an error whose status is 404 when the request is unknown or expired (a finished request's result is kept for a while, not forever).
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project |
requestId |
string |
The request id |
timeout optional |
number |
=10000] - Timeout in ms |
onProgress optional |
function |
Called with each progress payload |
signal optional |
AbortSignal |
Abort to stop waiting |
messages.cancelServiceRequest(projectId, requestId)Ask the service to stop a request made earlier. The request still ends with whatever the service then reports, on the stream of whoever is awaiting it. Rejects with 404 if unknown or expired, 409 once finished.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The UUID of the project |
requestId |
string |
The request id |
Operation Groups
operationGroups.get(id)Get a logical-operation group (its label + creator).
| Parameter | Type | Description |
|---|---|---|
id |
string |
The group id |
operationGroups.update(id, message)Relabel a logical-operation group after the fact. Owner or admin only.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The group id |
message |
string|null |
The new label |
Projects
projects.addWriter(id, userId)Set a user's access level to read and write for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.removeWriter(id, userId)Remove a user's writer privileges for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.addReader(id, userId)Set a user's access level to read-only for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.removeReader(id, userId)Remove a user's reader privileges for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.setConfig(id, namespace, configKey, configValue, [auditMessage])Set a configuration value for a project in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
projects.deleteConfig(id, namespace, configKey, [auditMessage])Remove a configuration value for a project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
projects.addMaintainer(id, userId)Assign a user as a maintainer for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.removeMaintainer(id, userId)Remove a user's maintainer privileges for this project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
projects.audit(projectId, [startTime], [endTime], [opTypes], [kinds])Get audit log for a project. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project ID |
startTime optional |
string |
Start of time range |
endTime optional |
string |
End of time range |
opTypes optional |
string[]|string |
Only return operations of these types, spelled as in an entry's op/type (e.g. ['span-layer/create', 'span-layer/delete']). An entry appears when one of its operations matches, carrying only the ones that did. |
kinds optional |
string[]|string |
Only the entries of operations of these kinds (e.g. ['review', 'guess-adoption']), each whole |
projects.auditPage(projectId, [opts], [opts.order], [opts.limit], [opts.cursor])One page of the same log, newest-first with order: "desc". Use this rather than audit() wherever the caller wants the recent end of a log that may be long: audit() walks every page before it resolves.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project ID |
opts optional |
object |
|
opts.order optional |
"asc"|"desc" |
"desc" pages newest-first |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
projects.myLastEdits(projectId)When the calling user last wrote to each document in a project, as a {documentId: timestamp} map. Documents they have never written to are absent. One request covers a whole document list.
The response is NOT key-transformed: its keys are document ids, and camelCasing would mangle the hyphens in a UUID.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project ID |
projects.linkVocab(id, vocabId)Link a vocabulary to a project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabId |
string |
The vocab layer ID |
projects.unlinkVocab(id, vocabId)Unlink a vocabulary from a project.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabId |
string |
The vocab layer ID |
projects.get(id)Get a project by ID. To fetch the project's documents, use listDocuments(id) — the include-documents flag has been removed.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
projects.listDocuments(id)List all documents in a project. Transparently follows pagination cursors and returns the full flat array.
Note: this endpoint does not support temporal (as-of) queries; the server rejects ?as-of= on the documents-list route with a 400.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The project ID |
projects.listDocumentsPage(id, [opts], [opts.limit], [opts.cursor])Fetch a single page of a project's documents.
Note: this endpoint does not support temporal (as-of) queries; the server rejects ?as-of= on the documents-list route with a 400.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The project ID |
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
projects.iterDocuments(id, [opts], [opts.pageSize])Async-iterate a project's documents page by page; yields each page's entries array.
Note: this endpoint does not support temporal (as-of) queries; the server rejects ?as-of= on the documents-list route with a 400.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The project ID |
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
projects.delete(id, [auditMessage], [options], [options.timeout])Delete a project and everything in it. This is irrecoverable. The project is gone when this returns, and what it holds is removed on the server afterwards.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
auditMessage optional |
string |
Custom audit-log message |
options optional |
object |
|
options.timeout optional |
number |
Per-request timeout in ms, the client's own by default (0/null disables) |
projects.update(id, name)Update a project's name.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
name |
string |
The name |
projects.list()List all projects accessible to user. Transparently follows pagination cursors and returns the full flat array.
projects.listPage([opts], [opts.limit], [opts.cursor])Fetch a single page of projects.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
projects.iterPages([opts], [opts.pageSize])Async-iterate projects page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
projects.create(name)Create a new project. Note: this also registers the user as a maintainer.
| Parameter | Type | Description |
|---|---|---|
name |
string |
The name |
Relation Layers
relationLayers.shift(relationLayerId, direction)Shift a relation layer's display order.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
direction |
string |
The direction ("up" or "down") |
relationLayers.create(spanLayerId, name)Create a new relation layer.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
name |
string |
The name |
relationLayers.setConfig(relationLayerId, namespace, configKey, configValue, [auditMessage])Set a configuration value for a layer in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
relationLayers.deleteConfig(relationLayerId, namespace, configKey, [auditMessage])Remove a configuration value for a layer.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
relationLayers.get(relationLayerId)Get a relation layer by ID.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
relationLayers.delete(relationLayerId)Delete a relation layer.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
relationLayers.update(relationLayerId, name)Update a relation layer's name.
| Parameter | Type | Description |
|---|---|---|
relationLayerId |
string |
The relation layer ID |
name |
string |
The name |
Relations
relations.setMetadata(relationId, body)Replace all metadata for a relation.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
body |
any |
The request body |
relations.deleteMetadata(relationId)Remove all metadata from a relation.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
relations.patchMetadata(relationId)Edit metadata for a relation with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
relations.setTarget(relationId, spanId)Update the target span of a relation.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
spanId |
string |
The span ID |
relations.get(relationId)Get a relation by ID.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
relations.delete(relationId)Delete a relation.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
relations.update(relationId, value)Update a relation's value.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
value |
any |
The value |
relations.setSource(relationId, spanId)Update the source span of a relation.
| Parameter | Type | Description |
|---|---|---|
relationId |
string |
The relation ID |
spanId |
string |
The span ID |
relations.create(layerId, sourceId, targetId, value, [metadata])Create a new relation. A relation is a directed edge between two spans with a value, useful for expressing phenomena such as syntactic or semantic relations.
| Parameter | Type | Description |
|---|---|---|
layerId |
string |
The relation layer ID |
sourceId |
string |
The source span ID |
targetId |
string |
The target span ID |
value |
any |
The value |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
relations.bulkCreate(body)Create multiple relations in a single operation.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
relations.bulkDelete(body)Delete multiple relations in a single operation. Provide an array of IDs.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
relations.bulkUpdate(body)Update many relations in a single operation: set values and/or patch metadata.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
Objects of the shape {id, value?, metadata?}. value is set only when the key is present (null sends JSON null); metadata is a list of metadata ops, as for patchMetadata. The relations may lie in several documents of one project; every document touched has its version bumped and every new version comes back in X-Document-Versions (past fifty documents, only their number, in X-Document-Versions-Omitted). A document-version precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update. |
Server
server.info()This server's version and the limits it enforces, e.g. { limits: { mediaFileBytes, jsonBodyBytes, batchOperations, … } }. Sizes are in bytes. Unauthenticated.
server.health()Liveness, version and database size. Unauthenticated, and served outside the REST router at /health, so it answers even while the API is refusing requests. Never cached — the point is that it is current.
Span Layers
spanLayers.setConfig(spanLayerId, namespace, configKey, configValue, [auditMessage])Set a configuration value for a layer in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
spanLayers.deleteConfig(spanLayerId, namespace, configKey, [auditMessage])Remove a configuration value for a layer.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
spanLayers.get(spanLayerId)Get a span layer by ID.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
spanLayers.delete(spanLayerId)Delete a span layer.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
spanLayers.update(spanLayerId, name)Update a span layer's name.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
name |
string |
The name |
spanLayers.create(tokenLayerId, name)Create a new span layer.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
name |
string |
The name |
spanLayers.shift(spanLayerId, direction)Shift a span layer's display order.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
direction |
string |
The direction ("up" or "down") |
Spans
spans.setTokens(spanId, tokens)Replace tokens for a span.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
tokens |
Array |
The tokens |
spans.create(spanLayerId, tokens, value, [metadata])Create a new span. A span holds a primary atomic value and optional metadata, and must at all times be associated with one or more tokens.
| Parameter | Type | Description |
|---|---|---|
spanLayerId |
string |
The span layer ID |
tokens |
Array |
The tokens |
value |
any |
The value |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
spans.get(spanId)Get a span by ID.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
spans.delete(spanId)Delete a span.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
spans.update(spanId, value)Update a span's value.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
value |
any |
The value |
spans.bulkCreate(body)Create multiple spans in a single operation.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
spans.bulkDelete(body)Delete multiple spans in a single operation. Provide an array of IDs.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
spans.bulkUpdate(body)Update many spans in a single operation: set values and/or patch metadata.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
Objects of the shape {id, value?, metadata?}. value is set only when the key is present (null sends JSON null); metadata is a list of metadata ops, as for patchMetadata. The spans may lie in several documents of one project; every document touched has its version bumped and every new version comes back in X-Document-Versions (past fifty documents, only their number, in X-Document-Versions-Omitted). A document-version precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update. |
spans.setMetadata(spanId, body)Replace all metadata for a span.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
body |
any |
The request body |
spans.deleteMetadata(spanId)Remove all metadata from a span.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
spans.patchMetadata(spanId)Edit metadata for a span with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
spanId |
string |
The span ID |
Text Layers
textLayers.setConfig(textLayerId, namespace, configKey, configValue, [auditMessage])Set a configuration value for a layer in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
textLayers.deleteConfig(textLayerId, namespace, configKey, [auditMessage])Remove a configuration value for a layer.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
textLayers.get(textLayerId)Get a text layer by ID.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
textLayers.delete(textLayerId)Delete a text layer.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
textLayers.update(textLayerId, name)Update a text layer's name.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
name |
string |
The name |
textLayers.shift(textLayerId, direction)Shift a text layer's display order within the project.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
direction |
string |
The direction ("up" or "down") |
textLayers.create(projectId, name)Create a new text layer for a project.
| Parameter | Type | Description |
|---|---|---|
projectId |
string |
The project ID |
name |
string |
The name |
Texts
texts.setMetadata(textId, body)Replace all metadata for a text.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
body |
any |
The request body |
texts.deleteMetadata(textId)Remove all metadata from a text.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
texts.patchMetadata(textId)Edit metadata for a text with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
texts.create(textLayerId, documentId, body, [metadata])Create a new text in a document's text layer. A text is a container for one long string in body for a given layer.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
documentId |
string |
The document ID |
body |
string |
The request body |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
texts.get(textId)Get a text.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
texts.delete(textId)Delete a text and all dependent data.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
texts.update(textId, body, [auditMessage])Update a text's body. A diff is computed and token indices are updated so that tokens remain intact. Alternatively, body can be a list of edit directives (code-point indices, applied in order): {type: 'delete', index, value: count} {type: 'insert', index, value: string} {type: 'replace', index, length, value: string} — unlike delete+insert, a token covering the whole range is resized to keep it (respell a word in place without losing its annotations). A list is applied exactly as sent. With base (the digest of the body the update was made on, as every read of a text gives it) the update applies only to that body, and is refused with 409 and text-changed otherwise. Strict mode then does not stamp it, unless versioned is true (a batch whose later writes are stamped needs its first write stamped too).
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
body |
any |
The request body |
auditMessage optional |
string |
texts.edit(textId, edits, [auditMessage])Change a text's body by the edits made at the caret: a list of edit directives as update takes them (code-point indices, applied in order, each index in the body the ones before it left). Only their net change counts. An insert or a delete stays where it was made, and a stretch deleted and typed over is read as a whole new body is. The answer is the text with its new digest and reshape (the tokens moved, the spans and vocab links trimmed, the rows deleted). With base, the digest of the body the edits were made on, the edit applies only to that body and is refused with 409 and text-changed otherwise, and strict mode does not stamp it unless versioned is true. See composeTextEdits and gapsToOps.
| Parameter | Type | Description |
|---|---|---|
textId |
string |
The text ID |
edits |
Array |
The edit directives |
auditMessage optional |
string |
Token Layers
tokenLayers.shift(tokenLayerId, direction)Shift a token layer's display order.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
direction |
string |
The direction ("up" or "down") |
tokenLayers.create(textLayerId, name, [overlapMode], [parentTokenLayerId])Create a new token layer.
| Parameter | Type | Description |
|---|---|---|
textLayerId |
string |
The text layer ID |
name |
string |
The name |
overlapMode optional |
string |
Per-layer, immutable token invariant: "any" (default), "non-overlapping", or "partitioning". On partitioning layers, single token create/update/delete are rejected; use bulkCreate plus split/merge/shift. |
parentTokenLayerId optional |
string |
Optional immutable parent token layer. Tokens in this layer must nest within a parent-layer token; the parent layer must be in the same text layer and be "non-overlapping" or "partitioning" (an "any" parent is rejected). A nested layer may be "any" or "non-overlapping" but not "partitioning" (partitioning is only for root layers), e.g. words (non-overlapping, parent=sentences) within sentences (partitioning). |
tokenLayers.setConfig(tokenLayerId, namespace, configKey, configValue, [auditMessage])Set a configuration value for a layer in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
tokenLayers.deleteConfig(tokenLayerId, namespace, configKey, [auditMessage])Remove a configuration value for a layer.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
tokenLayers.get(tokenLayerId)Get a token layer by ID.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
tokenLayers.delete(tokenLayerId)Delete a token layer.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
tokenLayers.update(tokenLayerId, name)Update a token layer's name.
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
name |
string |
The name |
Tokens
tokens.create(tokenLayerId, text, begin, end, [precedence], [metadata])Create a new token in a token layer. Tokens define text substrings using begin and end offsets. Tokens may be zero-width and may overlap. For tokens sharing the same begin, precedence controls the linear ordering.
Offsets are 0-based indices in Unicode CODE POINTS (not UTF-16 code units): a supplementary-plane character (emoji, SMP script) is one position. JS strings are UTF-16, so do NOT use str.length / str.substring to compute offsets — count code points instead (e.g. [...str].length, or iterate with codePointAt).
| Parameter | Type | Description |
|---|---|---|
tokenLayerId |
string |
The token layer ID |
text |
string |
The text ID |
begin |
number |
Start offset, inclusive (Unicode code points) |
end |
number |
End offset, exclusive (Unicode code points) |
precedence optional |
number |
Ordering precedence |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
tokens.get(tokenId)Get a token.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
tokens.delete(tokenId)Delete a token and remove it from any spans. If this causes a span to have no remaining tokens, the span will also be deleted.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
tokens.update(tokenId, [begin], [end], [precedence])Update a token.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
begin optional |
number |
New start offset, inclusive (Unicode code points) |
end optional |
number |
New end offset, exclusive (Unicode code points) |
precedence optional |
?number |
Ordering precedence. Omit (undefined) to leave unchanged; pass a number to set; pass null explicitly to CLEAR it (revert to no explicit ordering). bodyOf keeps null but drops undefined, so the three cases map correctly to the server. |
tokens.bulkCreate(body)Create multiple tokens in a single operation.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
tokens.bulkDelete(body)Delete multiple tokens in a single operation. Provide an array of IDs.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
The request body |
tokens.bulkUpdate(body)Update many tokens in a single operation: patch metadata.
| Parameter | Type | Description |
|---|---|---|
body |
Array |
Objects of the shape {id, metadata}. metadata is a list of metadata ops, as for patchMetadata. The tokens may lie in several documents of one project; every document touched has its version bumped and every new version comes back in X-Document-Versions (past fifty documents, only their number, in X-Document-Versions-Omitted). A document-version precondition is accepted only when every entry lies in one document. An unknown id refuses the whole update. |
tokens.split(tokenId, position, [auditMessage])Split a token at a Unicode code-point offset. The original token becomes the left half (keeps its ID, spans, vocab-links); the new right token's ID is returned.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
position |
number |
Code-point offset to split at (strictly between begin and end) |
auditMessage optional |
string |
Audit message for this write |
tokens.merge(tokenId, otherTokenId)Merge two tokens. The left token (smaller begin) survives with the combined extent; the right is deleted and its spans/vocab-links are reparented to the left. On partitioning layers the tokens must be adjacent; on non-overlapping layers the merged extent must not engulf a third token.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The anchor token ID |
otherTokenId |
string |
The other token to merge in |
tokens.shift(tokenId, [begin], [end])Shift a token's boundary. On partitioning layers the adjacent token is auto-adjusted to preserve the partition; on non-overlapping layers a shift that would create an overlap is rejected.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
begin optional |
number |
New start offset, inclusive (Unicode code points) |
end optional |
number |
New end offset, exclusive (Unicode code points) |
tokens.setMetadata(tokenId, body)Replace all metadata for a token.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
body |
any |
The request body |
tokens.deleteMetadata(tokenId)Remove all metadata from a token.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
tokens.patchMetadata(tokenId)Edit metadata for a token with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
tokenId |
string |
The token ID |
User Data
userData.list(userId, [opts], [opts.prefix], [opts.pattern], [opts.includeValues], [opts.pageSize])List a user's private data entries ({key, updatedAt}, plus value when includeValues), ordered by key. Owner or admin only. Transparently follows pagination cursors and returns the full flat array.
Narrow with prefix (the literal head of a key) and/or pattern, a GLOB over the whole key (* any run, ? one character) — the way to ask for a key convention identified by a segment in the middle, e.g. igt:assistant:*:meta:* for every conversation's sidebar entry across every project without dragging down the transcripts beside them.
pageSize is exposed here, and defaults lower than elsewhere, because one value runs to 1 MB: a page of them with includeValues is the largest response this API can be asked for. Raise it when the listing is keys, or the values are known to be small.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
opts optional |
object |
|
opts.prefix optional |
string |
Only keys starting with this prefix |
opts.pattern optional |
string |
Only keys matching this GLOB |
opts.includeValues optional |
boolean |
Also return each entry's value |
opts.pageSize optional |
number |
=100] - Entries per request (1..1000) |
userData.listPage(userId, [opts], [opts.prefix], [opts.pattern], [opts.includeValues], [opts.limit], [opts.cursor])One page of a user's private data entries, ordered by key.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
opts optional |
object |
|
opts.prefix optional |
string |
Only keys starting with this prefix |
opts.pattern optional |
string |
Only keys matching this GLOB |
opts.includeValues optional |
boolean |
Also return each entry's value |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
userData.iterPages(userId, [opts], [opts.prefix], [opts.pattern], [opts.includeValues], [opts.pageSize])Async generator over a user's private data entries, one page at a time.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
opts optional |
object |
|
opts.prefix optional |
string |
Only keys starting with this prefix |
opts.pattern optional |
string |
Only keys matching this GLOB |
opts.includeValues optional |
boolean |
Also return each entry's value |
opts.pageSize optional |
number |
=100] - Entries per request (1..1000) |
userData.get(userId, key)Read one private data entry ({key, updatedAt, value}); 404 if absent.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
key |
string |
userData.put(userId, key, value)Create or replace one private data entry. value is any JSON (up to 1 MB). Not audited, not batchable.
The server stores it verbatim, but this client recases object keys on the way out and back like any other body (myKey <-> my-key), so a value whose keys are camelCase round-trips unchanged while one keyed by arbitrary strings does not. Put such a map under a metadata key, which both clients pass through untouched.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
key |
string |
|
value |
* |
userData.delete(userId, key)Delete one private data entry; 404 if absent.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
|
key |
string |
Users
users.list([opts], [opts.q])List (or search) users. Transparently follows pagination cursors and returns the full flat array. Admin-or-maintainer only.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.q optional |
string |
Filter to users whose display name or email contains this text (case-insensitive) |
users.listPage([opts], [opts.q], [opts.limit], [opts.cursor])Fetch a single page of users (optionally filtered by q).
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.q optional |
string |
Filter to users whose display name or email contains this text (case-insensitive) |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
users.iterPages([opts], [opts.q], [opts.pageSize])Async-iterate users page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.q optional |
string |
Filter to users whose display name or email contains this text (case-insensitive) |
opts.pageSize optional |
number |
Per-request page size |
users.create(email, password, isAdmin, [displayName])Create a new user.
| Parameter | Type | Description |
|---|---|---|
email |
string |
The account's email address. It becomes the user's id and is what they log in with; it can never be changed. |
password |
string |
The password |
isAdmin |
boolean |
Whether the user is an admin |
displayName optional |
string |
How the user is shown in the UI. Defaults to the local part of the email. |
users.audit(userId, [startTime], [endTime], [opTypes], [kinds])Get audit log for a user's actions. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID |
startTime optional |
string |
Start of time range |
endTime optional |
string |
End of time range |
opTypes optional |
string[]|string |
Only return operations of these types, spelled as in an entry's op/type (e.g. ['span-layer/create', 'span-layer/delete']). An entry appears when one of its operations matches, carrying only the ones that did. |
kinds optional |
string[]|string |
Only the entries of operations of these kinds (e.g. ['review', 'guess-adoption']), each whole |
users.auditPage(userId, [opts], [opts.order], [opts.limit], [opts.cursor])One page of the same log, newest-first with order: "desc". Use this rather than audit() wherever the caller wants the recent end of a log that may be long: audit() walks every page before it resolves.
| Parameter | Type | Description |
|---|---|---|
userId |
string |
The user ID |
opts optional |
object |
|
opts.order optional |
"asc"|"desc" |
"desc" pages newest-first |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
users.get(id)Get a user by ID
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
users.delete(id)Deactivate a user. Users are never hard-deleted: deactivation rejects their logins and tokens, strips their project memberships and vocab maintainerships, and revokes their API tokens. The user stays visible in listings with a deactivated-at timestamp. Reversible via activate(), which restores login only.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
users.activate(id)Reactivate a deactivated user, restoring their ability to log in. Project memberships, vocab maintainerships, and API tokens removed at deactivation are NOT restored — re-grant them deliberately.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
users.update(id, [password], [displayName], [isAdmin])Modify a user. Admins may change the display name, password, and admin status of any user. All other users may only modify their own display name or password.
A user's id is their email address and is fixed for the life of the account — it is what they log in with, so nothing can change it.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID (the user's email address) |
password optional |
string |
New password |
displayName optional |
string |
New display name |
isAdmin optional |
boolean |
New admin status |
users.avatarUrl(id, [avatarHash])Build a URL for a user's profile picture, suitable for use directly as an <img> src. An image element cannot send an Authorization header, so the session token rides in the query string, the same way document media does.
Pass the user record's avatarHash as the second argument whenever you have it: the URL then addresses that exact picture, so the browser can cache it indefinitely and still pick up a replacement the moment the user changes it. Returns null when the user has no picture, so callers can fall back to initials without a wasted request.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The user ID |
avatarHash optional |
string |
The user record's avatarHash |
users.getAvatar(id)Fetch a user's profile picture as raw bytes. Most callers want avatarUrl() instead. This one is for non-browser consumers.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The user ID |
users.setAvatar(id, file)Upload a profile picture. Your own, or anyone's if you are an admin. The server center-crops to a square, scales to the configured edge length, and re-encodes, so no client-side resizing is needed. Accepts PNG, JPEG, WebP, and GIF. Resolves to the updated user record.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The user ID |
file |
File |
The image to upload |
Vocab Items
vocabItems.setMetadata(id, body)Replace all metadata for a vocab item.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
body |
any |
The request body |
vocabItems.deleteMetadata(id)Remove all metadata from a vocab item.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabItems.patchMetadata(id)Edit metadata for a vocab item with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabItems.create(vocabLayerId, form, [metadata])Create a new vocab item
| Parameter | Type | Description |
|---|---|---|
vocabLayerId |
string |
The vocab layer ID |
form |
string |
The vocab item form |
metadata optional |
any |
Metadata map. Omit to leave unset; pass null to send JSON null. |
vocabItems.bulkCreate()Create multiple vocab items in a single operation. Entries may target different vocab layers; the user must have write access to each.
vocabItems.bulkUpdate()Update many vocab items in a single operation: set forms and/or patch metadata.
vocabItems.bulkDelete(body)Delete multiple vocab items in a single operation. Each item's descendant vocab links are deleted too. Provide an array of IDs.
Every document holding one of those links has its version bumped, and a strict-mode client picks up their new versions from the response.
| Parameter | Type | Description |
|---|---|---|
body |
string[] |
The vocab item IDs to delete |
vocabItems.get(id)Get a vocab item by ID
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabItems.delete(id, [auditMessage])Delete a vocab item, and every link to it. Every document holding one of those links has its version bumped, and a strict-mode client picks up their new versions from the response.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
auditMessage optional |
string |
Audit message for this write |
vocabItems.merge(survivorId, loserIds)Merge entries into this one, in one operation. Every link to a loser moves to the survivor (keeping its id and metadata), except a link on words the survivor is already linked to, which is deleted, and then the losers are deleted. Links are read when the merge runs, so one made after the caller looked moves too. Every loser must be in the survivor's vocabulary, and a loser that is already gone is skipped, so a repeated merge changes nothing. References to a loser in other entries' metadata are the caller's to rewrite, in the same batch. Needs maintainer rights on the vocabulary.
| Parameter | Type | Description |
|---|---|---|
survivorId |
string |
The entry that stays |
loserIds |
string[] |
The entries merged into it |
vocabItems.update(id, form)Update a vocab item's form. A document read carries the entry's form on every link to it, so a rename restates those documents: each has its version bumped, and a strict-mode client picks up their new versions from the response.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
form |
string |
The vocab item form |
Vocab Layers
vocabLayers.get(id, [includeItems], [asOf])Get a vocab layer by ID. With asOf, the vocabulary as it was at that instant, read from its history: the same shape as the live read, its entries with includeItems. A vocabulary that did not exist then is a 404, a malformed or pruned time a 400.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
includeItems optional |
boolean |
Include vocab items |
asOf optional |
string |
The moment to read at (ISO-8601 instant) |
vocabLayers.getItemAt(id, itemId, asOf)One entry of the vocabulary as it was at asOf, also when it has been deleted since. The same shape as vocabItems.get. A 404 when the entry was not in this vocabulary at that time.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The vocabulary ID |
itemId |
string |
The entry ID |
asOf |
string |
The moment to read at (ISO-8601 instant) |
vocabLayers.audit(id, [startTime], [endTime], [opTypes], [itemId], [kinds])Get the audit log of a vocabulary: every change to it or to its entries, folded into entries as the document log is. Links are not listed, they belong to the document they annotate. Transparently follows pagination cursors and returns the full flat array.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The vocabulary ID |
startTime optional |
string |
Start of time range |
endTime optional |
string |
End of time range |
opTypes optional |
string[]|string |
Only return operations of these types, spelled as in an entry's op/type (e.g. ['vocab-item/delete', 'vocab-item/restore']) |
itemId optional |
string |
Only the changes that wrote this one entry, each with only its operations that did |
kinds optional |
string[]|string |
Only the entries of operations of these kinds (e.g. ['review', 'guess-adoption']), each whole |
vocabLayers.auditPage(id, [opts], [opts.order], [opts.limit], [opts.cursor], [opts.itemId])One page of the same log, newest-first with order: "desc". Use this rather than audit() wherever the caller wants the recent end of a log that may be long: audit() walks every page before it resolves.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The vocabulary ID |
opts optional |
object |
|
opts.order optional |
"asc"|"desc" |
"desc" pages newest-first |
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
opts.itemId optional |
string |
Only the changes that wrote this one entry, each with only its operations that did |
vocabLayers.restoreItem(id, itemId, asOf, [options], [auditMessage])Put one entry of the vocabulary back as it was at asOf, as one operation. A deleted entry comes back under its original id with its form and fields, and a living one has its form and fields set back. Links are not part of an entry: a deleted entry's links come back through each document's own documents.restore. Resolves to {inserted, form, metadata, total}, total 0 when nothing changes. A form set back bumps every linking document, and the new versions come back in X-Document-Versions (or, past fifty documents, X-Document-Versions-Omitted), which the client takes up as on any write. A time when the entry did not exist is a 400. Maintainers of the vocabulary only.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The vocabulary ID |
itemId |
string |
The entry ID |
asOf |
string |
The moment to go back to (ISO-8601 instant), typically a history entry's endTime |
options optional |
object |
{ dryRun }: with dryRun true nothing is written and the summary says what would change |
auditMessage optional |
string |
Custom audit message for this operation |
vocabLayers.delete(id)Delete a vocab layer.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabLayers.update(id, name)Update a vocab layer's name.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
name |
string |
The name |
vocabLayers.setConfig(id, namespace, configKey, configValue, [auditMessage])Set a configuration value for a layer in an editor namespace.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
configValue |
any |
Configuration value to set |
auditMessage optional |
string |
Audit message for this write |
vocabLayers.deleteConfig(id, namespace, configKey, [auditMessage])Remove a configuration value for a layer.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
namespace |
string |
The config namespace |
configKey |
string |
The config key |
auditMessage optional |
string |
Audit message for this write |
vocabLayers.list()List all vocab layers accessible to user. Transparently follows pagination cursors and returns the full flat array.
vocabLayers.listPage([opts], [opts.limit], [opts.cursor])Fetch a single page of vocab layers.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.limit optional |
number |
Page size (1..1000; server default 100) |
opts.cursor optional |
string |
Opaque cursor from a previous page |
vocabLayers.iterPages([opts], [opts.pageSize])Async-iterate vocab layers page by page; yields each page's entries array.
| Parameter | Type | Description |
|---|---|---|
opts optional |
object |
|
opts.pageSize optional |
number |
Per-request page size |
vocabLayers.create(name)Create a new vocab layer. Note: this also registers the user as a maintainer.
| Parameter | Type | Description |
|---|---|---|
name |
string |
The name |
vocabLayers.addMaintainer(id, userId)Assign a user as a maintainer for this vocab layer.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
vocabLayers.removeMaintainer(id, userId)Remove a user's maintainer privileges for this vocab layer.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
userId |
string |
The user ID |
Vocab Links
vocabLinks.create(vocabItem, tokens, [metadata])Create a new vocab link between tokens and a vocab item.
| Parameter | Type | Description |
|---|---|---|
vocabItem |
string |
The vocab item to link |
tokens |
Array |
The tokens to link |
metadata optional |
any |
Metadata for the link. Omit to leave unset; pass null to send JSON null. |
vocabLinks.bulkCreate()Create multiple vocab links in a single operation. Entries may reference different vocab items, but all tokens across the call must belong to one document.
vocabLinks.bulkDelete(body)Delete multiple vocab links in a single operation. Provide an array of IDs.
| Parameter | Type | Description |
|---|---|---|
body |
string[] |
The vocab link IDs to delete |
vocabLinks.setMetadata(id, body)Replace all metadata for a vocab link. The entire metadata map is replaced - existing metadata keys not included in the request will be removed.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
body |
any |
The request body |
vocabLinks.deleteMetadata(id)Remove all metadata from a vocab link.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabLinks.patchMetadata(id)Edit metadata for a vocab link with a list of ops applied in order, in one operation. {op: 'set', path, value} writes value at path, creating missing objects along it; {op: 'delete', path} removes the key at path (a no-op when absent). A path is a non-empty array of keys, the first a top-level key. A path through a non-object is refused (400). See metadataOps and applyMetadataOps.
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabLinks.get(id)Get a vocab link by ID
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
vocabLinks.delete(id)Delete a vocab link
| Parameter | Type | Description |
|---|---|---|
id |
string |
The resource ID |
Comments
comments.create(entityType, entityId, body, [opts], [opts.anchorLabel])Post a comment on an entity. Requires write access to the entity's project, or to the vocabulary for a vocab-item; the author is the authenticated caller. Comments are not audited and do not bump the document version.
A comment outlives its anchor: deleting the entity does not delete the comment.
anchorLabelis the caption shown once that happens ("Gloss of ktab, sentence 4"), so pass what the comment is about.entityType'document'|'text'|'token'|'span'|'relation'|'vocab-item'entityIdstringbodystringoptsoptionalobjectopts.anchorLabeloptionalstringcomments.get(id)Read one comment.
idstringcomments.update(id, body)Edit a comment's body. Only the comment's AUTHOR may do this - not maintainers, not admins. Sets
editedon the comment.idstringbodystringcomments.delete(id)Delete a comment. The author may delete their own; a project maintainer (or admin) may delete any.
idstringcomments.list(projectId, [filters], [filters.documentId], [filters.entityType], [filters.entityId])List comments in a project, oldest first. Transparently follows pagination cursors and returns the full flat array.
projectIdstringfiltersoptionalobjectfilters.documentIdoptionalstringfilters.entityTypeoptionalstringfilters.entityIdoptionalstringcomments.listPage(projectId, [opts], [opts.limit], [opts.cursor], [opts.documentId], [opts.entityType], [opts.entityId])Fetch a single page of a project's comments.
projectIdstringoptsoptionalobjectopts.limitoptionalnumberopts.cursoroptionalstringopts.documentIdoptionalstringopts.entityTypeoptionalstringopts.entityIdoptionalstringcomments.iterPages(projectId, [opts], [opts.pageSize], [opts.documentId], [opts.entityType], [opts.entityId])Async-iterate a project's comments page by page; yields each page's entries array.
projectIdstringoptsoptionalobjectopts.pageSizeoptionalnumberopts.documentIdoptionalstringopts.entityTypeoptionalstringopts.entityIdoptionalstringcomments.counts(projectId, [filters])Comment counts per entity, as an
{entityId: n}map, over the same scope and filters aslist. One cheap request paints a comment indicator on every annotated item in a document without paging through the bodies.The response is NOT key-transformed: its keys are entity ids, and camelCasing would mangle the hyphens in a UUID.
projectIdstringfiltersoptionalobjectcomments.listInVocab(vocabId, [filters], [filters.entityId])List the comments on a vocabulary's entries, oldest first. Requires read access to the vocabulary. Transparently follows pagination cursors and returns the full flat array.
vocabIdstringfiltersoptionalobjectfilters.entityIdoptionalstringcomments.listInVocabPage(vocabId, [opts], [opts.limit], [opts.cursor], [opts.entityId])Fetch a single page of a vocabulary's comments.
vocabIdstringoptsoptionalobjectopts.limitoptionalnumberopts.cursoroptionalstringopts.entityIdoptionalstringcomments.iterInVocabPages(vocabId, [opts], [opts.entityId], [opts.pageSize])Async-iterate a vocabulary's comments page by page, oldest first; yields each page's entries array.
vocabIdstringoptsoptionalobjectopts.entityIdoptionalstringopts.pageSizeoptionalnumbercomments.countsInVocab(vocabId, [filters], [filters.entityId])Comment counts per entry of a vocabulary, as an
{entityId: n}map. The response is NOT key-transformed: its keys are entry ids.vocabIdstringfiltersoptionalobjectfilters.entityIdoptionalstring