API Reference

The MASK API is a RESTful JSON API for managing short links, bio pages, QR codes, analytics, and webhooks. Every feature available in the dashboard is accessible programmatically.

Base URL

All API requests are made to the following base URL over HTTPS. Plain HTTP requests are rejected. All request and response bodies use JSON encoding with Content-Type: application/json.

Base URL
https://mask.pk/api/v1

All endpoint paths in this documentation are relative to the base URL. For example, /links refers to https://mask.pk/api/v1/links.

Authentication

The MASK API uses API keys for authentication. Include your key in the Authorization header as a Bearer token. Keys are scoped to a workspace and can be created from Settings → API Keys.

Authenticated request
curl https://mask.pk/api/v1/links \
  -H "Authorization: Bearer mk_live_abc123def456"

Production keys are prefixed with mk_live_ and test keys with mk_test_. See the Authentication guide for details on scopes and key rotation.

Rate Limits

The API enforces a rate limit of 1,000 requests per minute per API key. Rate limit status is communicated via response headers on every request.

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window (1000)
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the window resets
Retry-AfterSeconds to wait before retrying (only on 429 responses)

When the rate limit is exceeded, the API returns a 429 Too Many Requests response. Use the Retry-After header to determine when to retry.

429 response
{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Retry after 12 seconds.",
    "retry_after": 12
  }
}

Error Format

The API uses conventional HTTP status codes to indicate success or failure. All error responses include a JSON body with a consistent structure containing a machine-readable code, a human-readable message, and an optional details array for validation errors.

Error response
{
  "error": {
    "code": "validation_error",
    "message": "Request body failed validation.",
    "details": [
      {
        "field": "url",
        "message": "Must be a valid URL starting with https://"
      },
      {
        "field": "slug",
        "message": "Slug must be between 4 and 64 characters"
      }
    ]
  }
}
StatusDescription
200Request succeeded
201Resource created
204Resource deleted (no response body)
400Bad request — malformed JSON or missing required fields
401Unauthorized — missing or invalid API key
403Forbidden — API key lacks required scope
404Resource not found
409Conflict — resource already exists (e.g. duplicate slug)
422Validation error — check error.details
429Rate limit exceeded
500Internal server error

Pagination

List endpoints return paginated results using cursor-based pagination. Each response includes a pagination object with the cursor for the next page. Pass the cursor as a query parameter to fetch subsequent pages.

ParameterTypeDescription
limitintegerNumber of items per page (default: 25, max: 100)
cursorstringCursor from a previous response to fetch the next page
Paginated request
curl "https://mask.pk/api/v1/links?limit=10&cursor=eyJpZCI6Imxua18zNDU2In0" \
  -H "Authorization: Bearer mk_live_abc123def456"
Paginated response
{
  "data": [
    { "id": "lnk_abc123", "url": "https://example.com", "slug": "demo" },
    { "id": "lnk_def456", "url": "https://other.com", "slug": "other" }
  ],
  "pagination": {
    "has_more": true,
    "cursor": "eyJpZCI6Imxua19kZWY0NTYifQ"
  }
}

When has_more is false, you have reached the last page. The cursor field will be null on the last page.

Request IDs

Every API response includes an X-Request-Id header containing a unique identifier for the request. Include this ID when contacting support to help us debug issues quickly.

Response headers
HTTP/2 200
Content-Type: application/json
X-Request-Id: req_7f3a2b1c4d5e
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1711296000