Docs

API reference

DocRelay is preparing for launch. The specification may change before launch. The base URL is https://api.docrelay.jp.

Issuing documents

Issue documents with POST /v1/renders. If the document is ready within wait seconds (0-25, default 25), it is returned immediately (200). Otherwise you receive 202 with a Render, and a webhook on completion. Requests with wait set to 0 and batches of more than 50 documents are processed asynchronously.

curl https://api.docrelay.jp/v1/renders \
  -H "Authorization: Bearer $DOCRELAY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: inv_88" \
  -d '{
    "template": "invoice",
    "format": "pdf",
    "data": { "customer": { "name": "Acme Inc." }, "total": 128000 },
    "wait": 10
  }' \
  -o invoice.pdf
MethodPathDescription
POST/v1/rendersIssue a document. The document itself (200) if ready; a Render with Accept: application/json or link. 202 with a Render if not ready
GET/v1/rendersIssuance history (filter by template_id, status, since; newest first)
GET/v1/renders/:idOne Render
GET/v1/renders/:id/fileThe document. 410 gone after the retention period, 409 if not yet complete
POST/v1/renders/:id/linksCreate an expiring URL (expires_in, max_downloads; defaults to 24 hours with no download limit)
GET/d/:tokenDownload through an expiring URL (no API key). 404 once expired or over the limit, 410 after the document is deleted at the end of its retention period
Request and response
type RenderRequest = {
  template: string;          // template ID or name
  version?: number;          // default: published (production) / latest (test)
  format?: 'pdf' | 'xlsx' | 'docx' | 'csv';  // default: the first format
  data?: object;             // one document
  items?: object[];          // 1-1,000 documents, returned as a zip
  merge?: boolean;           // layouts only: merge into one PDF
  wait?: number;             // seconds, 0-25 (default 25)
  keep_for?: number;         // retention period in seconds (default 86,400)
  link?: true | { expires_in?: number; max_downloads?: number };  // expiring URL
  metadata?: Record<string, string>;  // up to 20, also sent in webhooks
  csv?: {                    // how to write CSV (format csv only)
    newline?: 'crlf' | 'lf';  // default crlf
    trailing_newline?: boolean;  // newline after the last row (default true)
    bom?: boolean;           // UTF-8 BOM (default true)
    encoding?: 'utf-8' | 'shift_jis';  // default utf-8
    quote?: 'minimal' | 'all';  // minimal quotes only when needed, all quotes every value
  };
};

type Render = {
  id: string;                // rnd_…
  status: 'queued' | 'rendering' | 'succeeded' | 'failed' | 'expired';
  template_id: string; version: number; format: string;
  filename: string; size: number | null; sha256: string | null; item_count: number;
  error: { code: string; message: string } | null;
  ledger: { seq: number } | null;
  link: { url: string; expires_at: number } | null;
  metadata: Record<string, string>;
  created_at: number; completed_at: number | null; expires_at: number | null;
};
  • Headers on a direct response: content-type, content-disposition (non-ASCII file names supported), x-render-id, x-content-sha256.
  • With the optional Idempotency-Key header, a retry with the same key within 24 hours returns the same result.
  • Request bodies are limited to 5MB.
  • Each document issued in production counts toward your monthly volume (batches count each document). Test issuance and previews are not counted.
  • Responses include Server-Timing with the milliseconds spent in each stage (authentication, template loading, fonts, rendering and more). Send X-DocRelay-Timing: cpu to also receive the CPU time of rendering as cpu.

Templates

MethodPathDescription
GET/v1/templatesList templates
POST/v1/templatesCreate a layout template with JSON { name, layout? } (a blank A4 page if layout is omitted), or upload a multipart file (xlsx or docx) with name
GET / PATCH / DELETE/v1/templates/:idGet, update (name, file name, sample data) or delete. :id accepts an ID or template name. Issuance history and the tamper-evident log are kept after deletion
PUT/v1/templates/:id/layoutSave a layout. Overwrites the latest version if it is a draft; otherwise creates a new draft version
POST/v1/templates/:id/versionsUpload an xlsx or docx of the same type to create a new draft version
POST/v1/templates/:id/versions/:v/publishPublish a version. Production API keys use it from then on
POST/v1/templates/:id/previewPreview. Uses the sample data if data is omitted. Not recorded in issuance history and not counted toward your monthly volume. Missing merge fields are returned in the x-missing-fields header

