Skip to main content

API reference · v1

Redaction as an API call

Upload a document, read what Northdocs found, decide, apply and download the redacted file. Plain JSON over HTTPS, bearer keys, hosted in the EU.

Base URL
https://northdocs.com/api/v1
Auth
Bearer API key
Rate limit
120 req/min per key
Hosting
EU only

Introduction

The Northdocs API is a REST API with predictable resource URLs, JSON bodies and standard HTTP status codes. Every path in this reference is relative to the base URL.

Documents are stored and processed in the EU, exactly as when they are uploaded in the app. Each call runs with the role of the member who created the key, so a reviewer key can decide detections but cannot upload or apply.

Base URL
https://northdocs.com/api/v1

Authentication

Authenticate with an API key in the Authorization header. Admins and the security role create keys in Settings, API keys. A key is shown once: store it in your secret manager straight away.

Authorization: Bearer bd_live_3f9c...e21a

Keys start with bd_live_. Never ship one in a browser or mobile app.

A key stops working when it is revoked or when the member who created it leaves the organization. Calls then answer 401 or 403.

Request
curl "https://northdocs.com/api/v1/documents" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"

Scopes

Each key carries scopes. A call without the scope it needs answers 403. Give each integration the smallest set it needs.

  • documents:readList documents and read status, detections, text and downloads. Also reads cases and jobs.
  • documents:writeUpload documents, decide or reclassify detections and apply. Also creates cases.
  • cases:readList and read cases.
  • cases:writeCreate cases.
  • jobs:readList recent processing jobs.
  • outbound_guardText sanitizing and the Outbound Guard endpoints used by the Outlook add-in.

Quick start

Four calls take a PDF from upload to redacted download. The script on the right is the whole flow in bash with curl and jq.

  1. 1

    Upload the file. Northdocs extracts text, runs OCR where needed and detects personal data.

  2. 2

    Poll until the status is ready_for_review, or pass resultUrl on upload and wait for the callback.

  3. 3

    Apply. acceptAll accepts every pending detection first; decide single detections before this call to keep some.

  4. 4

    Download the redacted PDF once the status is completed.

bash
# 1. Upload
DOC=$(curl -s -X POST "https://northdocs.com/api/v1/documents" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -F "file=@contract.pdf" | jq -r .id)

# 2. Wait for review
until [ "$(curl -s "https://northdocs.com/api/v1/documents/$DOC" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" | jq -r .status)" = "ready_for_review" ]; do
  sleep 3
done

# 3. Accept every finding and redact
curl -s -X POST "https://northdocs.com/api/v1/documents/$DOC/apply" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"redact","acceptAll":true}'

# 4. Download when completed
curl -s "https://northdocs.com/api/v1/documents/$DOC/download" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" -o contract-redacted.pdf

Document lifecycle

A document moves through these statuses. Poll GET /documents/{id} or subscribe to the document.completed webhook.

uploadedextractingdetectingready_for_reviewapplyingcompleted
uploaded
Stored, waiting for a worker.
extracting
Reading text and running OCR on scanned pages.
detecting
Finding personal data.
ready_for_review
Detections are ready to read and decide.
applying
Burning redactions into the file and verifying the result.
completed
The redacted output is ready to download.
failed
Processing stopped. The error field says why.
Documents
POST/documents

Upload a document

Uploads a file and starts processing. Send multipart/form-data with the file in the file field. PDF, Office documents, images, e-mail and plain text are accepted.

Scopedocuments:write

Parameters

  • filefileformrequired

    The file to process.

  • caseIdstringform

    The case id.

  • resultUrlurlform

    Public HTTPS URL that receives a signed POST when the document completes.

  • redirectUrlurlform

    Where the in-app review sends the user afterwards. Must be on the app origin.

Request
curl -X POST "https://northdocs.com/api/v1/documents" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -F "file=@contract.pdf" \
  -F "caseId=case_7Hq2"
Response
201 Created
{
  "id": "doc_9fK3a",
  "status": "uploaded"
}
GET/documents

List documents

Documents the key owner can open, newest first. Filter by case with caseId.

Scopedocuments:read

Parameters

  • caseIdstringquery

    Only documents in this case.

