API reference

Virtual data room API

A data room is many documents behind one set of gates, in folders, with reading attributed per file. Build a room for a fundraise, an acquisition or a client portal, give each viewer group its own files, and freeze it when the deal closes.

Base URL https://xdocs.io/api/v1, with Authorization: Bearer xdk_… on every request. Pagination, rate limits, scopes and error codes are the same for every endpoint and are described once, in how the API behaves.

Data rooms

Many documents behind one set of gates, in folders, with reading attributed per file. A frozen room's contents are locked while its links keep serving them; an archived room is retired, and every link into it answers with a closed page until it is restored. A viewer group narrows a room link to some of its files and some of its readers: create one, give it members and permissions, then mint a link for it.

GET/api/v1/roomsrooms:read

Your data rooms, most recently changed first.

Query parameters

archivedboolean
false for the rooms the app's room list shows, true for the archived ones. Omitted, both.
limitinteger
Rows per page, up to 100. Defaults to 50.
cursorstring
The previous page's nextCursor. Opaque — pass it back unchanged.

Returns

{ data: Room[], nextCursor } — id, name, description, frozenAt, archivedAt, createdAt, updatedAt

POST/api/v1/roomsrooms:write

An empty room; file documents into it next.

Body (JSON)

namestringrequired
1–200 characters.
descriptionstring
Up to 2,000 characters.

Returns

201 { data: Room & { folders: [], documents: [] } }

  • 422 The plan's room ceiling has been reached.
GET/api/v1/rooms/:idrooms:read

The room with its state, its intro page and appearance, and its folders and filed documents in the order an ordinary room link shows them.

Returns

{ data: Room & { intro: { enabled, title, body, welcomeMessage }, appearance: { layout, accentColor, displayName, showSender, hasLogo, hasBanner }, folders: Folder[], documents: [{ documentId, folderId, position, title, kind, addedAt }] } } — documents in the trash are left out. The logo and banner are chosen in the app; the API says only whether there is one.

PATCH/api/v1/rooms/:idrooms:write

Rename or re-describe a room, freeze or unfreeze it, archive or restore it, or change what a visitor meets before the files. None of these changes what is filed, so a frozen room accepts them.

Body (JSON)

namestring
1–200 characters.
descriptionstring
Up to 2,000 characters.
frozenboolean
true locks the contents — nothing filed, moved or removed — while links keep serving exactly what is there.
archivedboolean
true retires the room: every link into it answers with a closed page. false puts it all back.
intro{ enabled?, title?, body?, welcomeMessage? }
The page shown once per visitor after the gates and before the contents — title up to 160 characters, body Markdown up to 20,000 — and welcomeMessage, a plain note of up to 600 at the top of the contents. Merged over what is there; switching the page on needs a title or a body.
appearance{ layout?, accentColor?, displayName?, showSender? }
The room's own branding over the workspace's: layout is list or grid; accentColor a six-digit hex such as #1f5fe0, or null for the workspace's; displayName (up to 120) is shown in place of the sender's name; showSender false hides it. Merged over what is there. A plan feature.

Returns

{ data: Room & { intro, appearance, folders[], documents[] } }

  • 422 An intro switched on with neither a title nor a body, an accent that is not a hex colour, or per-room branding on a plan without it.
DELETE/api/v1/rooms/:idrooms:write

To the trash. Links into the room stop answering at once; the documents themselves are untouched.

Returns

204

  • 409 A legal hold names this room.
GET/api/v1/rooms/:id/documentsrooms:read

What is filed in the room, and where.

Returns

{ data: [{ documentId, folderId, position, title, kind, addedAt }] }

POST/api/v1/rooms/:id/documentsrooms:write

File one of your documents. Idempotent: filing one already there moves it, which is what a sync wants.

Body (JSON)

documentIdstringrequired
One of your live documents.
folderIdstring | null
A folder in this room, or null for the top level.

Returns

201 { data: RoomDocument[] } — everything now filed in the room, not only this document

  • 404 No such document, or no such folder in this room.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
PATCH/api/v1/rooms/:id/documents/:documentIdrooms:write

Move a filed document between folders or along the order.

Body (JSON)

folderIdstring | null
A folder in this room, or null for the top level.
positioninteger 0–10,000

Returns

{ data: RoomDocument[] }

  • 404 That document is not in this room, or no such folder in it.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
DELETE/api/v1/rooms/:id/documents/:documentIdrooms:write

Take a document out of the room. The document itself stays.

Returns

204

  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
GET/api/v1/rooms/:id/foldersrooms:read

The room's folders, in display order.

Returns

{ data: Folder[] } — id, dataRoomId, name, position, createdAt

POST/api/v1/rooms/:id/foldersrooms:write

