expiring-links

Create and manage time-limited, count-limited, and password-protected share links for uploaded files.

7 stars

Best use case

expiring-links is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Create and manage time-limited, count-limited, and password-protected share links for uploaded files.

Teams using expiring-links should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/expiring-links/SKILL.md --create-dirs "https://raw.githubusercontent.com/heldernoid/agentic-build-templates/main/projects/web-applications/file-sharing/skills/expiring-links/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/expiring-links/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How expiring-links Compares

Feature / Agentexpiring-linksStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Create and manage time-limited, count-limited, and password-protected share links for uploaded files.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Expiring Links Skill

## When to use

Use this skill when you need to:
- Create links that automatically stop working after a date
- Create links that stop working after N downloads
- Add password protection to a download link
- Check if a link is still valid without consuming a download
- Understand why a link returned 410 or 401

## How expiry works

A share link is checked in this order when accessed via `GET /dl/:token`:

1. Is the link active (`is_active = 1`)? If not, return `410`.
2. Is `expires_at` set and in the past? Return `410`.
3. Is `max_downloads` set and `download_count >= max_downloads`? Return `410`.
4. Does the link have a password (`password_hash != NULL`)? Check `X-Link-Password` header. If missing or wrong, return `401`.
5. Stream the file. After the stream completes, atomically increment `download_count` and insert a `downloads` row.

## Create a link that expires in 7 days

```bash
curl -X POST http://localhost:3000/api/files/ab3kx9mz1qwe/links \
  -H "Content-Type: application/json" \
  -d '{
    "label": "7-day access",
    "expires_at": "2026-03-27T00:00:00Z"
  }'
```

## Create a link limited to 1 download (one-time link)

```bash
curl -X POST http://localhost:3000/api/files/ab3kx9mz1qwe/links \
  -H "Content-Type: application/json" \
  -d '{"max_downloads": 1, "label": "One-time access"}'
```

After the first download, this link returns `410 Gone`.

## Create a link with both expiry and download limit

```bash
curl -X POST http://localhost:3000/api/files/ab3kx9mz1qwe/links \
  -H "Content-Type: application/json" \
  -d '{
    "expires_at": "2026-04-20T00:00:00Z",
    "max_downloads": 10,
    "label": "Client share - limited"
  }'
```

## Create a password-protected link

```bash
curl -X POST http://localhost:3000/api/files/ab3kx9mz1qwe/links \
  -H "Content-Type: application/json" \
  -d '{
    "password": "MySecretPassword",
    "label": "Secure access"
  }'
```

The password is hashed with bcrypt (cost 10) before storage. It is never returned in API responses.

## Check if a link is still valid

Use `HEAD /dl/:token` to check validity without downloading the file and without consuming a download slot:

```bash
curl -I http://localhost:3000/dl/pq7rn4xyz90a
```

Responses:

| Status | Meaning |
|---|---|
| 200 | Link is active and the file is accessible |
| 401 | Link is active but password-protected; provide `X-Link-Password` |
| 404 | Token does not exist |
| 410 | Link is expired, revoked, or download limit reached |

## Check how many downloads are remaining

```bash
curl http://localhost:3000/api/links/pq7rn4xyz90a
```

Response includes:

```json
{
  "token": "pq7rn4xyz90a",
  "max_downloads": 10,
  "download_count": 3,
  "expires_at": "2026-04-20T00:00:00Z",
  "is_active": true
}
```

Remaining downloads = `max_downloads - download_count` = 7.
If `max_downloads` is null, the link has no download limit.

## Extend the expiry of an existing link

```bash
curl -X PUT http://localhost:3000/api/links/pq7rn4xyz90a \
  -H "Content-Type: application/json" \
  -d '{"expires_at": "2026-06-01T00:00:00Z"}'
```

## Remove the expiry from a link (make it permanent)

```bash
curl -X PUT http://localhost:3000/api/links/pq7rn4xyz90a \
  -H "Content-Type: application/json" \
  -d '{"expires_at": null}'
```

## Remove the download limit from a link

```bash
curl -X PUT http://localhost:3000/api/links/pq7rn4xyz90a \
  -H "Content-Type: application/json" \
  -d '{"max_downloads": null}'
```

## Manually revoke a link

```bash
curl -X DELETE http://localhost:3000/api/links/pq7rn4xyz90a
```

This sets `is_active = 0`. The link immediately returns `410` for all new download attempts. The download history is preserved.

## Reactivate a revoked link

PATCH is not supported. To reactivate, use PUT to update is_active:

```bash
# Note: is_active cannot be directly set via PUT.
# Delete and recreate the link to reactivate, or use the admin UI.
```

The admin UI has a "Reactivate" button that calls an internal endpoint. Via the API, revocation is permanent - create a new link if access needs to be restored.

## Automatic cleanup

The server runs a cleanup job every `CLEANUP_INTERVAL_HOURS` (default: 1 hour). This job:

1. Sets `is_active = 0` for all links where `expires_at < now`
2. Deletes orphan files (files with no active links older than 7 days)

The cleanup job does not delete download history or the file records themselves.

## Troubleshooting

**Link returns 410 but it was just created**

Check that `expires_at` is in the future (UTC). The server compares against UTC time.

```bash
date -u  # Check current UTC time
```

**Password returns 401 even with the correct password**

Ensure you are sending the password in the `X-Link-Password` request header, not as a query parameter or body field.

**download_count is not incrementing**

The count only increments after the file stream completes successfully. Cancelled or aborted downloads do not increment the count. Check `GET /api/links/:token/downloads` to see the log of completed downloads.

**Link is active but still returns 410**

Check `max_downloads`. If the count equals the limit, the link is exhausted regardless of `is_active`.

```bash
curl http://localhost:3000/api/links/pq7rn4xyz90a | jq '{max_downloads, download_count, expires_at, is_active}'
```

Related Skills

Skill: Uptime Monitoring

7
from heldernoid/agentic-build-templates

## Overview

Skill: Status Page

7
from heldernoid/agentic-build-templates

## Overview

Skill: unit-conversion

7
from heldernoid/agentic-build-templates

## Overview

Skill: recipe-scaler

7
from heldernoid/agentic-build-templates

## Overview

reading-list

7
from heldernoid/agentic-build-templates

Operate the reading-list API to save, manage, tag, search, and export articles.

email-digest

7
from heldernoid/agentic-build-templates

Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.

websocket-realtime

7
from heldernoid/agentic-build-templates

Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".

poll-builder

7
from heldernoid/agentic-build-templates

Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.

Skill: personal-finance

7
from heldernoid/agentic-build-templates

## Overview

Skill: csv-import

7
from heldernoid/agentic-build-templates

## Overview

Skill: Syntax Highlighting

7
from heldernoid/agentic-build-templates

## Purpose

Skill: Pastebin Core

7
from heldernoid/agentic-build-templates

## Purpose