Organizing documents with workspaces

Partition your corpus into isolated containers per team, customer, or tenant, then scope search and ask to a workspace.

A workspace is a container for documents. Every file lives in exactly one workspace, set at upload time. Workspaces are the top-level partition of your corpus: one per team, customer, or tenant. They're the natural fit for multi-tenant products, where each customer's documents must stay isolated from everyone else's.

"Search only this customer's documents."

"Answer using just the engineering team's files."

Where tags and ontology layer labels and metadata across your corpus, workspaces draw hard boundaries through it.

This tutorial covers POST /api/v1/workspaces, GET /api/v1/workspaces, PATCH /api/v1/workspaces/{id}, and DELETE /api/v1/workspaces/{id}. Uploading files into a workspace is covered in Uploading & managing files.

Workspaces, tags, or ontology?

All three organise documents, but at different layers. They compose: a file lives in one workspace, can carry several tags, and can be enriched with ontology metadata.

WorkspacesTagsOntology
What it isA container; every file lives in exactly oneFlat, reusable labelsTyped, structured metadata with a schema
A file belongs toexactly one workspacemany tags, across workspacesstructured metadata, across workspaces
Best forIsolating teams, customers, tenantsCross-cutting collections (project, topic)Precise structured queries
Setup costCreate a workspaceCreate a tagDefine an ontology
Scope a query withworkspace_idtag_idrelation / relation_depth
Access controlYes: API keys can be scoped to a workspace with a per-key roleNo: not a permission boundaryNo: not a permission boundary

Only workspaces are a permission boundary: you can issue API keys scoped to specific workspaces, which makes them the right tool for segmenting data that needs different permission levels. workspace_id and tag_id can be combined in a search or ask to narrow within a workspace; file_id is mutually exclusive with both. The rest of this tutorial covers workspaces.

Step 1: Create a workspace

A workspace needs a name. The caller is automatically added as its owner.

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

const response = await fetch("https://api.context212.com/api/v1/workspaces", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "Engineering Team",
    description: "Documents owned by the engineering org",
  }),
});
console.log(await response.json());

A 201 returns the new workspace with its id. You'll pass that ID when uploading files and when scoping queries.

{
  "id": 42,
  "name": "Engineering Team",
  "workspace_type": "shared",
  "document_upload_method": "manual",
  "description": "Documents owned by the engineering org",
  "files_count": 0,
  "user_role": "owner"
}

A 403 means workspace creation is disabled for your company and you're not an admin: create it from the console instead, or ask an admin.

Step 2: Find a workspace's ID

List the workspaces you're a member of to grab an ID. Filter by name, or by workspace_type, user_role, and document_upload_method.

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

// Workspaces you're a member of, optionally filtered by name
const response = await fetch(
  "https://api.context212.com/api/v1/workspaces?name=engineering",
  { headers },
);
console.log(await response.json());

The response is paginated. Each workspace carries a files_count and your user_role (owner, editor, or viewer).

{
  "count": 1,
  "results": [
    {
      "id": 42,
      "name": "Engineering Team",
      "workspace_type": "shared",
      "files_count": 128,
      "user_role": "owner"
    }
  ]
}

Step 3: Put files in a workspace

A file is assigned to its workspace at upload time: pass workspace_id in the upload payload. A file's workspace is fixed; there's no "move file" operation. See Uploading & managing files for the full upload flow.

To pull back every file in a workspace, filter the file listing:

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

// Every file in a workspace
const response = await fetch("https://api.context212.com/api/v1/files?workspace_id=42", {
  headers,
});
console.log(await response.json());

To populate a workspace automatically from Google Drive, SharePoint, ServiceNow, or a web scraper, convert it to a synced workspace with a datasource payload on PATCH /api/v1/workspaces/{id}. The workspace must be empty first.

Step 4: Scope search and ask to a workspace

Both POST /api/v1/search and POST /api/v1/ask accept a workspace_id array that restricts the query to those workspaces. Pass several IDs to query across a set of them.

Search within a workspace:

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

// Search only within these workspaces (pass several IDs to widen the scope)
const response = await fetch("https://api.context212.com/api/v1/search", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    query: "deployment runbook",
    workspace_id: [42],
  }),
});
console.log(await response.json());

Ask a grounded question within a workspace:

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

// Grounded answer over only the documents in these workspaces
const response = await fetch("https://api.context212.com/api/v1/ask", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    query: "What is our incident escalation policy?",
    workspace_id: [42],
  }),
});
console.log(await response.json());

Combine workspace_id with tag_id to scope to a tagged collection within a workspace.

Step 5: Rename or delete a workspace

Only custom workspaces can be updated, and only by their owner. Personal and company workspaces are managed by the platform.

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

const workspaceId = 42; // replace with your workspace ID

// Only CUSTOM workspaces can be updated, and only by their owner
const response = await fetch(`https://api.context212.com/api/v1/workspaces/${workspaceId}`, {
  method: "PATCH",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Engineering (EMEA)" }),
});
console.log(await response.json());

Deleting a workspace removes it and every document inside it, so use it deliberately.

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

const workspaceId = 42; // replace with your workspace ID

// Deletes the workspace and every document it contains
const response = await fetch(`https://api.context212.com/api/v1/workspaces/${workspaceId}`, {
  method: "DELETE",
  headers,
});
console.log(response.status);

A 204 confirms the workspace and its contents are gone.

Segmenting access with scoped API keys

Workspaces aren't only an organisational boundary, they're a permission boundary. You can mint an API key that is scoped to specific workspaces, so the key can only read or write the documents in those workspaces and nothing else. This is what makes workspaces the right tool when different data needs different permission levels.

Pass a scopes list to POST /api/v1/keys. Each entry pairs a workspace_id with a role, one of viewer, editor, or owner. The requested role on a workspace is capped at the role you currently hold there, so you can't hand out more access than you have.

const headers = {
  Authorization: `Bearer ${process.env.C212_API_KEY}`,
};

// Scope the key to a workspace with a per-workspace role (viewer | editor | owner).
// The requested role is capped at the role you hold on that workspace.
const response = await fetch("https://api.context212.com/api/v1/keys", {
  method: "POST",
  headers: { ...headers, "Content-Type": "application/json" },
  body: JSON.stringify({
    name: "Acme tenant key (read-only)",
    expires_at: null, // or an ISO 8601 datetime to auto-expire
    scopes: [{ workspace_id: 42, role: "viewer" }],
  }),
});
console.log(await response.json()); // the full `key` value is returned only once

The key value is returned only once, on creation, so store it immediately.

A few patterns this unlocks:

  • Multi-tenant isolation: give each customer a key scoped to their own workspace. A request made with that key can never reach another tenant's documents, even if your application code has a bug.
  • Read-only integrations: issue a viewer-scoped key to a dashboard or analytics job that should search and ask but never upload or delete.
  • Per-environment separation: scope staging and production keys to different workspaces so a misconfigured job can't touch the wrong corpus.

Because a single key can list several scopes with different roles per workspace, you can grant, say, editor on one workspace and viewer on another with one key. An unscoped key, by contrast, inherits the full permissions of its owner across every workspace, so prefer scoped keys whenever a credential is handed to a narrower-trust consumer.

Tags and ontology metadata don't carry their own permissions: a key that can see a workspace can see every tag and ontology field on the files inside it. When access control is the goal, segment with workspaces.

Next steps

On this page