All API documentation

Publications API

Automate PDF publications, replacements and reading analytics.

Authentication and limits

Use workspace keys with publications:read and/or publications:write on a Publications entitlement that includes API access. QR-only keys do not receive publication access automatically. All APIs share the existing 120 requests/minute/workspace limit. Send an Idempotency-Key for each mutation; reuse it only with the same request.

Upload PDFs privately in three steps: reserve, upload directly to storage, finalize. Maximum 20 MB and 300 pages. Finalization rejects malformed, encrypted, interactive-form, script-bearing and embedded-file PDFs. Existing PDFs remain live until replacement validation succeeds.

Endpoints

MethodPathAction
GET, POST/publicationsList / create draft
GET, PATCH, DELETE/publications/{id}Read / update / delete
POST/publications/{id}/uploadsReserve PDF upload
POST/publications/{id}/finalizeValidate and commit
POST/publications/{id}/duplicateDuplicate as draft
GET/publications/{id}/analyticsReading analytics

Create, upload, publish

// Backend only. Keep the workspace key secret.
const base = process.env.LINKWAVE_URL + '/api/v1/publications';
async function call(path, method, body, key) {
  const response = await fetch(base + path, {
    method,
    headers: {
      Authorization: 'Bearer ' + process.env.LINKWAVE_API_KEY,
      'Content-Type': 'application/json', 'Idempotency-Key': key
    }, body: JSON.stringify(body)
  });
  const result = await response.json();
  if (!response.ok) throw new Error(result.error.message);
  return result.data;
}
const pub = await call('', 'POST', { title: 'Catalogue' }, 'catalogue:create:v1');
const upload = await call('/' + pub.id + '/uploads', 'POST',
  { bytes: pdfBytes.length }, 'catalogue:upload:v1');
const stored = await fetch(upload.signed_url, {
  method: 'PUT', headers: { 'Content-Type': 'application/pdf', 'x-upsert': 'false' },
  body: pdfBytes
});
if (!stored.ok) throw new Error('PDF upload failed');
await call('/' + pub.id + '/finalize', 'POST',
  { upload_id: upload.upload_id }, 'catalogue:finalize:v1');
const published = await call('/' + pub.id, 'PATCH',
  { status: 'published' }, 'catalogue:publish:v1');
console.log(published.viewer_url, published.embed_url);

Replacement and access

Repeat uploads and finalize against the same ID with new keys to replace a PDF. The viewer URL and QR codes stay unchanged. PATCH status to archived to stop public access. Visibility workspace requires active membership. Download and print settings control viewer actions, not DRM.

Analytics include deduplicated reading sessions, pages visited, active-tab heartbeats, and supported PDF link clicks. They are approximate client-reported metrics, not verified human readership. OCR is not included.

OpenAPI specification