API Reference
Programmatically create and manage short links. All endpoints return JSON and accept JSON request bodies.
Base URL: https://app.0.mk
Authentication
All API requests require a Bearer token. Create API keys from Settings in your dashboard.
curl -X POST https://app.0.mk/api/links \
-H "Authorization: Bearer 0mk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com"}'API keys start with 0mk_ and are scoped to a workspace. All links created via API belong to the workspace associated with the key.
Links
/api/linksCreate a short link
/api/linksList links (paginated)
/api/links/:idGet a single link
/api/links/:idUpdate a link
/api/links/:idDelete a link
Create a link
urlstringrequiredDestination URL (must be http or https)
aliasstringCustom slug. If omitted, a random one is generated.
titlestringOptional title for the link (max 500 chars)
expiresAtstringISO 8601 datetime when the link should expire
tagIdsstring[]Array of tag IDs to attach
curl -X POST https://app.0.mk/api/links \
-H "Authorization: Bearer 0mk_abc123" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/long-page",
"alias": "my-link",
"title": "Example Page"
}'{
"id": "cm...",
"shortUrl": "https://0.mk/my-link",
"slug": "my-link",
"destinationUrl": "https://example.com/long-page",
"createdAt": "2026-04-26T12:00:00.000Z"
}List links
cursorstringPagination cursor (from previous response's nextCursor)
limitnumberResults per page (default 25, max 100)
qstringSearch by slug, URL, or title
statusstringFilter: active, disabled, or needs_review
tagstringFilter by tag ID
curl "https://app.0.mk/api/links?limit=10&q=example" \
-H "Authorization: Bearer 0mk_abc123"{
"data": [
{
"id": "cm...",
"shortUrl": "https://0.mk/my-link",
"slug": "my-link",
"destinationUrl": "https://example.com/long-page",
"title": "Example Page",
"totalClicks": "142",
"status": "active",
"expiresAt": null,
"createdAt": "2026-04-26T12:00:00.000Z",
"tags": [{ "id": "t1", "name": "marketing", "color": "#3b82f6" }]
}
],
"nextCursor": "cm...",
"hasMore": true,
"total": 87
}Update a link
destinationUrlstringNew destination URL
slugstringChange the slug
titlestringUpdate the title
statusstringSet to "active" or "disabled"
expiresAtstring | nullSet or clear expiration (ISO 8601, or null)
tagIdsstring[]Replace all tags with these IDs
curl -X PATCH https://app.0.mk/api/links/cm_link_id \
-H "Authorization: Bearer 0mk_abc123" \
-H "Content-Type: application/json" \
-d '{"status": "disabled"}'Delete a link
curl -X DELETE https://app.0.mk/api/links/cm_link_id \
-H "Authorization: Bearer 0mk_abc123"Analytics
/api/analyticsWorkspace-wide analytics
/api/links/:id/analyticsPer-link analytics
daysnumberLookback period in days (default 30, max 365)
curl "https://app.0.mk/api/analytics?days=7" \
-H "Authorization: Bearer 0mk_abc123"{
"totalClicks": 1423,
"daily": [
{ "day": "2026-04-20", "clicks": 203 },
{ "day": "2026-04-21", "clicks": 187 }
],
"countries": [
{ "country": "US", "clicks": 612 },
{ "country": "MK", "clicks": 298 }
],
"referrers": [
{ "referrer": "twitter.com", "clicks": 421 },
{ "referrer": null, "clicks": 389 }
],
"topLinks": [
{ "linkId": "cm...", "slug": "launch", "clicks": 312 }
]
}Domains
/api/domainsList custom domains
/api/domainsAdd a custom domain
/api/domains/:idVerify domain DNS
/api/domains/:idRemove a domain
curl -X POST https://app.0.mk/api/domains \
-H "Authorization: Bearer 0mk_abc123" \
-H "Content-Type: application/json" \
-d '{"hostname": "go.acme.com"}'{
"id": "cm...",
"hostname": "go.acme.com",
"status": "pending",
"dnsRecord": {
"type": "CNAME",
"name": "go.acme.com",
"value": "cname.0.mk"
},
"createdAt": "2026-04-26T12:00:00.000Z"
}Team
/api/teamList workspace members
/api/teamInvite a member
/api/team/:idChange member role
/api/team/:idRemove a member
Roles: owner, admin, member, viewer
Errors
All errors return a JSON object with an error field.
| Status | Meaning |
|---|---|
| 400 | Invalid request body or parameters |
| 401 | Missing or invalid API key |
| 403 | Blocked URL or insufficient permissions |
| 404 | Resource not found |
| 409 | Conflict (e.g. alias already taken, domain already registered) |
| 422 | DNS verification failed |
| 429 | Rate limit exceeded |
| 500 | Server error |
{
"error": "This alias is already taken"
}Code Examples
const response = await fetch("https://app.0.mk/api/links", {
method: "POST",
headers: {
"Authorization": "Bearer 0mk_your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/my-page",
alias: "my-link",
}),
});
const link = await response.json();
console.log(link.shortUrl); // https://0.mk/my-linkimport requests
response = requests.post(
"https://app.0.mk/api/links",
headers={"Authorization": "Bearer 0mk_your_api_key"},
json={"url": "https://example.com/my-page", "alias": "my-link"},
)
link = response.json()
print(link["shortUrl"]) # https://0.mk/my-link# Create multiple links from a file (one URL per line)
while IFS= read -r url; do
curl -s -X POST https://app.0.mk/api/links \
-H "Authorization: Bearer 0mk_your_api_key" \
-H "Content-Type: application/json" \
-d "{\"url\": \"$url\"}" | jq .shortUrl
done < urls.txtRate Limits
API requests are rate limited per IP address:
| Endpoint | Limit | Window |
|---|---|---|
| POST /api/links | 30 requests | per minute |
| POST /api/auth/send | 5 requests | per minute |
| POST /api/reports | 10 requests | per minute |
When rate limited, the API returns 429 Too Many Requests.