Contents

Imports

Bring in whole collections at once, from an upload or your own storage tools.

The FolioImport object

Attributes

  • id required Type: string
  • reference required Type: string
  • source_system required Type: string
  • state required Type: "open" "closed" "complete"
  • counts required Type: FolioImportCounts
  • bytes_expected required Type: integer
  • bytes_filed required Type: integer
  • bytes_to_file required Type: integer

    The declared bytes of the lines waiting for their file or queued.

  • percent required Type: integer
  • created_at_ms required Type: integer
  • closed_at_ms required Type: integer null
  • completed_at_ms required Type: integer null

FolioImport on its own page

The organisation's imports

GET /folio/imports

Needs folio:records:read: See filed records, folders and metadata

The latest 50 imports, newest first, each with its progress.

Returns

  • 200
    The imports
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/imports · curl
curl "https://api.unforged.sh/folio/imports" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/imports · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/imports",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/imports · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Start an import, or add lines to it

POST /folio/imports

Needs folio:records:write: File records, create folders, extend retention

A bulk import of an existing archive: send the manifest, then each line's file to POST /folio/imports/{id}/content. The body is a JSON manifest, or a CSV one (Content-Type: text/csv, a header row naming the columns reference, folder, name, sha256, size, class, original_date, event_date, matter, subject, with reference and source_system in the query). The import is named by its reference: the same manifest sent again, or in parts of up to 10,000 lines, adds only the lines it does not have, so an interrupted import is resumed by running it again. The answer carries the estimate of what is left to file, before any file is sent. A line repeated with another hash, or without a usable reference (1 to 200 characters), is listed in rejected and changes nothing. A line that cannot be filed (a bad name, an unknown class, a Locked class without the Locked add-on) is refused with why; a file the organisation has already filed, or an earlier line of the import carries, is a duplicate and is not filed again. Ledgered as import.started.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • reference queryoptional Type: string

    The import's reference, for a CSV manifest.

  • source_system queryoptional Type: string

    The system the archive comes from, for a CSV manifest.

Request body

FolioImportManifest

  • reference required Type: string

    The import's name, chosen by you: the same reference resumes the same import.

  • source_system required Type: string
  • items required Type: FolioImportManifestLine[]

Returns

  • 200 Type: FolioImportAccepted
    The import, and what this call added
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 413 Type: Problem
    too_large
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /folio/imports · curl
curl -X POST "https://api.unforged.sh/folio/imports" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{
  "reference": "legacy-2026",
  "source_system": "LegacyDMS",
  "items": [
    {
      "reference": "dms/1",
      "folder": "archive/2020",
      "name": "letter.pdf",
      "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
      "size": 18234,
      "original_date": "2020-01-15"
    }
  ]
}'
POST /folio/imports · Python
import requests

r = requests.post(
    "https://api.unforged.sh/folio/imports",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={
        "reference": "legacy-2026",
        "source_system": "LegacyDMS",
        "items": [
            {
                "reference": "dms/1",
                "folder": "archive/2020",
                "name": "letter.pdf",
                "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "size": 18234,
                "original_date": "2020-01-15"
            }
        ]
    },
)
print(r.json())
POST /folio/imports · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({
    "reference": "legacy-2026",
    "source_system": "LegacyDMS",
    "items": [
      {
        "reference": "dms/1",
        "folder": "archive/2020",
        "name": "letter.pdf",
        "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
        "size": 18234,
        "original_date": "2020-01-15"
      }
    ]
  }),
});
console.log(await res.json());
Response 200
{
  "import": {
    "id": "imp_01J9X3M8V7K2Q4R5S6T7U8V9W0",
    "reference": "legacy-2026",
    "source_system": "LegacyDMS",
    "state": "open",
    "counts": {
      "total": 2,
      "awaiting": 2,
      "queued": 0,
      "filed": 0,
      "duplicate": 0,
      "refused": 0,
      "failed": 0
    },
    "bytes_expected": 36468,
    "bytes_filed": 0,
    "bytes_to_file": 36468,
    "percent": 0,
    "created_at_ms": 1790000000000,
    "closed_at_ms": null,
    "completed_at_ms": null
  },
  "added": 2,
  "unchanged": 0,
  "rejected": [],
  "estimate": {
    "estimate": {
      "files": 2,
      "bytes": 36468,
      "pages": 2,
      "profile": "reference",
      "filing_minor": null,
      "reading_minor": null,
      "storage_month_minor": null,
      "first_month_minor": null
    },
    "priced": false
  }
}

An import's progress

GET /folio/imports/{id}

Needs folio:records:read: See filed records, folders and metadata

