merlon API v1
A resource-oriented REST API for creating documents, provisioning secure vaults, orchestrating approval workflows, and reading tamper-evident audit logs. All requests are JSON over HTTPS, authenticated with a bearer token, and served from the region that matches your data-residency configuration.
- Base URL
https://api.merlon.ch/v1- Regional URLs
https://api.us.merlon.ch/v1·https://api.eu.merlon.ch/v1- Content type
application/jsonfor request and response bodies- Timestamps
- RFC 3339 / ISO 8601, always UTC (e.g.
2026-07-27T09:41:08Z) - Object IDs
- Prefixed, opaque strings —
doc_,vault_,wf_,evt_
Authentication
merlon supports two authentication schemes: API keys for server-to-server integrations, and OAuth2 for apps that act on behalf of a user. All requests must be made over HTTPS; calls over plain HTTP fail before reaching the application.
API keys
Pass your secret key as a bearer token. Keys are environment-scoped: sk_test_… hits the sandbox, sk_live_… hits production. Never expose a secret key in client-side code.
curl "https://api.merlon.ch/v1/documents" \ -H "Authorization: Bearer sk_live_51Kf9c1Za8Xq2Wn4dPmR0"
OAuth2
For user-context apps, use the authorization-code flow. Exchange the code for an access token, then call the API with that token. Scopes map to the same permissions a user holds in the web app.
curl -X POST "https://api.merlon.ch/v1/oauth/token" \ -d "grant_type=authorization_code" \ -d "code=ac_9c1Za8Xq" \ -d "client_id=cl_7Qd2" \ -d "client_secret=cs_••••" \ -d "redirect_uri=https://app.example.com/callback"
{
"access_token": "at_5Rk21m…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "documents:write vaults:read"
}
Rate limits
Limits are applied per API key on a sliding one-minute window. Every response includes rate-limit headers so you can back off gracefully before you hit a wall.
| Plan | Sustained | Burst | Scope |
|---|---|---|---|
| Sandbox | 120 req/min | 200 req/min | per test key |
| Standard | 600 req/min | 1,000 req/min | per live key |
| Enterprise | Custom | Custom | per account |
When you exceed the limit the API returns 429 Too Many Requests with a Retry-After header (seconds). Response headers on every call: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Pagination
List endpoints are cursor-paginated. Pass limit (1–100, default 20) and follow next_cursor until it is null. Cursors are opaque and stable across inserts.
curl "https://api.merlon.ch/v1/documents?limit=20&cursor=cur_8Xq2Wn4d" \ -H "Authorization: Bearer $MERLON_API_KEY"
{
"object": "list",
"data": [ /* … 20 documents … */ ],
"has_more": true,
"next_cursor": "cur_3kf9c1Za"
}
Errors
merlon uses conventional HTTP status codes. Codes in the 2xx range mean success, 4xx indicate a problem with your request, and 5xx indicate a problem on our side. Every error body carries a machine-readable type, a human message, and a request_id you can quote to support.
| Status | Type | Meaning |
|---|---|---|
| 400 | bad_request | Malformed request — invalid JSON or missing body. |
| 401 | authentication_error | Missing, invalid, or revoked API key. |
| 403 | permission_error | Key is valid but lacks the required scope. |
| 404 | not_found | The requested resource does not exist in this region. |
| 409 | conflict | Idempotency-Key reused with a different payload, or state conflict. |
| 422 | validation_error | Body parsed but a field failed validation. |
| 429 | rate_limit_error | Too many requests — see Retry-After. |
| 500 | api_error | Something went wrong on our end. Safe to retry idempotent calls. |
{
"error": {
"type": "permission_error",
"message": "This key is missing the 'vaults:write' scope.",
"request_id": "req_9c1Za8Xq"
}
}
Versioning & deprecation policy
The API is versioned in the URL path (/v1). We add fields and endpoints without bumping the version — your integration should ignore unknown fields. Breaking changes only ship under a new major version.
- Additive changes
- New optional fields and endpoints may appear at any time within
/v1. Treat responses as forward-compatible. - Deprecations
- Announced in the changelog with at least a 12-month sunset window and a
Sunsetresponse header on affected endpoints. - Regional endpoints
- Every version is available on all three hosts; residency is enforced at the edge regardless of host.
Documents
A document is a versioned file living inside a vault. Documents move through a lifecycle — draft → in_review → signed → locked — and every transition is recorded in the audit log.
List documents visible to the authenticated key, newest first. Cursor-paginated.
| Parameter | Type | Description |
|---|---|---|
vault_id | string | Optional. Filter to a single vault. |
status | string | Optional. One of draft, in_review, signed, locked. |
limit | integer | 1–100, default 20. |
cursor | string | Pagination cursor from a previous response. |
curl "$MERLON_BASE/documents?vault_id=vault_9f2c&status=signed&limit=2" \ -H "Authorization: Bearer $MERLON_API_KEY"
{
"object": "list",
"data": [
{ "id": "doc_3kf9c1Za", "title": "MSA — Acme", "status": "signed", "version": 4 },
{ "id": "doc_7Qd2Wn4d", "title": "NDA — Nordwind", "status": "signed", "version": 2 }
],
"has_more": false,
"next_cursor": null
}Create a new document in a vault. Supports Idempotency-Key.
| Parameter | Type | Description |
|---|---|---|
title | string | Required. Human-readable title. |
vault_id | string | Required. Destination vault. |
metadata | object | Optional. Arbitrary key/value pairs (counterparty, retention…). |
curl -X POST "$MERLON_BASE/documents" \ -H "Authorization: Bearer $MERLON_API_KEY" \ -H "Idempotency-Key: 3f0c-b12a-7d91" \ -d '{ "title": "MSA — Acme", "vault_id": "vault_9f2c" }'
{
"id": "doc_3kf9c1Za",
"object": "document",
"status": "draft",
"version": 1,
"created_at": "2026-07-27T09:41:08Z"
}Retrieve a single document, including its version history and current signers.
curl "$MERLON_BASE/documents/doc_3kf9c1Za" \ -H "Authorization: Bearer $MERLON_API_KEY"
{
"id": "doc_3kf9c1Za",
"title": "MSA — Acme",
"status": "signed",
"version": 4,
"vault_id": "vault_9f2c",
"signers": [ "gc@acme.com", "legal@merlon.ch" ],
"sha256": "9f2c…a81e"
}Soft-delete a document. Documents under legal hold or retention cannot be deleted and return 409.
curl -X DELETE "$MERLON_BASE/documents/doc_3kf9c1Za" \ -H "Authorization: Bearer $MERLON_API_KEY"
{ "id": "doc_3kf9c1Za", "deleted": true }Secure Vaults
A vault is an encrypted, access-scoped container for documents. Each vault has its own region, encryption envelope, and least-privilege member list.
List vaults the key can access.
curl "$MERLON_BASE/vaults" -H "Authorization: Bearer $MERLON_API_KEY"
{
"object": "list",
"data": [
{ "id": "vault_9f2c", "name": "Legal — Contracts", "region": "eu", "members": 6 }
]
}Provision a new vault with a chosen region and initial members.
| Parameter | Type | Description |
|---|---|---|
name | string | Required. Display name. |
region | string | Required. us or eu — sets data residency. |
members | array | Optional. Email addresses to grant access at creation. |
curl -X POST "$MERLON_BASE/vaults" \ -H "Authorization: Bearer $MERLON_API_KEY" \ -d '{ "name": "HR — Personnel", "region": "eu" }'
{
"id": "vault_5Rk21m",
"name": "HR — Personnel",
"region": "eu",
"encryption": "aes-256-gcm · envelope"
}Retrieve a vault with its member list and document count.
{
"id": "vault_9f2c",
"name": "Legal — Contracts",
"region": "eu",
"document_count": 42,
"members": [
{ "email": "gc@acme.com", "role": "owner" }
]
}Workflows
A workflow is an ordered set of approval steps attached to a document. Steps advance in sequence; each transition is captured with the actor, timestamp, and decision.
Start a workflow on a document with an ordered list of approver steps.
| Parameter | Type | Description |
|---|---|---|
document_id | string | Required. Document the workflow governs. |
steps | array | Required. Ordered approver objects with assignee and optional condition. |
curl -X POST "$MERLON_BASE/workflows" \ -H "Authorization: Bearer $MERLON_API_KEY" \ -d '{ "document_id": "doc_3kf9c1Za", "steps": [ { "assignee": "controller@acme.com" }, { "assignee": "cfo@acme.com", "condition": "amount > 50000" } ] }'
{
"id": "wf_8Xq2Wn4d",
"document_id": "doc_3kf9c1Za",
"status": "in_progress",
"current_step": 1
}Record a decision on the current step. A decision of reject halts the workflow.
| Parameter | Type | Description |
|---|---|---|
decision | string | Required. approve or reject. |
comment | string | Optional. Reviewer note stored on the audit trail. |
{
"id": "wf_8Xq2Wn4d",
"status": "in_progress",
"current_step": 2,
"last_decision": { "by": "controller@acme.com", "decision": "approve" }
}Retrieve a workflow with the full decision history for each step.
{
"id": "wf_8Xq2Wn4d",
"status": "completed",
"steps": [
{ "assignee": "controller@acme.com", "decision": "approve" },
{ "assignee": "cfo@acme.com", "decision": "approve" }
]
}Audit Logs
Every action in merlon writes an immutable, hash-chained audit event. The audit API is read-only — you can list and export events but never mutate them.
List audit events, filterable by resource, actor, and time range.
| Parameter | Type | Description |
|---|---|---|
resource_id | string | Optional. Filter to one document or vault. |
since | string | Optional. ISO 8601 lower bound. |
until | string | Optional. ISO 8601 upper bound. |
{
"object": "list",
"data": [
{ "id": "evt_5Rk21m", "type": "signature.completed", "actor": "gc@acme.com", "at": "2026-07-27T09:44:12Z" },
{ "id": "evt_4dPmR0", "type": "document.locked", "actor": "system", "at": "2026-07-27T09:44:13Z" }
]
}Request a signed export of the audit log for a period. Returns a job you can poll; when ready, a time-limited download URL is issued.
{
"id": "exp_7Qd2",
"status": "processing",
"format": "csv",
"period": "2026-07-01/2026-07-31"
}Webhooks
Register HTTPS endpoints to receive signed, retried event notifications. Each endpoint has its own signing secret used to compute the X-Merlon-Signature header.
Create a webhook endpoint subscribed to one or more event types.
| Parameter | Type | Description |
|---|---|---|
url | string | Required. HTTPS endpoint to receive events. |
events | array | Required. Event types, e.g. signature.completed. |
curl -X POST "$MERLON_BASE/webhook_endpoints" \ -H "Authorization: Bearer $MERLON_API_KEY" \ -d '{ "url": "https://app.example.com/hooks", "events": ["signature.completed"] }'
{
"id": "whk_3kf9c1",
"url": "https://app.example.com/hooks",
"signing_secret": "whsec_••••",
"status": "active"
}List all registered webhook endpoints and their subscribed events.
{
"object": "list",
"data": [
{ "id": "whk_3kf9c1", "url": "https://app.example.com/hooks", "events": ["signature.completed"] }
]
}Delete a webhook endpoint. In-flight deliveries are allowed to finish; no new events are sent.
{ "id": "whk_3kf9c1", "deleted": true }