Shark Track API
Create and manage short links, variants, projects and analytics over plain HTTP. Everything the dashboard does, the API does.
Quickstart
Create a key in the dashboard under Settings › API keys. It is shown once. Store it in an environment variable rather than pasting it into a file or a chat.
# Create a link. The utm_source is lifted out of the URL automatically.
curl -sX POST https://app.shrk.in/v1/links \
-H "Authorization: Bearer $SHARK_TRACK_KEY" \
-H "content-type: application/json" \
-d '{"destination":"https://example.com/sale?utm_source=newsletter"}'
{
"id": "lnk_8Kq2mXpR",
"shortUrl": "https://shrk.in/aB3xY9",
"destination": "https://example.com/sale",
"params": { "utm_source": "newsletter" }
}
Authentication
Send the key as a bearer token on every request. The base URL is https://app.shrk.in/v1.
Authorization: Bearer stk_live_...
| Scope | What it allows |
|---|---|
read | Every GET. Links, analytics, exports, QR codes. Cannot change anything. |
write | Everything read allows, plus create, update and delete. |
Keys cannot manage other keys, and cannot sign in. A key inherits the powers of the account that created it, so only a superuser key can set or rename a custom slug.
Concepts worth knowing
Tracking parameters are invisible in the link. You set them on the link; the redirect applies them to the destination and records them against the click. The URL you share is always the bare https://shrk.in/<slug>.
UTMs are lifted from URLs you submit. Post a destination carrying utm_source and it becomes a tracking parameter. Functional parameters like id=482 stay on the destination, because the page needs them.
A variant is a second address to the same destination, like shrk.in/sale/email, with its own parameters layered over the link's. A project is a folder; a link belongs to at most one, while tags are many per link.
All timestamps are epoch milliseconds. clickCount counts humans; bot hits are counted separately in botCount and excluded from analytics unless you pass bots=1.
Errors
Every failure returns the same shape. Branch on code, never on the message, which may be reworded.
{
"error": {
"code": "delete_requires_dashboard",
"message": "This link has 143 recorded clicks. Sign in to delete it, or archive it instead.",
"field": null
}
}
| Code | Meaning |
|---|---|
key_missing | No Authorization header |
key_unknown | Not a valid key |
key_revoked | Revoked in the dashboard |
key_expired | Past its expiry |
insufficient_scope | A read key attempted a change |
invalid_request | Bad input; field says which |
conflict | Slug or name already taken |
delete_requires_dashboard | The link has recorded clicks |
rate_limited | Too many requests |
Rate limits
600 requests per minute per key. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining, and a 429 carries Retry-After in seconds.
Endpoints
| Method | Path | What it does |
|---|---|---|
| GET | /links |
List links |
| POST | /links |
Create a linkAny utm_ keys on the destination are lifted into params automatically. Other query parameters stay on the destination. A custom slug requires a superuser key; leave it out for a generated one. |
| POST | /links/bulk/create |
Create many linksOne URL per line, up to 200. A tab after a URL makes the rest its title. utm_ keys are lifted from each line. Exact repeats are skipped. |
| POST | /links/bulk/params |
Apply parameters to many links |
| POST | /links/bulk/preview |
Preview a bulk pasteParses without creating. Use this to check what a paste will do. |
| POST | /links/bulk/project |
Move many links into a project |
| GET | /links/tags |
List tags in use |
| GET | /links/{id} |
Get a link |
| PATCH | /links/{id} |
Update a linkSend only the fields you want to change. Setting status to archived hides a link while keeping it working. |
| DELETE | /links/{id} |
Delete a linkOnly links with no recorded human clicks can be deleted through the API. A link with clicks returns 409 with code `delete_requires_dashboard`; archive it instead, or delete it from the dashboard. Bot-only links are deletable. |
| GET | /links/{id}/clicks |
Recent clicks |
| GET | /links/{id}/export.csv |
Export clicks as CSV |
| GET | /links/{id}/qr.svg |
QR code |
| GET | /links/{id}/stats |
Analytics for a linkTotals, a time series, and breakdowns by country, city, device, OS, browser, referrer, traffic type, UTM source, medium and campaign, custom parameters, variant and language. Also lists variant codes that were clicked but match no variant. |
| GET | /links/{id}/variants |
List variants |
| POST | /links/{id}/variants |
Create a variantA variant is a second address to the same destination: shrk.in/<slug>/<code>. Its params layer over the link's. |
| PATCH | /links/{id}/variants/{variantId} |
Update a variant |
| DELETE | /links/{id}/variants/{variantId} |
Delete a variantPast clicks are kept and report as an unrecognised code afterwards. |
| GET | /me |
Who this key belongs to |
| GET | /projects |
List projects |
| POST | /projects |
Create a project |
| PATCH | /projects/{id} |
Rename a project |
| DELETE | /projects/{id} |
Delete a projectLinks inside are unfiled, never deleted. |
| GET | /workspace |
Workspace details |
| GET | /workspace/stats |
Analytics across every link |
Paths are relative to https://app.shrk.in/v1. The OpenAPI 3.1 spec has full request and response schemas.
Deleting links
A link with recorded clicks cannot be deleted through the API. Losing analytics history to an automated call is not recoverable, so it returns 409 with code delete_requires_dashboard. Links with no human clicks delete normally, and bot-only links count as empty.
Archive instead. The link keeps working and keeps its history, it just leaves the list.
curl -sX PATCH https://app.shrk.in/v1/links/lnk_123 \
-H "Authorization: Bearer $SHARK_TRACK_KEY" \
-H "content-type: application/json" \
-d '{"status":"archived"}'
Use with Claude
No MCP server needed. Claude can call this API directly with curl, and read the spec to work out how.
1. Put the key in the environment
export SHARK_TRACK_KEY="stk_live_..."
Never paste the key into a chat message or commit it. Claude reads it from the environment when it runs a command.
2. Point Claude at the spec
The Shark Track API is documented at https://docs.shrk.in/llms.txt and the full spec is at https://docs.shrk.in/openapi.json My key is in $SHARK_TRACK_KEY. Create short links for these five URLs and put them in a project called Q4 launch.
llms.txt is the compact version and fits comfortably in context. Use the OpenAPI spec when exact schemas matter.
3. Give it a read key unless it needs to write
An agent holding a write key can be steered by content it reads. If Claude browses a page or opens a document containing instructions, a prompt injection could create or change links on your account. Use a read-only key for reporting work, and a write key only when you are asking for changes. Revoke from the dashboard the moment a key is no longer needed.
Useful things to ask for: a weekly summary of which links got clicks and from where, a variant per channel across a set of links, a CSV export pulled into a spreadsheet, or a batch of links built from a list of URLs.