REST API
Create and manage short links from your own code. The API uses JSON over HTTPS.
Base URL: https://nocly.site/api/v1
Authentication
Create a key on the API page. The full key is shown once; store it safely. Send it in the Authorization header:
curl https://nocly.site/api/v1/me \
-H "Authorization: Bearer sl_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
X-API-Key: <key> is also accepted. Keys act as your account and can be disabled, re-enabled or revoked (permanent) at any time. Keys are stored as one-way hashes.
Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /me | Your account and the API limits |
| GET | /domains | Domains you can use for links |
| GET | /links | List your links |
| POST | /links | Create a link |
| POST | /links/bulk | Create many links at once |
| GET | /links/{id} | Get one link |
| PATCH | /links/{id} | Update a link (only the fields you send) |
| DELETE | /links/{id} | Delete a link |
| GET | /links/{id}/analytics | Analytics for one link |
Create a link
curl -X POST https://nocly.site/api/v1/links \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1234" \
-d '{"destination":"https://example.com/sale","alias":"sale","tags":["campaign"]}'
| Field | Type | Notes |
|---|---|---|
destination | string | Required. http or https URL |
domain | string | Hostname from /domains. Defaults to your default domain |
alias | string | Custom alias. Omit for a random one |
prefix | string | Prefix plus random part, when no alias is given |
redirect_type | integer | 301, 302 (default) or 307 |
title, notes | string | Optional |
tags | array | List of tag names |
password | string | Protect with a password. In PATCH, null removes it |
max_clicks | integer | Click limit |
activates_at, expires_at | string | ISO 8601, for example 2026-12-31T23:59:00Z |
utm_source … utm_content | string | UTM parameters |
status | string | PATCH only: active or disabled |
allow_duplicate | boolean | Create even if you already shortened this destination |
The response is 201 Created with {"data": {link}}. A link object includes id, short_url, alias, domain, destination, final_destination, status, state, clicks, unique_clicks, dates and tags.
List links
GET /links?q=sale&status=active&tag=campaign&sort=clicks&dir=desc&page=1&per_page=50
| Parameter | Values |
|---|---|
q | Search alias, destination and title |
status | active, disabled, scheduled, expired, limit_reached, blocked, protected |
domain | Domain id |
tag | Tag name |
sort | created, clicks, alias, title, last_click, expires |
dir | asc or desc |
page, per_page | Pagination, per_page up to 100 |
The response contains data and meta (page, per_page, total, pages).
Bulk create
POST /links/bulk with a links array (strings or objects with destination, alias, title, notes, tags) and optional shared options: domain, redirect_type, prefix, tags, activates_at, expires_at, max_clicks, utm_source, utm_medium, utm_campaign, and duplicates (skip, the default, or create).
{"links": ["https://example.com/a", {"destination": "https://example.com/b", "alias": "b-page"}], "tags": ["import"]}
Each item gets its own result (created, duplicate, invalid or failed), so one bad item never blocks the others. The maximum number of items per request is bulk_max_items in the /me response.
Analytics
GET /links/{id}/analytics?range=30d returns totals, a time series and breakdowns by referrer, country, region, city, device, OS, browser and language. range is today, 7d, 30d, 90d, 365d or custom with from and to (YYYY-MM-DD).
Idempotency
Send an Idempotency-Key header (8–128 characters) with POST requests. Retrying with the same key returns the original response with Idempotent-Replayed: true instead of creating a duplicate. Reusing a key with a different body returns 422 idempotency_mismatch; a key that is still being processed returns 409 idempotency_in_progress.
Rate limits
Each key may make 120 requests per minute (configurable by administrators). Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you receive 429 with Retry-After.
Errors
All errors use one shape:
{"error": {"code": "validation_failed", "message": "The request has invalid fields.", "request_id": "…", "fields": {"destination": "…"}}}
| Status | Codes |
|---|---|
| 400 | bad_request (invalid JSON or parameters) |
| 401 | unauthenticated, invalid_api_key, api_key_disabled, api_key_revoked |
| 403 | account_inactive, link_blocked |
| 404 | not_found |
| 409 | alias_taken, duplicate_destination (includes the existing link), idempotency_in_progress |
| 415 | unsupported_media_type (PATCH requires JSON) |
| 400 | invalid_idempotency_key |
| 422 | validation_failed, idempotency_mismatch |
| 429 | rate_limited |
| 500 | server_error |
| 503 | api_disabled, unavailable (maintenance) |
Include the request_id when asking an administrator for help; it matches the API request log.