Layouts

Layouts designed in the template editor are JSON documents, saved with PUT /v1/templates/:id/layout. Lengths are in millimeters, with the origin at the top left of the page. The MCP tool get_layout_guide returns the same guide with samples.

ElementDescription
textText with wrapping, shrinking or clipping, borders and fills, rotate and opacity
lineLines (width, color, dashes)
rectRectangles (stroke, fill, rounded corners)
imageImages (data: or https URLs, which can also come from merge fields)
tableLine-item table. Adds a row per item in a data array and continues onto the next page, repeating the header
flowBody text. Starts in its frame and flows onto the following pages. Content is Markdown or data blocks (headings, paragraphs, lists, tables, rules and page breaks)

Line-item tables and tables in body text

  • wrap: true wraps cell text to the column width and sizes each row to its tallest cell. A row that does not fit is split by lines within its cells.
  • Row data fields _span (span columns), _align and _bold change the look of individual rows.
  • A column’s rich field ([{ text, bold?, color?, underline? }]) makes part of the text bold, colored or underlined. Markdown body text supports **bold** and <u>underline</u>.
  • Set headerBold, padding (cell padding) and lineHeight (line height within cells).
  • Cells of tables in body text can contain paragraphs, lists and tables, nested one level (data blocks only).

Other options

  • follow: { id, gap? }: places elements such as totals right after the actual end of a table or body text, moving them to the next page if they do not fit.
  • repeat: draws elements other than tables and body text on the first page, every page or the last page.
  • watermark: { text, size?, color?, opacity?, angle?, tile?, gap? }: overlays semi-transparent text on every page. tile: true repeats it across the page.
  • Merge fields: {{a.b}}, {{x | number}}, {{d | date:YYYY-MM-DD}}, {{#list}}…{{/list}}, {{^x}}…{{/x}}, {{_page}} and {{_pages}}.
  • Invalid layouts return 422 invalid, with a description per field such as elements[3].w in fields.

Completion webhooks

There are two events: render.succeeded and render.failed. The body is { type, data: Render }. How to register endpoints will be announced at launch.

Example body
{
  "type": "render.succeeded",
  "data": {
    "id": "rnd_…", "status": "succeeded", "filename": "invoice_1024.pdf",
    "sha256": "a3f9…", "item_count": 1, "link": null, …
  }
}

Retention period

Set the retention period in seconds with keep_for. The default is 86,400 seconds (24 hours), and the maximum is your plan’s retention period. After the retention period, the document is deleted and only the issuance record (hash, size and file name) and the tamper-evident log are kept.

Tamper-evident log

Tamper detection that applies blockchain technology. Each issued document’s SHA-256, render ID, template, version and time are chained to the previous entry; the data you merge is never recorded. A daily hash (per UTC day) is published.

MethodPathDescription
GET/v1/ledger/:render_idThe log entry for a document and the chain up to that day’s daily hash (head is null until the day is closed)
POST/v1/ledger/verifySend a document file or { sha256 } to verify whether it was issued in this environment
GET/ledger/headsPublished daily hashes (no API key; the last 30 days if date is omitted)
type LedgerEntry = {
  seq: number;               // sequence number
  render_id: string; sha256: string; template_id: string; version: number;
  at: number;
  prev_hash: string;         // entry_hash of the previous entry
  // SHA-256 of prev_hash, seq, sha256, render_id, at joined by "\n"
  entry_hash: string;
};
type DailyHead = { date: string; seq: number; entry_hash: string; published_at: number };

API key scopes

ScopeAllows
templates:readGet and preview templates
templates:writeCreate, update, delete and publish templates
renders:createIssue documents
renders:readGet issuance history, document files and expiring URLs; get and verify the tamper-evident log

GET /v1/whoami can be called with any scope. It returns the API key’s environment, project and organization.

Rate limits and errors

  • Rate limits apply per environment, and the per-minute limit depends on your plan (Free 60, Starter 300, Pro 1,200, Business 3,000). Every response includes x-ratelimit-limit, x-ratelimit-remaining and x-ratelimit-reset. Over the limit, the API returns 429 rate_limited with retry-after.
  • Errors are returned as { error, message, fields?, request_id }. Input errors return 422 invalid, with a description per field in fields.
  • Failed requests can be reviewed in the API logs in the dashboard for 30 days.