Records
File documents, read them back, and keep them warm when you need them often.
The FolioRecord object
Attributes
idrequired Type:stringfolderrequired Type:stringnamerequired Type:stringsizerequired Type:integersha256required Type:stringclass_idrequired Type:stringclass_versionoptional Type:integerlockrequired Type:stringThe retention lock:
retainedorlocked.earliest_deleteoptional Type:stringnullstaterequired Type:stringmatteroptional Type:stringnullsubjectoptional Type:stringnullmetadataoptional Type:anyreceiptoptional Type:anyfiled_byoptional Type:stringfiled_at_msoptional Type:integernullattachmentsoptional Type:object[]Evidence filed with the document, each
{kind, sha256, body}; a Nib result isnib_result.nib_verdictoptional Type:stringThe Nib decision filed with the record, or
no verdict recorded.storageoptional Type:FolioStoragenullWhere it is kept (UNF-132); null when that could not be worked out.
The FolioReceipt object
The receipt for a filed document.
Attributes
record_idrequired Type:stringsha256required Type:stringsizeoptional Type:integerclass_idoptional Type:stringearliest_deleteoptional Type:stringnulllockoptional Type:string
List records
GET /folio/records
Needs folio:records:read: See filed records, folders and metadata
Listing needs no reason but is audited; the ledger records what was listed and which records were shown.
Parameters
folderqueryoptional Type:stringrecursivequeryoptional Type:booleanafterqueryoptional Type:stringThe previous page's
next_after.limitqueryoptional Type:integer
Returns
200Type:FolioRecordPageA page of records400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); File a document
POST /folio/records
Needs folio:records:write: File records, create folders, extend retention
The request body is the file (at most 50 MB here; for a larger file, up to 2 GiB, use POST /folio/uploads, or the S3 gateway). Filing needs no reason. The file is scanned for malware first: an infected file is refused (400, ledgered, nothing stored) and while no scanner can answer filing is refused (503 not_ready). The response is the receipt: the document's SHA-256, its retention class and lock, and the ledger position, returned once the object is locked. Folder and ancestors are created; the folder's matter and subject are inherited. A mistaken filing cannot be edited or replaced: file the correct document separately.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
folderqueryoptional Type:stringnamequeryrequired Type:stringclassqueryoptional Type:stringThe retention class; the organisation's default when omitted.
matterqueryoptional Type:stringsubjectqueryoptional Type:stringsource_systemqueryoptional Type:stringAn import's origin (kept as provenance and ledgered as
record.imported).source_refqueryoptional Type:stringoriginal_datequeryoptional Type:stringThe original created or event date; starts retention for classes that count from an event.
Returns
201Type:FolioReceiptFiled400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceeded503Type:Problembusy,not_ready,org_movingorstorage_provisioning(your organisation's storage is still being set up); retry afterRetry-AfterdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records?name=<name>" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.post(
"https://api.unforged.sh/folio/records?name=<name>",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records?name=<name>', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Open a record
GET /folio/records/{id}
Needs folio:records:read: See filed records, folders and metadata
The record's details and receipt. A view, so a reason is required where the organisation setting or a protected folder says so.
Parameters
idpathrequired Type:stringUnforged-Reason-Idheaderoptional Type:stringAn open reason (
POST /folio/reasons), when the organisation or the folder requires one for this view.Unforged-Reason-Codeheaderoptional Type:stringInstead of an open reason: a reason code for this one call (one of your organisation's codes). It is opened for exactly the records the call reads and ledgered like any reason.
Unforged-Reasonheaderoptional Type:stringThe reason's text, with
Unforged-Reason-Code(plain ASCII).
Returns
200Type:FolioRecordThe record400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region428Type:Problemreason_required429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records/{id}" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records/{id}",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Download a document
GET /folio/records/{id}/content
Needs folio:documents:read: Open and download documents (a reason is recorded)
The ledger entry is committed before any byte is returned. The stored bytes are checked against the receipt's hash on every whole read; a mismatch is an integrity alert and the transfer is cut short. Send Range to read part of a document (one range, at most 64 MiB a time): the answer is 206 with Content-Range, and each such request is a view like any other. A range that starts past the end is 416, and nothing is ledgered or counted. Every read counts the bytes it sends toward the download allowance.
Parameters
idpathrequired Type:stringUnforged-Reason-Idheaderoptional Type:stringAn open reason (
POST /folio/reasons), when the organisation or the folder requires one for this view.Unforged-Reason-Codeheaderoptional Type:stringInstead of an open reason: a reason code for this one call (one of your organisation's codes). It is opened for exactly the records the call reads and ledgered like any reason.
Unforged-Reasonheaderoptional Type:stringThe reason's text, with
Unforged-Reason-Code(plain ASCII).Rangeheaderoptional Type:stringOne range of bytes:
bytes=<first>-<last>,bytes=<first>-orbytes=-<count>(the last bytes). At most 64 MiB is sent at a time, cut from the end;Content-Rangesays what was sent. Any other form, or several ranges, is answered with the whole document.If-Rangeheaderoptional Type:stringThe
ETagof an earlier answer. Anything else, such as a date, is answered with the whole document.
Returns
200The document206The part of the document aRangeasked for400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable416Type:Probleminvalid_request421Type:Problemwrong_region428Type:Problemreason_required429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records/{id}/content" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records/{id}/content",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/content', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Extend a record's retention
POST /folio/records/{id}/retention
Needs folio:records:write: File records, create folders, extend retention
Retention only moves later. The object store's lock is extended first, then the record and the ledger.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
earliest_deleterequired Type:stringcoderequired Type:stringtextoptional Type:stringnull
Returns
200Extended400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/{id}/retention" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/{id}/retention",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/retention', {
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()); Ask to delete a record before its retention ends
POST /folio/records/{id}/early-delete
Needs folio:records:delete: Request disposal and early deletion
Only Retained records; a Locked record has no such path (409). Nothing is deleted until a different person approves with POST /folio/approvals/{id}/approve; a record under a legal hold cannot be deleted until the hold is released. The disposition certificate says the deletion was early and names both people.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
coderequired Type:stringtextoptional Type:stringnull
Returns
201Type:FolioApprovalThe approval request400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/{id}/early-delete" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/{id}/early-delete",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/early-delete', {
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()); Move a record to another folder
POST /folio/records/{id}/move
Needs folio:records:write: File records, create folders, extend retention
A change, so a reason is required; ledgered with the folder before and after. The stored file, its hashes, class and retention never change. Moving a record out of a protected folder answers 202 with the approval a different person must give (POST /folio/approvals/{id}/approve, which then also needs folio:records:write). 409 when the record is under a hold or the folder already has a file of that name.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
folderrequired Type:stringcoderequired Type:stringtextoptional Type:stringnull
Returns
200Type:FolioMoveMoved202Type:FolioApprovalWaiting for a second person400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/{id}/move" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/{id}/move",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/move', {
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()); Queue a document for filing
POST /folio/filings
Needs folio:records:write: File records, create folders, extend retention
The request body is the file. For bulk and import callers: the file is checked, held in folio's staging area and filed by a background job through the same pipeline as POST /folio/records (the same scan, rules, ledger and billing). Follow it with GET /folio/filings/{id}. No reason is needed. A file over 50 MB goes through POST /folio/uploads instead.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
folderqueryoptional Type:stringnamequeryrequired Type:stringclassqueryoptional Type:stringThe retention class; the organisation's default when omitted.
matterqueryoptional Type:stringsubjectqueryoptional Type:stringsource_systemqueryoptional Type:stringAn import's origin (kept as provenance and ledgered as
record.imported).source_refqueryoptional Type:stringoriginal_datequeryoptional Type:stringThe original created or event date; starts retention for classes that count from an event.
Returns
202Type:FolioQueuedFilingQueued400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/filings?name=<name>" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.post(
"https://api.unforged.sh/folio/filings?name=<name>",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/filings?name=<name>', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); {
"id": "fil_01J9X3M8V7K2Q4R5S6T7U8V9W0",
"state": "queued",
"folder": "import",
"name": "letter.pdf",
"size": 18234,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"record_id": null,
"error": null,
"created_at_ms": 1790000000000,
"finished_at_ms": null
} A queued filing
GET /folio/filings/{id}
Needs folio:records:read: See filed records, folders and metadata
queued, then filed with its record_id, refused with why (a bad name, a clash, a file the malware scan refused), or failed after the last retry.
Parameters
idpathrequired Type:stringA record, queued filing or export id.
Returns
200Type:FolioQueuedFilingThe queued filing400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/filings/{id}" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/filings/{id}",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/filings/{id}', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Your open large uploads
GET /folio/uploads
Needs folio:records:read: See filed records, folders and metadata
The caller's own large uploads that are still uploading or checking, newest first (at most 100), and intake_origin, the origin every part's URL names. Choosing the same file again carries on where an upload stopped.
Returns
200Type:FolioUploadListThe open uploads400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/uploads" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/uploads",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Start a large upload
POST /folio/uploads
Needs folio:records:write: File records, create folders, extend retention
For a file over 50 MB, up to 2 GiB (2147483648 bytes). The file goes in parts straight to your organisation's own intake storage, then it is scanned whole and filed through the same pipeline as POST /folio/records. Give the filing's options and the file's size; the answer says how big each part is and how many there are. Then sign parts with POST /folio/uploads/{id}/parts, PUT each one to its URL, finish with POST /folio/uploads/{id}/complete and follow GET /folio/uploads/{id} to the record. 413 too_large over 2 GiB; 409 when the name is taken in that folder, the same file is already on its way, or your organisation has too many large uploads open; 503 org_moving while your organisation is moving region. Open to API keys with folio:records:write.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
Request body
folderoptional Type:stringnamerequired Type:stringclassoptional Type:stringThe retention class; the organisation's default when omitted.
matteroptional Type:stringsubjectoptional Type:stringsource_systemoptional Type:stringsource_refoptional Type:stringoriginal_dateoptional Type:stringsizerequired Type:integer
Returns
201Type:FolioUploadStartedStarted400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceeded503Type:Problembusy,not_ready,org_movingorstorage_provisioning(your organisation's storage is still being set up); retry afterRetry-AfterdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/uploads" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{
"folder": "scans/2026",
"name": "deeds-bundle.pdf",
"size": 734003200
}' import requests
r = requests.post(
"https://api.unforged.sh/folio/uploads",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={
"folder": "scans/2026",
"name": "deeds-bundle.pdf",
"size": 734003200
},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
body: JSON.stringify({
"folder": "scans/2026",
"name": "deeds-bundle.pdf",
"size": 734003200
}),
});
console.log(await res.json()); {
"id": "lup_01J9X3M8V7K2Q4R5S6T7U8V9W0",
"state": "uploading",
"size": 734003200,
"part_size": 33554432,
"part_count": 22,
"max_parts_in_flight": 4
} A large upload
GET /folio/uploads/{id}
Needs folio:records:read: See filed records, folders and metadata
uploading (with the parts that have arrived, each with its SHA-256, so carrying on sends only what is missing or different), then checking once it is complete, then filed with its record_id, refused with why (the malware scan, a file the scanner could not check whole, a clash), failed after the last retry, or aborted. Only the person or key that started it sees it; anyone else gets 404.
Parameters
idpathrequired Type:string
Returns
200Type:FolioUploadThe upload400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/uploads/{id}" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/uploads/{id}",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads/{id}', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); {
"id": "lup_01J9X3M8V7K2Q4R5S6T7U8V9W0",
"state": "checking",
"folder": "scans/2026",
"name": "deeds-bundle.pdf",
"size": 734003200,
"part_size": 33554432,
"part_count": 22,
"parts": [],
"bytes_received": 734003200,
"record_id": null,
"error": null,
"created_at_ms": 1790000000000,
"updated_at_ms": 1790000420000
} Stop a large upload
DELETE /folio/uploads/{id}
Needs folio:records:write: File records, create folders, extend retention
Stops an upload that is still uploading; whatever was sent is thrown away. One that has already finished answers as it stands; one that is checking can't be stopped (409).
Parameters
idpathrequired Type:string
Returns
200Type:FolioUploadThe upload,aborted400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X DELETE "https://api.unforged.sh/folio/uploads/{id}" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.delete(
"https://api.unforged.sh/folio/uploads/{id}",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads/{id}', {
method: 'DELETE',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Sign parts of a large upload
POST /folio/uploads/{id}/parts
Needs folio:records:write: File records, create folders, extend retention
Up to 8 parts at a time. Each part is named by its number (from 1), its size (every part but the last is exactly part_size bytes) and the base64 SHA-256 of its bytes. Send each part with the method, to the url, with exactly the headers given, before expires_at_ms (15 minutes); a part whose bytes don't match its SHA-256 is refused by storage and can be signed and sent again.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
partsrequired Type:object[]
Returns
200Type:FolioUploadSignedThe signed parts400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/uploads/{id}/parts" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{
"parts": [
{
"number": 1,
"size": 33554432,
"sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="
}
]
}' import requests
r = requests.post(
"https://api.unforged.sh/folio/uploads/{id}/parts",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={
"parts": [
{
"number": 1,
"size": 33554432,
"sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="
}
]
},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads/{id}/parts', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
body: JSON.stringify({
"parts": [
{
"number": 1,
"size": 33554432,
"sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="
}
]
}),
});
console.log(await res.json()); {
"parts": [
{
"number": 1,
"method": "PUT",
"url": "https://unf-prod-org-01j9x3m8v7-folio-intake.s3.eu-west-2.amazonaws.com/u/lup_01J9X3M8V7K2Q4R5S6T7U8V9W0?partNumber=1&uploadId=example&X-Amz-Algorithm=AWS4-HMAC-SHA256",
"headers": {
"x-amz-checksum-sha256": "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="
},
"expires_at_ms": 1790000900000
}
]
} Finish a large upload
POST /folio/uploads/{id}/complete
Needs folio:records:write: File records, create folders, extend retention
List every part once, with the SHA-256 you signed it for. The parts storage holds are checked against that list and the file's size, then the file is put together and queued to be scanned and filed: 202 with the upload checking. Calling it again answers the upload as it stands. 400 when a part is missing or isn't the one that was signed: send that part again and finish again.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
partsrequired Type:object[]
Returns
202Type:FolioUploadComplete, and queued for filing400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/uploads/{id}/complete" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/uploads/{id}/complete",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/uploads/{id}/complete', {
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()); {
"id": "lup_01J9X3M8V7K2Q4R5S6T7U8V9W0",
"state": "checking",
"folder": "scans/2026",
"name": "deeds-bundle.pdf",
"size": 734003200,
"part_size": 33554432,
"part_count": 22,
"parts": [],
"bytes_received": 734003200,
"record_id": null,
"error": null,
"created_at_ms": 1790000000000,
"updated_at_ms": 1790000420000
} File a Nib check's document with its result
POST /folio/records/from-check
Needs folio:records:write: File records, create folders, extend retention
Copies the checked document and its result into folio: the result is attached to the record as hashed evidence and the record's provenance names the check. The check and its result are unchanged. Also needs nib:checks:read. 409 when Nib no longer keeps the document.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
Request body
job_idrequired Type:stringitem_indexrequired Type:integerfolderoptional Type:stringnameoptional Type:stringThe file name in folio; the checked file's own name when omitted.
classoptional Type:stringmatteroptional Type:stringsubjectoptional Type:string
Returns
201Type:FolioReceiptFiled400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/from-check" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{
"job_id": "job-2291",
"item_index": 0,
"folder": "cases/2291"
}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/from-check",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={
"job_id": "job-2291",
"item_index": 0,
"folder": "cases/2291"
},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/from-check', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
body: JSON.stringify({
"job_id": "job-2291",
"item_index": 0,
"folder": "cases/2291"
}),
});
console.log(await res.json()); A record's custody report
GET /folio/records/{id}/custody
Needs folio:exports:create: Create exports, custody and integrity reports
Every ledger event about the record from filing to date, in order, with actors, reasons, times and each entry's place in the hash chain, plus a fresh integrity check. Also needs folio:audit:read. An audited read, ledgered as record.custody_reported; no reason.
Parameters
idpathrequired Type:stringA record, queued filing or export id.
Returns
200Type:FolioCustodyThe custody report400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records/{id}/custody" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records/{id}/custody",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/custody', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Check a record's integrity now
GET /folio/records/{id}/integrity
Needs folio:exports:create: Create exports, custody and integrity reports
Reads the stored bytes and compares their SHA-256 with the receipt (ok, mismatch or unreadable). A mismatch raises folio.integrity.alert. A clean result means the bytes are the ones filed; it does not say the document is genuine.
Parameters
idpathrequired Type:stringA record, queued filing or export id.
Returns
200Type:FolioIntegrityResultThe result400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records/{id}/integrity" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records/{id}/integrity",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/integrity', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); A short-lived download link
POST /folio/records/{id}/link
Needs folio:documents:read: Open and download documents (a reason is recorded)
A pre-signed URL for tools that cannot send a key or reason headers. Issuing it is a view (428 without a reason where the rules require one), ledgered as record.link_issued; it lasts seconds (default 300, at most 900) and every download through it is ledgered as a view by you, before any byte is returned. The url is shown once.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:stringA record, queued filing or export id.
Unforged-Reason-Idheaderoptional Type:stringAn open reason (
POST /folio/reasons), when the organisation or the folder requires one for this view.Unforged-Reason-Codeheaderoptional Type:stringInstead of an open reason: a reason code for this one call (one of your organisation's codes). It is opened for exactly the records the call reads and ledgered like any reason.
Unforged-Reasonheaderoptional Type:stringThe reason's text, with
Unforged-Reason-Code(plain ASCII).
Request body
secondsoptional Type:integer
Returns
201Type:FolioLinkThe link400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable421Type:Problemwrong_region428Type:Problemreason_required429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/{id}/link" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{
"seconds": 120
}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/{id}/link",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={
"seconds": 120
},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/link', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14', 'Content-Type': 'application/json' },
body: JSON.stringify({
"seconds": 120
}),
});
console.log(await res.json()); Where a record is kept, and its restore
GET /folio/records/{id}/restore
Needs folio:records:read: See filed records, folders and metadata
The record's storage (its tier, the storage class it is in, whether it needs a restore before it can be opened, the pin keeping it out of colder storage) and its open restore, or the latest one. Not a view: nothing of the document is shown.
Parameters
idpathrequired Type:stringA record, queued filing or export id.
Returns
200Where it is kept400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/records/{id}/restore" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/records/{id}/restore",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/restore', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Restore an archived record
POST /folio/records/{id}/restore
Needs folio:documents:read: Open and download documents (a reason is recorded)
Asks for a readable copy of a record that has moved to Glacier Flexible Retrieval or Deep Archive, kept for days (7 by default, 1 to 30). standard takes 3 to 5 hours from Flexible Retrieval and 9 to 12 from Deep Archive; bulk takes 5 to 12 and 24 to 48. The price is the same at either speed. Authorised and ledgered as a view (record.restore_requested), so the organisation's reason rules apply, and metered once. 202 when it is asked for now; 200 with the open restore when there already is one (it is not asked for or charged again); 409 not_archived when the record can be opened as it is. When it is ready the folio.restore.ready webhook is sent, and an email goes to notify_email when one is given.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:stringA record, queued filing or export id.
Unforged-Reason-Idheaderoptional Type:stringAn open reason (
POST /folio/reasons), when the organisation or the folder requires one for this view.Unforged-Reason-Codeheaderoptional Type:stringInstead of an open reason: a reason code for this one call (one of your organisation's codes). It is opened for exactly the records the call reads and ledgered like any reason.
Unforged-Reasonheaderoptional Type:stringThe reason's text, with
Unforged-Reason-Code(plain ASCII).
Request body
speedoptional Type:"standard""bulk"daysoptional Type:integerHow long the restored copy can be read.
notify_emailoptional Type:stringnullWho to email when it is ready.
Returns
200Type:FolioRestoreResultA restore was already open for this record; it is returned as it is202Type:FolioRestoreResultAsked for400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region428Type:Problemreason_required429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/records/{id}/restore" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/records/{id}/restore",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/records/{id}/restore', {
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()); List restores
GET /folio/restores
Needs folio:records:read: See filed records, folders and metadata
The organisation's restores, newest first. open=true keeps those asked for, running or ready.
Parameters
openqueryoptional Type:booleanbeforequeryoptional Type:stringThe
nextof the previous page.limitqueryoptional Type:integer
Returns
200The restores400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/restores" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/restores",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/restores', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); List pins
GET /folio/pins
Needs folio:records:read: See filed records, folders and metadata
Parameters
include_endedqueryoptional Type:boolean
Returns
200The pins, newest first400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl "https://api.unforged.sh/folio/pins" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" import requests
r = requests.get(
"https://api.unforged.sh/folio/pins",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/pins', {
method: 'GET',
headers: { Authorization: `Bearer ${process.env.UNFORGED_KEY}`, 'Unforged-Version': '2026-10-14' },
});
console.log(await res.json()); Keep a record or folder out of colder storage
POST /folio/pins
Needs folio:records:write: File records, create folders, extend retention
Pins a record, or a folder and everything below it, so it stops moving to colder storage. Anything already in an archive class stays there and still needs a restore to open: a pin stops future moves, it does not bring anything back. Storage is then charged at the rate of the class each document is in. A change, so a reason is required; ledgered (storage.pinned). Pinning what is already pinned answers its active pin.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
Request body
kindrequired Type:"record""folder"valuerequired Type:stringThe record id, or the folder (everything below it is covered too).
coderequired Type:stringtextoptional Type:stringnull
Returns
201Type:FolioPinPinned400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/pins" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/pins",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/pins', {
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()); End a pin
POST /folio/pins/{id}/end
Needs folio:records:write: File records, create folders, extend retention
The pinned records take their storage profile's moves again, by their age. A change, so a reason is required; ledgered (storage.unpinned). Ending an ended pin answers it as it is.
Parameters
Idempotency-Keyheaderrequired Type:string1–255 printable ASCII characters, kept 24 hours.
idpathrequired Type:string
Request body
coderequired Type:stringtextoptional Type:stringnull
Returns
200Type:FolioPinEnded400Type:Probleminvalid_request,invalid_version,idempotency_key_requiredorinvalid_signature401Type:Problemunauthorized403Type:Problempermission_denied,org_suspended, oraddress_not_allowed(your organisation only accepts requests from the addresses it has listed)404Type:Problemnot_found409Type:Problemconflict,idempotency_conflict,idempotency_in_progressoridempotency_not_replayable413Type:Problemtoo_large421Type:Problemwrong_region429Type:Problemrate_limitedorquota_exceededdefaultType:Probleminternal
curl -X POST "https://api.unforged.sh/folio/pins/{id}/end" \
-H "Authorization: Bearer $UNFORGED_KEY" \
-H "Unforged-Version: 2026-10-14" \
-H "Content-Type: application/json" \
-d '{}' import requests
r = requests.post(
"https://api.unforged.sh/folio/pins/{id}/end",
headers={"Authorization": f"Bearer {UNFORGED_KEY}", "Unforged-Version": "2026-10-14"},
json={},
)
print(r.json()) const res = await fetch('https://api.unforged.sh/folio/pins/{id}/end', {
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());