Logo
Apdf tutorials July 2026 5 min read

How to Share PDF Links That Expire — or Revoke Them After Sending

The moment a PDF leaves your outbox as an attachment, you've lost it. The recipient changes jobs, the offer deadline passes, the rate card gets superseded — and the old file keeps circulating with your name on it.

Share the same PDF through a tracked link instead and you keep a kill switch: revoke one person without touching anyone else, restore them later if you were wrong, and expire whole batches automatically when their time is up.

What you'll build
A proposal shared with two stakeholders through individually revocable links — then one of them leaves the company, and you shut off just their access: Dana's link → 403 · Alex's link → still open · the reading history → still yours
Everything in this tutorial works on the free plan
1

Make the document private

Revocation only means something if the links are the only way in. Every uploaded document also has a bare viewer URL — flip the document to private so that URL stops working and access flows exclusively through the links you mint:

curl -X POST https://apdf.io/api/docs/4352c-99928-25aa1/private \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
{
    "data": {
        "doc_id": "4352c-99928-25aa1",
        "viewer_url": "https://docs.apdf.io/studio/4352c-99928-25aa1",
        "file_url": "https://apdf-files.s3.eu-central-1.amazonaws.com/docs/202607/4d3f13346a61ba93070dc.pdf",
        "name": "Q3 Services Proposal",
        "file_size": 36340,
        "file_pages": 3,
        "is_public": false,
        "created_at": "2026-07-23T06:54:11.000000Z",
        "archived_at": null
    }
}

With is_public: false, opening the bare viewer_url returns a 403. Only recipient links get through.

2

Mint one link per recipient

One shared URL gives you one shared kill switch — all or nothing. A link per person gives you a switch per person, plus per-person reading analytics for free:

curl -X POST https://apdf.io/api/docs/4352c-99928-25aa1/links \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -d name="Dana Whitfield" \
  -d email="dana@clientco.example"
{
    "data": {
        "token": "YGY35sNP",
        "name": "Dana Whitfield",
        "email": "dana@clientco.example",
        "url": "https://docs.apdf.io/studio/4352c-99928-25aa1?v=YGY35sNP",
        "is_active": true,
        "last_viewed_at": null,
        "created_at": "2026-07-23T07:48:05.000000Z"
    }
}

Send Dana the url; repeat for every stakeholder. GET /api/docs/{docId}/links lists them all with their is_active state and last_viewed_at — your access roster for the document.

Tip: First document? Uploading takes one POST /api/docs with a file or URL — and your workspace needs a handle (set once in the dashboard), since it becomes part of every viewer URL. The free plan includes 25 recipient links per document — plenty for a deal's stakeholder list.
3

Revoke one person — not the whole deal

Dana leaves ClientCo mid-deal. Their inbox — and everything forwarded from it — shouldn't keep a working link to your pricing. One call:

curl -X POST https://apdf.io/api/docs/4352c-99928-25aa1/links/YGY35sNP/deactivate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
{
    "data": {
        "token": "YGY35sNP",
        "name": "Dana Whitfield",
        "email": "dana@clientco.example",
        "url": "https://docs.apdf.io/studio/4352c-99928-25aa1?v=YGY35sNP",
        "is_active": false,
        "last_viewed_at": null,
        "created_at": "2026-07-23T07:48:05.000000Z"
    }
}

The effect is immediate and surgical: Dana's link now lands on a neutral “403 — Forbidden” page that reveals nothing — not even the document's name — while Alex's link keeps working untouched. And the history survives: every session Dana already had stays in your analytics, recipient attribution included.

Revoked the wrong person? POST …/links/{token}/activate restores the same URL they already have — nothing to resend. Only DELETE …/links/{token} is forever.

4

Expire links on a schedule

Links don't carry an expiry date — you decide when time is up, which means any policy you can script. “Offer valid until July 31” is a single cron line that fires the deactivate call on August 1. And a standing “quotes die after 30 days” policy is this script on a daily cron:

#!/usr/bin/env bash
# Revoke every active link older than MAX_AGE_DAYS. Run it from cron once a day.
API="https://apdf.io/api/docs/$DOC_ID/links"
CUTOFF=$(date -u -d "-$MAX_AGE_DAYS days" +%Y-%m-%dT%H:%M:%S)

curl -s "$API" -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/json" |
jq -r --arg cutoff "$CUTOFF" '.data[] | select(.is_active and .created_at < $cutoff) | .token' |
while read -r token; do
    curl -s -X POST "$API/$token/deactivate" \
      -H "Authorization: Bearer $API_TOKEN" -H "Accept: application/json" > /dev/null
    echo "Revoked $token"
done

It reads the roster, picks the links that are still active but past the cutoff, and flips each one off — already-revoked links are skipped, fresh ones left alone. Expired recipients who come back knocking are a signal too: last_viewed_at tells you who was still reading when the door closed.

Where to go from here

Your links are now doors you control — next, watch who walks through them.

Ready to see who reads?