95,615 people have shared 461,535 documents, viewed 9,802,508 times.

API reference

Publish documents and pages as tracked links, make a personal link per person, change link settings, and read who opened them, form responses and signatures. JSON over HTTPS at https://linkjar.com/api/v1/.

Prefer a command line? The CLI wraps all of this. Using Claude? Try the connector or the agent skills.

Authentication

Send a bearer token with every request:

Authorization: Bearer <token>
  • API key (starts with lj_) for servers and CI. Create one in Settings → Claude, CLI & API; revoke it there too.
  • OAuth for apps acting for a user: OAuth 2.1 with PKCE and dynamic client registration, discoverable at /.well-known/oauth-authorization-server. This is what linkjar login and the Claude connector use.

Reading needs the read scope; everything else needs write. API keys have both. Every call acts on your current workspace, and {ref} in a path is a document's link, slug or id.

Errors & limits

Errors are JSON: {"error": "message"}. Plan limits (403) also include upgrade_url.

400Something in the request is wrong; error says what.
401Missing, invalid or revoked token.
403The feature or limit needs a paid plan. The body includes upgrade_url.
404No such document (or not in your workspace).
409Not ready yet (a signed PDF still being made).
429Rate limited: publishing and updates are capped at 30 an hour per user.
500Our fault. Try again; it is logged.

Account

Who the token belongs to

GET /api/v1/me scope: read

The signed-in user and the workspace the API acts on. A quick way to check a key works.

Request

curl https://linkjar.com/api/v1/me \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

{
  "user": {
    "email": "[email protected]"
  },
  "workspace": {
    "uuid": "0b5f…",
    "name": "Acme",
    "plan": "Pro"
  },
  "app_url": "{base}"
}

Documents

List documents

GET /api/v1/documents scope: read

Published documents in the workspace, newest first.

Parameters

query query · string
Only titles containing this text.
limit query · integer
1–100, default 20.

Request

curl https://linkjar.com/api/v1/documents \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

{
  "documents": [
    {
      "title": "Acme proposal",
      "link": "https://pdflink.to/acme-proposal/",
      "slug": "acme-proposal",
      "kind": "html",
      "views": 3,
      "created": "2026-10-15"
    }
  ]
}

Errors: 400 limit is not a number.

Publish a document

POST /api/v1/documents scope: write