Line counts by state, bytes declared, filed and still to file, the percentage finished, and the estimate of what is left to file (filing, reading at least a page a file, and a month of storage on the reference profile; amounts are null until prices are set). open takes lines and files; closed takes no more lines; complete once nothing is waiting or queued.

Parameters

  • id pathrequired Type: string

    A record, queued filing or export id.

Returns

  • 200
    The import
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/imports/{id} · curl
curl "https://api.unforged.sh/folio/imports/{id}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/imports/{id} · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/imports/{id}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/imports/{id} · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Send one line's file

POST /folio/imports/{id}/content

Needs folio:records:write: File records, create folders, extend retention

The request body is the file of the manifest line named by reference. It must match the line's SHA-256 (otherwise 400, and nothing changes). It is then filed through the same pipeline as POST /folio/records (the same scan, rules, ledger and receipt), as bulk work, with the import's provenance and the line's original date. A line already sent is answered as it stands, so a resumed upload changes nothing. While the organisation has as much import work queued as it may, the answer is 429 rate_limited: send it again shortly.

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • id pathrequired Type: string

    A record, queued filing or export id.

  • reference queryrequired Type: string

    The manifest line's reference.

Returns

  • 202 Type: FolioImportLine
    The line, queued (or a duplicate)
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 413 Type: Problem
    too_large
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /folio/imports/{id}/content · curl
curl -X POST "https://api.unforged.sh/folio/imports/{id}/content?reference=<reference>" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
POST /folio/imports/{id}/content · Python
import requests

r = requests.post(
    "https://api.unforged.sh/folio/imports/{id}/content?reference=<reference>",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
POST /folio/imports/{id}/content · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}/content?reference=<reference>', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());
Response 202
{
  "seq": 1,
  "reference": "dms/1",
  "folder": "archive/2020",
  "name": "letter.pdf",
  "sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "size": 18234,
  "state": "queued",
  "record_id": null,
  "duplicate_of": null,
  "error": null
}

An import's lines

GET /folio/imports/{id}/items

Needs folio:records:read: See filed records, folders and metadata

The lines in manifest order, optionally of one state: state=refused and state=failed are the per-line error report, each with why. Follow next_after. Ledgered as import.listed.

Parameters

  • id pathrequired Type: string

    A record, queued filing or export id.

  • state queryoptional Type: "awaiting" "queued" "filed" "duplicate" "refused" "failed"
  • after queryoptional Type: integer
  • limit queryoptional Type: integer

Returns

  • 200
    A page of lines
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/imports/{id}/items · curl
curl "https://api.unforged.sh/folio/imports/{id}/items" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/imports/{id}/items · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/imports/{id}/items",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/imports/{id}/items · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}/items', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Close an import

POST /folio/imports/{id}/close

Needs folio:records:write: File records, create folders, extend retention

The import takes no more lines, and lines still waiting for their file are failed. It completes once its queued files are filed (ledgered as import.closed, then import.completed with the counts).

Parameters

  • Idempotency-Key headerrequired Type: string

    1–255 printable ASCII characters, kept 24 hours.

  • id pathrequired Type: string

    A record, queued filing or export id.

Returns

  • 200 Type: FolioImport
    The import
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /folio/imports/{id}/close · curl
curl -X POST "https://api.unforged.sh/folio/imports/{id}/close" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
POST /folio/imports/{id}/close · Python
import requests

r = requests.post(
    "https://api.unforged.sh/folio/imports/{id}/close",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
POST /folio/imports/{id}/close · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}/close', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

An import's report

GET /folio/imports/{id}/report

Needs folio:records:read: See filed records, folders and metadata

The import's counts, every refused or failed line with why, and whether the filed lines reconcile with the ledger: one record.imported entry for each, and no other. Together with the hash manifest (GET /folio/imports/{id}/manifest.csv) it shows what was imported, when, from where and with which hashes. Ledgered as import.reported.

Parameters

  • id pathrequired Type: string

    A record, queued filing or export id.

Returns

  • 200 Type: FolioImportReport
    The report
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/imports/{id}/report · curl
curl "https://api.unforged.sh/folio/imports/{id}/report" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/imports/{id}/report · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/imports/{id}/report",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/imports/{id}/report · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}/report', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

An import's hash manifest

GET /folio/imports/{id}/manifest.csv

Needs folio:records:read: See filed records, folders and metadata

Every line as CSV: its line number, reference, folder, name, SHA-256, size, state, record, the line it duplicates and any error. Ledgered as import.reported.

Parameters

  • id pathrequired Type: string

    A record, queued filing or export id.