Request
curl "https://northdocs.com/api/v1/documents?caseId=case_7Hq2" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
[
  {
    "id": "doc_9fK3a",
    "name": "contract.pdf",
    "status": "ready_for_review",
    "pageCount": 12,
    "caseId": "case_7Hq2"
  }
]
GET/documents/{id}

Retrieve a document

Status, page count, case and detected language of one document. Poll this while a document processes.

Scopedocuments:read

Parameters

  • idstringpathrequired

    The document id.

Request
curl "https://northdocs.com/api/v1/documents/doc_9fK3a" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
{
  "id": "doc_9fK3a",
  "name": "contract.pdf",
  "status": "ready_for_review",
  "pageCount": 12,
  "caseId": "case_7Hq2",
  "language": "da"
}
POST/documents/{id}/apply

Apply redactions

Burns accepted detections into the file, or replaces them with consistent pseudonyms. Answers 202 at once; the status moves to applying and then completed.

Scopedocuments:write

Parameters

  • idstringpathrequired

    The document id.

  • mode"redact" | "pseudonymize"body

    redact (default) blacks out values; pseudonymize replaces them consistently.

  • acceptAllbooleanbody

    Accept every pending detection before applying.

Request
curl -X POST "https://northdocs.com/api/v1/documents/doc_9fK3a/apply" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"mode":"redact","acceptAll":true}'
Response
202 Accepted
{
  "status": "applying"
}
GET/documents/{id}/download

Download the output

Returns the redacted PDF. With format=text it returns the redacted text per page as JSON instead. Answers 409 until redactions are applied.

Scopedocuments:read

Parameters

  • idstringpathrequired

    The document id.

  • format"text"query

    Return redacted text as JSON instead of the PDF.

Request
curl "https://northdocs.com/api/v1/documents/doc_9fK3a/download?format=text" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
{
  "id": "doc_9fK3a",
  "pages": [{ "pageNumber": 1, "text": "Lejer: [REDACTED], Nørregade 12" }],
  "fullText": "Lejer: [REDACTED], Nørregade 12"
}
GET/documents/{id}/text

Read extracted text

Raw text per page before redaction, for search and indexing. Not an export, and refused where the organization restricts unredacted access.

Scopedocuments:read

Parameters

  • idstringpathrequired

    The document id.

Request
curl "https://northdocs.com/api/v1/documents/doc_9fK3a/text" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
{
  "pages": [
    { "pageNumber": 1, "text": "Lejer: Jens Hansen, Nørregade 12", "ocr": false }
  ]
}
Detections
GET/documents/{id}/detections

List detections

Everything Northdocs found, with category, page, text and decision status. A status of possible is an uncertain suggestion: never redacted until set to accepted. Values are masked for roles that may not see them.

Scopedocuments:read

Parameters

  • idstringpathrequired

    The document id.

Request
curl "https://northdocs.com/api/v1/documents/doc_9fK3a/detections" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
[
  {
    "id": "det_41xZ",
    "category": "person",
    "text": "Jens Hansen",
    "pageNumber": 1,
    "status": "pending",
    "entityGroupId": "ent_02",
    "pseudonym": null
  }
]
PATCH/documents/{id}/detections/{detectionId}

Update a detection

Accept, skip or reset one detection, or move it to another category. Reviewer keys may decide but not reclassify.

Scopedocuments:write

Parameters

  • idstringpathrequired

    The document id.

  • detectionIdstringpathrequired

    The detection id from List detections.

  • status"pending" | "accepted" | "skipped"body

    The decision for this detection.

  • categorystringbody

    Move the detection to another category key.

Request
curl -X PATCH "https://northdocs.com/api/v1/documents/doc_9fK3a/detections/det_41xZ" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"skipped"}'
Response
200 OK
{
  "ok": true
}
Cases and jobs
POST/cases

Create a case

Creates a case to group documents. Pass its id as caseId on upload.

Scopecases:write

Parameters

  • namestringbodyrequired

    Case name, shown in the app.

  • referencestringbody

    Your own reference, such as a journal number.

  • defaultMode"redact" | "pseudonymize"body

    Default apply mode for documents in the case.

Request
curl -X POST "https://northdocs.com/api/v1/cases" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Aktindsigt 2026-114","reference":"J.nr. 2026-114"}'
Response
201 Created
{
  "id": "case_7Hq2"
}
GET/cases

