Uploading & managing files

Upload documents into Context212 so they become searchable in seconds.

Before you can search, your documents need to be in Context212. Uploading a file triggers an ingestion pipeline that parses the content, splits it into chunks, generates embeddings, and indexes everything. The whole process typically takes a few seconds for a standard PDF.

Ingestion is asynchronous: the upload returns immediately with a pending status, and you poll GET /api/v1/files/{id} for completion.

This tutorial covers POST /api/v1/files and GET /api/v1/files. The full schema for every endpoint and parameter lives in the API reference.

Upload a file

Send the file as multipart/form-data with the destination workspace_id.

Accepted formats: csv, doc, docx, htm, html, jpeg, jpg, md, odp, odt, pdf, png, ppt, pptx, txt, xhtml, xls, xlsx.

import { client, filesCreate } from "@context212/sdk";

client.setConfig({
  headers: {
    Authorization: `Bearer ${process.env.C212_API_KEY}`,
  },
});

const { data, error } = await filesCreate({
  body: {
    workspace_id: 42,
    file, // File or Blob
  },
});

if (error) throw error;
console.log(data.id, data.status, data.upload_session_uuid);
// → 12345 pending 550e8400-e29b-41d4-a716-446655440000

The response is a 201 with the new file record, including an upload_session_uuid you can use later to find every file uploaded in the same batch.

Optional fields: title (defaults to filename without extension), filename (override the uploaded name), and parser (omit to use the platform default).

When external_metadata.external_id is set and a manually uploaded document with the same ID already exists in that workspace, the API returns 200 with the existing file instead of creating a duplicate.

Wait for indexing to complete

Poll GET /api/v1/files/{id} until status reaches embedded:

import { filesRetrieve } from "@context212/sdk";

const { data: body, error } = await filesRetrieve({
  path: { id: data.id },
});

if (error) throw error;
console.log(body.status, body.status_detail);
// → embedded null

The status field moves through these stages:

StatusWhat's happening
pendingQueued for processing
parsingExtracting text from the document
parsing_failedParsing failed, see status_detail
embeddingGenerating vector embeddings
embedding_failedEmbedding failed, see status_detail
embeddedIndexed and ready to search
updatingRe-indexing in progress
failGeneric failure, see status_detail

status_vision tracks the same lifecycle for vision/image embeddings: pending, processing, embedded, fail, or - (not available for this file).

Organising documents with tags and titles

Add a human-readable title and assign tag IDs at upload time. Tags can be sent as a JSON-encoded array string or as repeated form fields with the same name.

import { filesCreate } from "@context212/sdk";

await filesCreate({
  body: {
    workspace_id: 42,
    title: "Q4 Financial Report",
    tags: "[1, 2]", // JSON-encoded list of tag IDs
    file, // File or Blob
  },
});

If a tag ID is invalid, the file is still created but the response is a 207 (multi-status) with a message explaining which tags were rejected.

To replace tags after upload, PATCH /api/v1/files/{id} with a new tags array. It replaces all existing tags, manual and auto-assigned. Send [0] (sentinel) to remove every tag when using multipart format. To add tags without touching existing ones, POST /api/v1/files/{id}/tags.

Tracking documents from external systems

If you're ingesting documents from a third-party system (ServiceNow, Confluence, SharePoint, etc.), store the source identifier in external_metadata. This lets you find the Context212 file later given only the external ID, and surface the original URL in your UI.

import { filesCreate } from "@context212/sdk";

await filesCreate({
  body: {
    workspace_id: 42,
    file, // File or Blob
    external_metadata: JSON.stringify({
      external_id: "SRV-456789",
      doc_type: "incident",
      additional_metadata: {
        external_url: "https://servicenow.example.com/incident/SRV-456789",
      },
    }),
  },
});

external_id is required when creating; doc_type and additional_metadata are optional. When sent via multipart/form-data, the whole external_metadata value must be a JSON string.

Retrieve it later by external ID:

GET /api/v1/files?external_metadata__external_id=SRV-456789

Listing and filtering your documents

GET /api/v1/files supports rich filtering. A few common patterns:

import { filesList } from "@context212/sdk";

// All files in a workspace
await filesList({ query: { workspace_id: "42" } });

// Semantic search across filenames and titles, with the top chunk inline
await filesList({
  query: { search: "security policy", search_details: true },
});

// PDFs tagged 'legal', most recent first
await filesList({
  query: { tag_id: "3", extension: "pdf", ordering: "-created_at" },
});

// Files in a 10–50 page window
await filesList({
  query: { total_pages_min: 10, total_pages_max: 50 },
});

Set include_details=true to receive the signature (TLSH hash for duplicate detection) and parser fields on each result.

For graph-aware filtering (concepts and relations), see the Ontology tutorials.

Deleting files

Single delete:

import { filesDestroy } from "@context212/sdk";

await filesDestroy({ path: { id: fileId } });

Bulk delete:

import { filesBulkDeleteCreate } from "@context212/sdk";

await filesBulkDeleteCreate({
  body: { ids: [123, 124, 125] },
});

Both return 204 No Content on success. Files in synced (datasource-managed) workspaces cannot be deleted manually. The API returns 400.

Common errors

StatusCause
400Validation error, unsupported file type, or synced-workspace constraint
401Missing or invalid API key
403Permission denied (no upload/delete rights)
404File does not exist or is not accessible
429Too many concurrent uploads for this session

On this page