Returns

  • 200
    The hash manifest
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/imports/{id}/manifest.csv · curl
curl "https://api.unforged.sh/folio/imports/{id}/manifest.csv" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/imports/{id}/manifest.csv · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/imports/{id}/manifest.csv",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/imports/{id}/manifest.csv · Node.js
const res = await fetch('https://api.unforged.sh/folio/imports/{id}/manifest.csv', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

List folio S3 keys

GET /folio/s3/keys

Needs folio:settings:manage: Retention classes, reason settings, protected folders, webhooks

The access keys S3 clients use with folio S3 that can still be used: long-lived keys not revoked, and reason-session credentials not yet expired. Never a secret. Also names the organisation's bucket and region.

Returns

  • 200 Type: FolioS3KeyList
    The keys
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
GET /folio/s3/keys · curl
curl "https://api.unforged.sh/folio/s3/keys" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
GET /folio/s3/keys · Python
import requests

r = requests.get(
    "https://api.unforged.sh/folio/s3/keys",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
GET /folio/s3/keys · Node.js
const res = await fetch('https://api.unforged.sh/folio/s3/keys', {
  method: 'GET',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Create a folio S3 key

POST /folio/s3/keys

Needs folio:settings:manage: Retention classes, reason settings, protected folders, webhooks

A long-lived access key for S3 clients (SigV4), acting as s3_key:<id>. Its permissions are folio permissions within yours. The secret is shown once. No Idempotency-Key: a retry creates another key. At most 20 keys at once (409).

Request body

FolioS3KeyCreate

  • name optional Type: string null
  • permissions required Type: string[]

Returns

  • 201 Type: FolioS3KeyCreated
    Created
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 409 Type: Problem
    conflict, idempotency_conflict, idempotency_in_progress or idempotency_not_replayable
  • 413 Type: Problem
    too_large
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /folio/s3/keys · curl
curl -X POST "https://api.unforged.sh/folio/s3/keys" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /folio/s3/keys · Python
import requests

r = requests.post(
    "https://api.unforged.sh/folio/s3/keys",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={},
)
print(r.json())
POST /folio/s3/keys · Node.js
const res = await fetch('https://api.unforged.sh/folio/s3/keys', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({}),
});
console.log(await res.json());

Revoke a folio S3 key

DELETE /folio/s3/keys/{id}

Needs folio:settings:manage: Retention classes, reason settings, protected folders, webhooks

The key stops working at once. 404 for an unknown or already revoked key.

Parameters

  • id pathrequired Type: string

Returns

  • 204
    Revoked
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
DELETE /folio/s3/keys/{id} · curl
curl -X DELETE "https://api.unforged.sh/folio/s3/keys/{id}" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14"
DELETE /folio/s3/keys/{id} · Python
import requests

r = requests.delete(
    "https://api.unforged.sh/folio/s3/keys/{id}",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json())
DELETE /folio/s3/keys/{id} · Node.js
const res = await fetch('https://api.unforged.sh/folio/s3/keys/{id}', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json());

Open a reason session for S3 tools

POST /folio/s3/sessions

Needs folio:records:read: See filed records, folders and metadata

For S3 tools that cannot send reason headers: opens a reason as POST /folio/reasons does (at most 20 minutes) and returns short-lived S3 credentials bound to it. They act as you, reach only the reason's context (a folder and its sub-folders, a matter, a subject or records), and stop when the reason ends. permissions default to your folio permissions. The secret is shown once; no Idempotency-Key.

Request body

FolioS3SessionCreate

  • code required Type: string

    One of the organisation's reason codes.

  • text optional Type: string null
  • matter optional Type: string null
  • context required Type: object
  • seconds optional Type: integer

    Requested lifetime; capped by the organisation and at 20 minutes.

  • permissions optional Type: string[] null

Returns

  • 201 Type: FolioS3Session
    Opened
  • 400 Type: Problem
    invalid_request, invalid_version, idempotency_key_required or invalid_signature
  • 401 Type: Problem
    unauthorized
  • 403 Type: Problem
    permission_denied, org_suspended, or address_not_allowed (your organisation only accepts requests from the addresses it has listed)
  • 404 Type: Problem
    not_found
  • 413 Type: Problem
    too_large
  • 421 Type: Problem
    wrong_region
  • 429 Type: Problem
    rate_limited or quota_exceeded
  • default Type: Problem
    internal
POST /folio/s3/sessions · curl
curl -X POST "https://api.unforged.sh/folio/s3/sessions" \
  -H "Authorization: Bearer $UNFORGED_KEY" \
  -H "Unforged-Version: 2026-10-14" \
  -H "Content-Type: application/json" \
  -d '{}'
POST /folio/s3/sessions · Python
import requests

r = requests.post(
    "https://api.unforged.sh/folio/s3/sessions",
    headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
    json={},
)
print(r.json())
POST /folio/s3/sessions · Node.js
const res = await fetch('https://api.unforged.sh/folio/s3/sessions', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
  body: JSON.stringify({}),
});
console.log(await res.json());