Documentation / REST API

Small API.
Useful output.

Publish a document, preserve a URL, organize a collection, or share reviewed continuation context. All endpoints are versioned under https://api.linkifact.com/v1.

Recoverable creation protocol: protocol 1 clients send X-Linkifact-Publish-Recovery: 1 with a pre-saved, CSPRNG-backed lfpr_ idempotency key on account publication. An unsupported publication-recovery version returns 400 unsupported_publish_recovery_protocol. The first-party browser also sends X-Linkifact-Expected-Account-SHA256 on irreversible authenticated operations. It is the lowercase SHA-256 of the opaque account ID that prepared the operation; the API verifies it before reading content or application state. API-key clients authenticate with their bearer key and omit this browser-only intent header.

Requests and identity

Publishing requires an account. Agent and backend clients use a scoped Linkifact API key. Browser sessions use secure host-only cookies on the API origin, and the app makes credentialed requests to that origin. Viewer and bundle links remain unlisted bearer URLs: readers do not sign in, but anyone who receives a URL can open it.

CONTENT TYPESapplication/json
multipart/form-data
AUTHORIZATIONAccount session
Scoped API key
API VERSIONv1

Artifact, collection, and session routes

POST/v1/artifacts

Publish authenticated JSON or multipart HTML

GET/v1/artifacts

List the current account's artifacts

GET/v1/artifacts/{id}

Read metadata as the owner

DELETE/v1/artifacts/{id}

Delete as the owner

POST/v1/artifacts/{id}/versions

Publish an authenticated version with If-Match

GET/v1/collections

List the current account's collections

POST/v1/collections

Create a collection

GET/v1/collections/{id}

Read an owned collection and its ordered items

PUT/v1/collections/{id}

Rename an owned collection

DELETE/v1/collections/{id}

Delete a collection, not its artifacts

POST/v1/collections/{id}/items

Add or reposition an owned artifact

DELETE/v1/collections/{id}/items/{artifactId}

Remove an item, not its artifact

POST/v1/sessions

Publish one reviewed, redacted session bundle

GET/v1/sessions

List the current account's shared sessions

GET/v1/sessions/{id}

Read metadata as the owner

DELETE/v1/sessions/{id}

Delete as the owner

POST/v1/session-abuse-reports

Report a shared session without downloading it

GET/v1/admin/session-abuse-reports

Page moderation reports by exact status

PUT/v1/admin/session-abuse-reports/{id}

Advance or close a moderation report

POST/v1/admin/sessions/{id}/quarantine

Remove an active session from public view

POST/v1/admin/sessions/{id}/restore

Restore an unexpired quarantined session

POST/v1/admin/sessions/{id}/delete

Permanently schedule a moderated session deletion

GET/v1/api-keys

List API key metadata for the current browser session

POST/v1/api-keys

Create a scoped API key; the token is returned once

DELETE/v1/api-keys/{id}

Revoke an API key from the current browser session

POST/v1/abuse-reports

Report an artifact without loading it on the trusted origin

POST /v1/artifacts

Send authenticated JSON with html and optional title, or multipart with a file part and optional title. Generate a 32-byte random value locally, encode it as 52 lowercase, unpadded RFC 4648 base32 characters, and prefix it with lfpr_. Durably save it before upload, send it as Idempotency-Key with X-Linkifact-Publish-Recovery: 1, and retry an uncertain response only with the exact same HTML, normalized title, and key. Use a new key after any change.

Artifacts use a script-free HTML/CSS profile. Inline CSS, same-document fragment links, native controls, embedded data, and compact data: or blob: media are accepted. Scripts, event handlers, forms, password inputs, frames, objects, popups, downloads, workers, and known active URL carriers are rejected. A deny-by-default CSP blocks any remaining network load, and the opaque sandbox prevents navigation of the parent page.

POST /v1/artifacts HTTP/1.1
Host: api.linkifact.com
Authorization: Bearer lfk_<account API key>
Content-Type: application/json
Idempotency-Key: lfpr_<52 lowercase base32 characters>
X-Linkifact-Publish-Recovery: 1

{
  "title": "Quarterly model",
  "html": "<!doctype html>..."
}

Response fields

id

Internal artifact identifier used by owner-management endpoints.

url

Public, unlisted linkifact.com/artifacts/<id> wrapper URL. Anyone with this bearer link can view.

expires_at

Exact instant the public URL stops serving the artifact.

POST /v1/sessions

Send one Linkifact Session Bundle v1 from an authenticated account after local redaction review. privacy_confirmed must be exactly true; unknown fields and invalid bundle shapes are rejected. The reviewed title, when present, belongs inside the bundle; an outer title override is rejected. Generate and durably save one CSPRNG-backed lfpr_ key before publication, send it as Idempotency-Key together with X-Linkifact-Publish-Recovery: 1, and reuse it only for uncertain retries of that exact reviewed bundle. Choose a new key after any change and re-review. Raw JSON ingress has a plan-specific streamed byte limit in addition to the canonical bundle limit; the historical 16 MiB portable-request ceiling remains an absolute bound, so heavily escaped Pro JSON can require a more compact wire representation. Before storing bytes, the server verifies publisher ledger claims against actual fixed markers, normalizes their counts, ignores caller-asserted server provenance, and applies a separately tagged mandatory server redaction pass. The public metadata count reports only fresh server detections. The public manifest commits last.