Publishes a file or an HTML page and returns its link. Send either JSON with html (one complete, self-contained HTML document) or multipart/form-data with a file: PDF, Word, PowerPoint, Excel, .html/.htm, or a .zip of a website with index.html at its top. Office files are converted to PDF first (status is Converting for a minute or two; the link works once it's done). Every upload is scanned; a page held for review has review_status: "held" until approved. Settings that need a paid plan are checked before anything is created.

Body (JSON, or the same fields as multipart form data)

title string
Title shown to viewers.
html string
The page (JSON only), up to 5 MB.
password string
Visitors need this password to open the link (4+ characters).
require_email boolean
Ask viewers for their email first (Pro).
allowed_email_domains string
Only accept emails at these comma-separated domains, e.g. "acme.com, acme.co.uk".
expires_on string
YYYY-MM-DD after which the link stops working (paid plans).
cta_label string
Call-to-action button text, e.g. "Book a call".
cta_url string
Where the button goes (full https:// URL).

Request

curl -X POST https://linkjar.com/api/v1/documents \
  -H "Authorization: Bearer $LINKJAR_API_KEY" \
  -F [email protected] \
  -F title="Q4 deck" -F password=hunter22

Response 201

{
  "title": "Acme proposal",
  "link": "https://pdflink.to/acme-proposal/",
  "slug": "acme-proposal",
  "uuid": "6f1c2d9e-8a7b-4c3d-9e2f-1a2b3c4d5e6f",
  "kind": "html",
  "status": "Published",
  "review_status": null,
  "views": 3,
  "created": "2026-10-15T09:00:00+00:00",
  "settings_url": "{base}/document/6f1c2d9e-8a7b-4c3d-9e2f-1a2b3c4d5e6f/update/",
  "settings": {
    "password": false,
    "require_email": false,
    "allowed_email_domains": "",
    "expires_at": null,
    "cta_label": "Book a call",
    "cta_url": "https://cal.com/you/intro"
  },
  "warnings": [],
  "message": "Published \"Acme proposal\": https://pdflink.to/acme-proposal/"
}

Errors: 400 No html or file, an unsupported file type, or a bad setting. · 403 Document limit reached, or a setting needs a paid plan (upgrade_url included). · 429 More than 30 publishes in an hour.

Get a document

GET /api/v1/documents/{ref} scope: read

A document with its link, status, view count and link settings.

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.

Request

curl https://linkjar.com/api/v1/documents/acme-proposal \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

{
  "title": "Acme proposal",
  "link": "https://pdflink.to/acme-proposal/",
  "slug": "acme-proposal",
  "uuid": "6f1c2d9e-8a7b-4c3d-9e2f-1a2b3c4d5e6f",
  "kind": "html",
  "status": "Published",
  "review_status": null,
  "views": 3,
  "created": "2026-10-15T09:00:00+00:00",
  "settings_url": "{base}/document/6f1c2d9e-8a7b-4c3d-9e2f-1a2b3c4d5e6f/update/",
  "settings": {
    "password": false,
    "require_email": false,
    "allowed_email_domains": "",
    "expires_at": null,
    "cta_label": "Book a call",
    "cta_url": "https://cal.com/you/intro"
  }
}

Errors: 404 No such document in your workspace.

Change link settings

PATCH /api/v1/documents/{ref} scope: write

Only the fields you send change. Use the remove_* flags to turn a setting off.

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.

Body

title string
New title.
password string
Visitors need this password to open the link (4+ characters).
require_email boolean
Ask viewers for their email first (Pro).
allowed_email_domains string
Only accept emails at these comma-separated domains, e.g. "acme.com, acme.co.uk".
expires_on string
YYYY-MM-DD after which the link stops working (paid plans).
cta_label string
Call-to-action button text, e.g. "Book a call".
cta_url string
Where the button goes (full https:// URL).
remove_password boolean
Turn password protection off.
remove_expiry boolean
Remove the expiry date.
remove_cta boolean
Remove the button.

Request

curl -X PATCH https://linkjar.com/api/v1/documents/acme-proposal \
  -H "Authorization: Bearer $LINKJAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"password": "hunter22", "expires_on": "2026-12-31"}'

Response 200

{
  "link": "https://pdflink.to/acme-proposal/",
  "changed": [
    "password",
    "expiry"
  ],
  "message": "Updated https://pdflink.to/acme-proposal/: password, expiry."
}

Errors: 400 A bad value, e.g. expires_on not a date. · 403 The setting needs a paid plan. · 404 No such document.

Replace what a link shows

PUT /api/v1/documents/{ref}/content scope: write

Swaps in a new version and keeps the same link, so everyone who has it sees the update. Send JSON html for web pages, or a multipart file of the same kind (a PDF for a PDF, HTML/zip for a page).

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.

Multipart fields

file
The new version.
title
Optional new title.

Request

curl -X PUT https://linkjar.com/api/v1/documents/acme-proposal/content \
  -H "Authorization: Bearer $LINKJAR_API_KEY" \
  -F [email protected]

Response 200

{
  "link": "https://pdflink.to/acme-proposal/",
  "message": "Updated https://pdflink.to/acme-proposal/ (same link).",
  "warnings": []
}

Errors: 400 A different kind of file than the original. · 403 Update limit reached on the free plan. · 404 No such document. · 429 Too many updates in an hour.

Tracking

Who opened it

GET /api/v1/documents/{ref}/activity scope: read

Recent views: who (when they used a personal link or the email gate), when, where and on what device. On Pro also time spent, how far they got (completion_pct), clicks (engagement) and, for web pages, the headings they scrolled to (sections).

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.
days query · integer
Look back this many days (default 90).
limit query · integer
Most recent views to return, 1–50 (default 20).

Request

curl https://linkjar.com/api/v1/documents/acme-proposal/activity \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

{
  "title": "Acme proposal",
  "total_views": 3,
  "views": [
    {
      "when": "2026-10-15T09:14:00+00:00",
      "who": "Sam Lee",
      "where": "Chicago, Illinois, United States",
      "device": "Chrome, Mac",
      "time": "3m 10s",
      "completion_pct": 94,
      "engagement": "read 94% of the page; clicked Book a call",
      "sections": [
        "Summary",
        "Pricing"
      ]
    }
  ],
  "message": "\"Acme proposal\": 3 views in total.\n- 2026-10-15 09:14: Sam Lee (Chicago, …)"
}

Errors: 404 No such document.

Form responses and signatures

GET /api/v1/documents/{ref}/responses scope: read

What people filled in and submitted on a fillable PDF, newest first. answers lists them in the PDF's order with readable labels (the owner's names for the fields, else ones read from the PDF); fields has the same answers keyed by the PDF's own field names. Signed responses have signed: true, the signer, an audit trail (time, IP, location, device, consent, document hashes) and a signed_pdf download URL. Taking submissions is on paid plans; past responses stay readable.

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.
limit query · integer
1–200, default 50.

Request

curl https://linkjar.com/api/v1/documents/acme-proposal/responses \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

{
  "title": "Service agreement",
  "total": 1,
  "responses": [
    {
      "id": "0d6c2b1e-6a1f-4f0e-9a51-1e2b8c7d9f00",
      "when": "2026-10-15T18:00:00+00:00",
      "who": "Sam Lee",
      "email": "[email protected]",
      "fields": {
        "full_name": "Sam Lee",
        "agree": "Yes",
        "signature": {
          "type": "signature",
          "signer_name": "Sam Lee",
          "method": "draw"
        }
      },
      "answers": [
        {
          "name": "full_name",
          "label": "Full name",
          "value": "Sam Lee"
        },
        {
          "name": "agree",
          "label": "I agree to the terms",
          "value": "Yes"
        },
        {
          "name": "signature",
          "label": "Signature",
          "value": {
            "type": "signature",
            "signer_name": "Sam Lee",
            "method": "draw"
          }
        }
      ],
      "signed": true,
      "signer_name": "Sam Lee",
      "signed_pdf_ready": true,
      "audit": {
        "signed_at": "2026-10-15T18:00:00+00:00",
        "ip": "203.0.113.9",
        "location": "Chicago, Illinois, United States"
      },
      "signed_pdf": "{base}/api/v1/documents/service-agreement/responses/0d6c2b1e-6a1f-4f0e-9a51-1e2b8c7d9f00/signed.pdf"
    }
  ],
  "message": "\"Service agreement\": 1 response."
}

Errors: 404 No such document.

Download a signed PDF

GET /api/v1/documents/{ref}/responses/{response_uuid}/signed.pdf scope: read

The signed copy of a response: the answers and signature drawn in, plus a signature certificate page.

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.
response_uuid path · string · required
The response id from the responses endpoint.

Request

curl -o signed.pdf https://linkjar.com/api/v1/documents/acme-proposal/responses/0d6c2b1e-6a1f-4f0e-9a51-1e2b8c7d9f00/signed.pdf \
  -H "Authorization: Bearer $LINKJAR_API_KEY"

Response 200

The file (application/pdf).

Errors: 404 No signed response with that id. · 409 The signed copy is still being made; try again in a minute.

Sharing

Make a personal link

POST /api/v1/documents/{ref}/recipients scope: write

A link for one person, so their views (and form responses) show up under their name.

Parameters

ref path · string · required
The document: its link, slug (the part after the domain) or id.

Body

name string
Their name.
email string
Their email. Give a name, an email or both.

Request

curl -X POST https://linkjar.com/api/v1/documents/acme-proposal/recipients \
  -H "Authorization: Bearer $LINKJAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Sam Lee", "email": "[email protected]"}'

Response 201

{
  "link": "https://pdflink.to/acme-proposal/?r=6r3cpddgxy",
  "message": "Link for Sam Lee: https://pdflink.to/acme-proposal/?r=6r3cpddgxy"
}

Errors: 400 Neither name nor email. · 404 No such document.

Create a room

POST /api/v1/rooms scope: write

Several documents behind one link, e.g. a deal room. Pro.

Body

title string
Room title.
documents array
Links or slugs, in display order (up to 50).
description string
Optional text shown at the top.
require_email boolean
Ask visitors for their email once.

Request

curl -X POST https://linkjar.com/api/v1/rooms \
  -H "Authorization: Bearer $LINKJAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title": "Acme deal room", "documents": ["acme-proposal", "case-study"], "require_email": true}'

Response 201

{
  "link": "https://pdflink.to/r/acme-deal-room/",
  "message": "Room \"Acme deal room\" with 2 documents: https://pdflink.to/r/acme-deal-room/"
}

Errors: 400 documents is not a list. · 403 Rooms need Pro. · 404 One of the documents is not yours.