A folder, appended after the existing ones.

Body (JSON)

namestringrequired
1–200 characters.

Returns

201 { data: Folder }

  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
PATCH/api/v1/rooms/:id/folders/:folderIdrooms:write

Rename or reorder a folder.

Body (JSON)

namestring
1–200 characters.
positioninteger 0–10,000

Returns

{ data: Folder }

  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
DELETE/api/v1/rooms/:id/folders/:folderIdrooms:write

Remove a folder. Its documents return to the top level rather than leaving the room.

Returns

204

  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
GET/api/v1/rooms/:id/groupsrooms:read

The room's viewer groups, oldest first.

Returns

{ data: Group[] } — id, dataRoomId, name, members (addresses and bare domains), permissions: [{ itemType, itemId, canView, canDownload, canDownloadOriginal }], links: [{ id, name, active, viewCount }], createdAt, updatedAt

POST/api/v1/rooms/:id/groupsrooms:write

A viewer group. It starts able to see nothing and admitting nobody, so give it members and permissions — here or afterwards — and then a link with POST /rooms/:id/groups/:groupId/links.

Body (JSON)

namestring
1–120 characters. Defaults to “Untitled group”.
membersstring[]
Up to 500 addresses, or bare domains such as acme.com that admit everybody there.
permissions{itemType, itemId, canView?, canDownload?, canDownloadOriginal?}[]
The whole matrix, replacing what is there; anything not listed is not visible. itemType is folder or document, itemId one of this room's folders or filed documents. A folder grants everything filed in it that has no row of its own, and a document with canView: false inside a granted folder stays hidden. canView defaults to true; canDownload (a rendered copy, watermarked where the link says so) and canDownloadOriginal (the uploaded bytes) default to false, and neither applies to what cannot be seen. Up to 500.

Returns

201 { data: Group }

  • 422 A member that is neither an address nor a domain, or a permission naming a folder or document that is not in this room. Nothing is created.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
GET/api/v1/rooms/:id/groups/:groupIdrooms:read

One viewer group, with its members, its permissions and its links.

Returns

{ data: Group }

PATCH/api/v1/rooms/:id/groups/:groupIdrooms:write

Rename a group, or replace what it may see.

Body (JSON)

namestring
1–120 characters.
permissions{itemType, itemId, canView?, canDownload?, canDownloadOriginal?}[]
The whole matrix, replacing what is there; anything not listed is not visible. itemType is folder or document, itemId one of this room's folders or filed documents. A folder grants everything filed in it that has no row of its own, and a document with canView: false inside a granted folder stays hidden. canView defaults to true; canDownload (a rendered copy, watermarked where the link says so) and canDownloadOriginal (the uploaded bytes) default to false, and neither applies to what cannot be seen. Up to 500.

Returns

{ data: Group }

  • 422 A permission naming a folder or document that is not in this room. Nothing is changed.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
DELETE/api/v1/rooms/:id/groups/:groupIdrooms:write

Delete a group. Its links are switched off with it — a group link with no group behind it would have to show everything or nothing — and each that was live raises link.disabled.

Returns

204

  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
POST/api/v1/rooms/:id/groups/:groupId/membersrooms:write

Admit people to a group. Nobody is mailed by this; POST /links/:id/invitations on one of the group's links does that, and adds the address on the way.

Body (JSON)

emailsstring[]required
Up to 500 addresses, or bare domains such as acme.com. Lowercased; entries that are neither come back in skipped.

Returns

{ data: Group, added: string[], alreadyMembers: string[], skipped: string[] }

  • 422 None of the entries is an address or a domain.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
DELETE/api/v1/rooms/:id/groups/:groupId/members/:emailrooms:write

Take an address or a domain out of a group, URL-encoded in the path. They stop being admitted on its links on their next visit — unless the group also lists their domain.

Returns

{ data: Group, stillAdmitted } — stillAdmitted is true when a listed domain still lets this address in

  • 404 Not a member of this group.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
POST/api/v1/rooms/:id/groups/:groupId/linkslinks:write

A room link that admits only this group's members and shows only what the group was given. Takes every setting POST /links does, except that it always asks for an address — that is how membership is checked — and cannot be public.

Body (JSON)

…same as POST /links
Every setting POST /links accepts, all optional, with name defaulting to “<group> link”. isPublic: true and requireEmail: false are refused. The body may be left out entirely.

Returns

201 { data: Link } — with groupId set; raises link.created with groupId in its metadata

  • 422 A public or email-less group link, a setting POST /links would refuse, or the plan's link ceiling has been reached.
  • 409 The room is frozen. Unfreeze it with PATCH /rooms/:id to change its contents.
← The whole API referenceNext: NDA and agreements API →