POST /v1/sessions HTTP/1.1
Host: api.linkifact.com
Authorization: Bearer lfk_<account API key>
Content-Type: application/json; charset=utf-8
Idempotency-Key: lfpr_<52 lowercase base32 characters>
X-Linkifact-Publish-Recovery: 1

{
  "privacy_confirmed": true,
  "bundle": {
    "$schema": "https://linkifact.com/schemas/session-bundle/v1",
    "kind": "linkifact.session-bundle",
    "version": 1,
    "exported_at": "2026-09-04T12:00:00.000Z",
    "source": { "adapter": "generic-json", "product": "Generic JSON", "format": "json" },
    "conversation": { "turns": [{ "role": "user", "kind": "message", "content": "Selected context" }] },
    "redactions": []
  }
}

Success returns id, the unlisted branded url, a cookie-free bundle_url, and exact expires_at. Share url as the human viewer and pass bundle_url to linkifact session import; the viewer page is not an import source. The bundle download uses application/vnd.linkifact.session+json as an attachment. Session source-adapter metadata is publisher-claimed, not authenticated provider provenance. Every session, bundle, management, error, and tombstone response is noindex. Unlisted is not private: anyone with a session or bundle URL can read it.

The API does not locate native transcripts or import into harness state. Use the session-exchange package or CLI to create a bundle from an explicitly selected source and render an imported bundle as untrusted continuation context.

Optimistic, immutable versions

Authenticated artifacts retain a stable public URL. Send the current version in If-Match when publishing a new version. The first-party browser saves a fresh lfpr_ idempotency key, the base-version ID, and an exact request digest before upload. If a response is uncertain, reselect the exact same HTML and title: the app reuses the saved key and original base even if the stable URL already advanced. A genuinely stale new attempt returns 409 Conflict; fetch fresh metadata before starting it.

Use the narrowest credential

Browser account routes use a secure session cookie. Agent clients send Authorization: Bearer lfk_…. Reading artifact metadata requires artifacts:read; publishing versions and deleting require artifacts:write; collection mutations require collections:write; shared-session metadata and publication use sessions:read and sessions:write. Moderation routes require the privileged admin:artifacts scope. API-key create, list, and revoke routes accept browser sessions only, so an API key cannot mint another key.

Create requests use { "name": "Codex on laptop", "scopes": ["artifacts:read", "artifacts:write", "sessions:read", "sessions:write"] }. Save the returned token immediately; only its hash is retained.

Assemble an unlisted collection

Create with { "name": "Release demos" }. Add or reposition an active owned artifact with { "artifact_id": "…", "position": 0 }; omit position to append. Collection responses use the branded https://linkifact.com/collections/<id> URL; its fixed wrapper loads the safely escaped renderer at https://pages.linkifact.com/c/<id>. It is unlisted and noindex, but anyone with its URL can view it.

Report without rendering

For an artifact, send artifact_id to /v1/abuse-reports. For a shared session, send session_id to /v1/session-abuse-reports. Both accept a reason of malware, phishing, harassment, copyright, privacy, or other, plus optional bounded details and contact. Browser reports also send the configured Turnstile token. A successful submission returns 201 with an opaque report ID and open status.

Triage, quarantine, restore, or delete

Moderators with admin:artifacts can list shared-session reports by an exact status of open, reviewing, resolved, or dismissed. limit is 1–100 and before is the opaque cursor returned as next_before. Update a report with { "status": "reviewing" }.

Quarantine, restore, and delete mutations require { "reason": "bounded moderator note" }. Restore accepts only an unexpired quarantined session (or an idempotent replay of a completed restore); it never extends expires_at. A manifest-commit failure fails closed back to quarantine. All moderation responses remain noindex and no-store, and report records should never be copied into public session URLs or crawlable pages.

Stable codes, useful request IDs

Non-2xx responses use one envelope. Branch on code, show message, and include request_id when reporting a problem.

{
  "error": {
    "code": "html_validation_failed",
    "message": "The HTML did not pass publication checks.",
    "request_id": "req_01J...",
    "details": {
      "issues": [{ "code": "external_url", "message": "..." }]
    }
  }
}

Documented error families cover authentication, validation, quota, expiry, conflict, plan limits, and quarantine.

Retry deliberately

Do not immediately retry 429. Honor Retry-After when it is present; otherwise inspect the structured quota window and wait for that UTC window to reset. Retry transient 5xx failures with exponential backoff and the same idempotency key. Do not retry policy violations or size errors unchanged.