List cases

Cases the key owner can open.

Scopecases:read

Parameters

No parameters.

Request
curl "https://northdocs.com/api/v1/cases" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
[
  {
    "id": "case_7Hq2",
    "name": "Aktindsigt 2026-114",
    "status": "open",
    "reference": "J.nr. 2026-114"
  }
]
GET/cases/{id}

Retrieve a case

One case with its status and reference.

Scopecases:read

Parameters

  • idstringpathrequired

    The case id.

Request
curl "https://northdocs.com/api/v1/cases/case_7Hq2" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
{
  "id": "case_7Hq2",
  "name": "Aktindsigt 2026-114",
  "status": "open",
  "reference": "J.nr. 2026-114"
}
GET/jobs

List jobs

Recent processing jobs with kind, status and progress.

Scopejobs:read

Parameters

No parameters.

Request
curl "https://northdocs.com/api/v1/jobs" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY"
Response
200 OK
[
  {
    "id": "job_k21P",
    "kind": "process",
    "status": "completed",
    "documentId": "doc_9fK3a",
    "progress": 100
  }
]
Text
POST/sanitize

Sanitize text

Finds and redacts or replaces personal data in plain text. Nothing is stored.

Scopeoutbound_guard

Parameters

  • textstringbodyrequired

    The text to sanitize.

  • mode"redact" | "replace"body

    redact (default) blacks out values; replace swaps in a placeholder.

Request
curl -X POST "https://northdocs.com/api/v1/sanitize" \
  -H "Authorization: Bearer $NORTHDOCS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"Ring til Jens Hansen på 20 30 40 50","mode":"redact"}'
Response
200 OK
{
  "cleanText": "Ring til ███████████ på ███████████",
  "findings": [
    {
      "category": "person",
      "label": "Person name",
      "text": "Jens Hansen",
      "start": 9,
      "end": 20,
      "replacement": "███████████",
      "source": "regex"
    },
    {
      "category": "phone",
      "label": "Phone number",
      "text": "20 30 40 50",
      "start": 24,
      "end": 35,
      "replacement": "███████████",
      "source": "regex"
    }
  ],
  "byCategory": { "person": 1, "phone": 1 },
  "mode": "redact"
}
Events and errors

Webhooks

Add an endpoint in Settings, Webhooks. Northdocs sends a signed POST with a JSON body to your public HTTPS URL when a subscribed event happens.

  • document.completedA document finished applying and the output is ready to download.
  • outbound_guard.alertOutbound Guard stopped or warned about an outgoing mail.

Verify every delivery. x-northdocs-signature is the hex HMAC SHA-256 of the raw body, keyed with the signing secret shown when you added the endpoint.

HTTP
POST /northdocs-hook HTTP/1.1
Content-Type: application/json
x-northdocs-event: document.completed
x-northdocs-signature: 5f2c8a...e91a

{
  "event": "document.completed",
  "data": { "documentId": "doc_9fK3a", "mode": "redact" },
  "timestamp": 1793500000000
}
verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function isFromNorthdocs(rawBody: string, signature: string, secret: string) {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(signature, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

Errors

Errors use standard HTTP status codes and a JSON body with an error message. Some carry a machine readable code as well.

  • 400A field is missing or invalid.
  • 401The API key is missing, wrong or revoked.
  • 402The plan or quota does not cover this call.
  • 403Missing scope, or the key owner's role cannot do this.
  • 404Not found, or the key owner cannot open it.
  • 409Wrong state, for example still processing.
  • 413The file is too large.
  • 415The file type is not supported.
  • 429Rate limited. Back off and retry.
Response
403 Forbidden
HTTP/1.1 403 Forbidden
Content-Type: application/json

{
  "error": "api key missing documents:write scope"
}

Rate limits

Each key may make 120 requests per minute. Above that the API answers 429; wait a few seconds and retry. Polling one document every few seconds stays well inside the limit.

Uploads count toward the organization's monthly page quota, exactly as uploads in the app do.

OpenAPI spec

The full API is described in an OpenAPI 3.1 document. Import it into Postman or Insomnia, or generate a typed client.

Open openapi.json

Missing an endpoint? Tell us from Contact support in the app.

https://northdocs.com/api/v1

URL
https://northdocs.com/api/v1/openapi.json