---
title: "Get a document"
method: GET
path: "/documents/{documentId}"
tags: ["Documents"]
---

# Get a document

`GET /documents/{documentId}`

## Request

- Base URL: `https://inkseal.net/api/v1`
- URL: `https://inkseal.net/api/v1/documents/{documentId}`
- Auth: HTTP bearer

The document's current state, including the per-page dimensions written during conversion. This is the endpoint to poll after `confirm-upload`: `status` moves from `processing` to either `ready` or `failed`, and `pageCount` and `pageDimensions` are populated only once it is `ready`. A failed conversion carries the reason in `processingError`.

A document owned by another workspace answers exactly the 404 an unknown id gets, so public ids stay unprobeable.

## Response `200`

The document.

- object
  - `data` Document, required: A document in full, as returned by `GET /documents/{documentId}`.
    - `id` string, required: A document's public id: a 21-character URL-safe nanoid. Internal row ids and storage keys are never exposed.
    - `status` 'processing' | 'ready' | 'failed', required: Where the document is in conversion. `processing` from the moment the record is created until the background job finishes; then `ready` or `failed`.
    - `originalFilename` string, required
    - `originalMimeType` 'application/pdf' | 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' | 'application/msword' | 'application/rtf' | 'text/rtf' | 'application/vnd.oasis.opendocument.text' | 'application/vnd.openxmlformats-officedocument.presentationml.presentation' | 'text/plain' | 'text/markdown' | 'text/html' | 'image/png' | 'image/jpeg' | 'image/webp', required: The content type declared at creation. This is checked against the uploaded file's leading bytes at confirmation when the format has a reliable signature, so it has to be the truth. `text/html` accepts `.html` and `.htm` files up to 10 MB during conversion. The file must be self-contained and is rejected without alteration at the first forbidden construct: - Any `<script>` element, including inside SVG; any `on*` event attribute; or a `javascript:` or `vbscript:` URL. - Any `<iframe>`, `<frame>`, `<frameset>`, `<object>`, `<embed>`, `<applet>`, `<base>`, `<form>`, `<link>`, `<meta http-equiv>`, `<video>`, `<audio>`, `<track>`, or `<portal>` construct. - Any `src`, `href`, `srcset`, `poster`, `background`, `xlink:href`, `data`, `formaction`, or `ping` value unless it is empty, a bare fragment, or an allowed image data URL. Relative and scheme-relative references are external. - Any CSS `@import` or `@font-face`, any `url()` outside the allowed data-image rule, or any `image-set()` with an external member, in either `<style>` or a `style` attribute. - Any data URL except `image/png`, `image/jpeg`, `image/webp`, or `image/gif`; SVG images, fonts, media, and all other data types are rejected. Chromium independently disables JavaScript, runs offline, and aborts non-data and non-about requests. The normalized PDF follows the same 200-page limit and downstream workflow as every other source format.
    - `originalSizeBytes` integer, required: The stored object's size in bytes.
    - `pageCount` integer, nullable, required: Null until conversion has finished.
    - `pageDimensions` DocumentPageDimensions[], nullable, required: Per-page size in PDF points, in page order. Null until conversion has finished.
      - `width` number, required
      - `height` number, required
    - `processingError` string, nullable, required: Why conversion failed, when it did.
    - `createdAt` string, date-time, required
    - `updatedAt` string, date-time, required

## Other responses

- `401`: The credential is missing, malformed, unknown, expired, revoked, or past its rotation grace window. Code: `unauthorized`. Every authentication failure is deliberately indistinguishable.
- `403`: The credential is valid but may not do this. Codes: `insufficient_scope` (the route's scopes are not all held; `details` names `requiredScopes` and `missingScopes`), `forbidden` (the capability check on the resolved resource failed), `scope_escalation` (a grant wider than the caller's own was requested).
- `404`: The resource does not exist, was deleted, or belongs to another workspace. Code: `not_found`. The three cases are answered identically on purpose, so a 404 never confirms that an identifier exists elsewhere.
- `429`: A limit was reached. Codes: `rate_limited` (the credential's per-bucket HTTP limit, or the per-workspace detection throttle), `workspace_limit_exceeded` (a workspace content or send limit; `details.limit` names it), `daily_budget_exceeded` (the workspace's daily detection budget). The four rate-limit headers are sent on this response and on no successful one, so a client cannot read its remaining budget without being refused. Retrying after `Retry-After` is always safe: a rate-limit refusal happens before the handler runs.
- `503`: A dependency the request needs is unreachable and nothing was changed. Codes: `queue_unavailable` (a background job could not be enqueued; retry the call), `billing_unavailable` (Stripe would not open a checkout session; nothing was charged, retry the call), `service_unavailable` (the rate-limit store is down, so the request was refused before running).

---

[API](https://skmtc.dev/inkseal/apis/inkseal-api.md) · [All operations](https://skmtc.dev/inkseal/apis/inkseal-api/llms.txt) · [OpenAPI document](https://skmtc.dev/inkseal/apis/inkseal-api/revisions/c440c981d578?raw)
