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