Public URL-shortener and QR-link API · Demo · OpenAPI · Automation
/v1/keys/request.lti_live_… key.Authorization: Bearer lti_live_….# 1. Request a key (check your inbox)
curl -X POST https://api.link.to.it/v1/keys/request \
-H 'Content-Type: application/json' \
-d '{"email":"you@example.com","label":"my-app","terms_accepted":true}'
# 2. Once you have your key, shorten a URL
curl -X POST https://api.link.to.it/v1/shorten \
-H 'Authorization: Bearer lti_live_…' \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/very-long-link"}'
Most endpoints require a Bearer token in the Authorization header:
Authorization: Bearer lti_live_92f4a8b1c3d5e6f7…
Keys follow the pattern lti_live_ + 32 hex characters. The lti_live_ prefix allows scanners (e.g. GitHub secret scanning) to identify leaked keys.
| Scenario | HTTP | Code |
|---|---|---|
| Missing header on protected route | 401 | unauthorized |
| Malformed token (wrong prefix/length) | 401 | unauthorized |
| Valid format but unknown/revoked key | 401 | unauthorized |
| Daily quota exhausted | 429 | quota_exceeded |
Account keys share one daily quota (UTC midnight reset), not per key: Casual 10, Intentional 100, Determined 300. Keys without an account use the Casual limit. Bulk POST /v1/bulk/shorten counts each newly created URL against that quota. Returning an existing link (200) does not count toward the daily quota.
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/keys/request | — | Request a new API key (sends magic link to email) |
| GET | /v1/keys/claim?token=… | — | Claim key via magic link (one-time display) |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/shorten | Bearer | Create a short link |
| POST | /v1/bulk/shorten | Bearer | Bulk create up to 100 links (Intentional+) |
| GET | /v1/links/{hash} | Bearer | Get full details of a link you own |
| PATCH | /v1/links/{hash} | Bearer | Update URL or rename hash |
| DELETE | /v1/links/{hash} | Bearer | Soft-delete a link |
| GET | /v1/lookup?url=… | — | Check if a URL was shortened before |
| GET | /v1/check-hash?hash=… | — | Check if a short hash exists |
| GET | /v1/stats?hash=… | — | Get click stats for a hash |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/usage | Bearer | Get your API key usage stats |
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/qr-link | Bearer | Create a QR payload link (vCard, event, wifi, etc.) |
| GET | /v1/qr-resolve?hash=… | — | Resolve a QR payload hash |
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/captcha | — | Get a captcha challenge (for webapp integration) |
| POST | /v1/report | — | Report an abusive link |
| GET | /v1/geocode?ip=… | — | IP geolocation (country only) |
| GET | /v1/favicon?url=… | — | Fetch a site's favicon |
All errors return JSON with error (human-readable) and code (machine-readable):
{
"error": "Invalid or expired token",
"code": "token_invalid"
}
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_json | Request body is not valid JSON |
| 400 | missing_url | Required url field missing |
| 400 | invalid_url | URL failed validation (scheme, length, format) |
| 400 | blocked_url | URL is on the blocklist (spam, phishing, etc.) |
| 401 | unauthorized | Missing or invalid Bearer token |
| 403 | forbidden | Action not allowed for this key |
| 404 | not_found | Resource does not exist |
| 405 | method_not_allowed | Wrong HTTP method for this endpoint |
| 409 | hash_taken | Custom hash already exists |
| 410 | token_invalid | Magic link expired or already used |
| 429 | rate_limit_exceeded | Too many requests — check Retry-After |
| 429 | quota_exceeded | Daily quota exhausted — resets at midnight UTC |
| 500 | internal_error | Server error — report if persistent |
Every response includes rate-limit headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Max requests in current window |
X-RateLimit-Remaining | Requests left in current window |
X-RateLimit-Reset | Unix timestamp when window resets |
Retry-After | Seconds to wait (only on 429) |
| Tier | Burst | Daily |
|---|---|---|
| Anonymous (IP-based) | 30 req / 30 min per bucket | — |
| Authenticated (default) | 60 req / min | 1,000 req / day |
| Authenticated (upgraded) | custom | custom |
Need higher limits? Contact kosmar@kosmar.de.
For safe retries on network failures, include an Idempotency-Key header on POST requests:
curl -X POST https://api.link.to.it/v1/shorten \
-H 'Authorization: Bearer lti_live_…' \
-H 'Idempotency-Key: my-unique-request-id-123' \
-H 'Content-Type: application/json' \
-d '{"url":"https://example.com/page"}'
If you retry with the same key within 24 hours, you get the original response (no duplicate link created). The response includes Idempotent-Replayed: true header on replays.
Connect link.to.it to Make, Zapier, or n8n in two ways:
https://api.link.to.it/openapi.yaml. Authenticate with your Bearer API key. Use POST /v1/bulk/shorten to create up to 100 short links per request (Intentional+).POST with a small JSON body, for example:{"hash":"vnebHy","t":1730000000}
hash is the short-link slug; t is the click time (Unix seconds). We do not send visitor IP addresses, user agents, referrers, or destination URLs – only hash and time. See the ClickWebhookPayload schema in the OpenAPI spec.
Interactive demo page to test all endpoints: API Demo →
API is in beta. Breaking changes will be announced in advance via the contact email tied to your key. Backwards-compatible additions can happen any time.
Project: link.to.it · Contact: kosmar@kosmar.de