Skip to content

API documentation

Create and manage your short links from your own tools. All requests and responses are in JSON format.

Get a key

Sign in, open Settings › API keys, give your key a name, then copy it: it is shown only once. You can have 5 at most.

The key is sent in the Authorization header of every request.

Getting started

All requests go through the base URL https://shortit.fr/api/v1, over HTTPS. Request bodies are sent as JSON with the Content-Type: application/json header and the Content-Length header (curl sets them itself). Fields are in snake_case, dates are in ISO 8601 format in UTC.

Version 1. In version 1, a field is never removed, renamed or changed in type; fields may be added, so ignore those you do not know. A breaking change would get a new address (/api/v2) and version 1 would remain available. A request containing an unknown field is rejected (400): this is intentional, so that a typo does not go unnoticed.

The OpenAPI specification, generated from the code, is public: /api/v1/openapi.json. No IP address is ever returned or stored.

GET/api/v1/me

Checks that your key works and returns the associated account: its address, the number of links and the limit.

Request
curl https://shortit.fr/api/v1/me \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 200
{
  "email": "camille@example.com",
  "links": 6,
  "links_limit": 1000
}

Authentication

Every request must carry your key in the Authorization header, preceded by “Bearer”. A revoked, malformed or missing key returns a 401 error. A key is never sent in the address, and the API reads no cookie.

Keys are created in Settings (5 at most). A key is shown only once, when it is created: only its fingerprint is stored. It does not expire: it works until it is revoked, which takes effect immediately. Keys start with shortit_.

Never put a key in public code or on the browser side: use it from a server or a script. The API does not send CORS headers. The account must have a verified address and must not be suspended.

GET/api/v1/links

Example of an authentication header, and of the response when the key is missing or revoked.

Request
curl https://shortit.fr/api/v1/links \
  -H "Authorization: Bearer shortit_…"
Response · 401
{
  "error": {
    "code": "unauthorized",
    "message": "Clé absente ou révoquée."
  }
}

Create a link

Body fields: destination (required, http or https, 2,048 characters at most); alias (3 to 32 letters, digits or hyphens, generated if omitted); title (120 characters at most); expires_at (ISO 8601 date in the future); password (6 to 128 characters, never returned: the response only indicates has_password); domain (one of your active custom domains, see My domains; absent: shortit.fr).

Destinations are checked as in the interface: private addresses, raw IP addresses, other link shorteners and sites flagged as dangerous are rejected (422). Creation limits are the same as in the interface: 10 per hour and 50 per day, then 429.

Pro plan: max_clicks (1 to 10,000,000) limits the number of clicks; once the limit is reached, the link responds like an expired link. The response shows max_clicks and remaining_clicks (null without a limit). A free account that sends max_clicks gets 403 pro_required.

POST/api/v1/links

Creates a short link. Only destination is required; alias is generated if you omit it.

Request
curl -X POST https://shortit.fr/api/v1/links \
  -H "Authorization: Bearer VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"destination":"https://exemple-atelier.fr/inscriptions/printemps-2026","alias":"atelier26","title":"Atelier printemps"}'
Response · 201
{
  "alias": "atelier26",
  "domain": "shortit.fr",
  "short_url": "https://shortit.fr/atelier26",
  "destination": "https://exemple-atelier.fr/inscriptions/printemps-2026",
  "title": "Atelier printemps",
  "status": "active",
  "expires_at": null,
  "has_password": false,
  "created_at": "2026-09-14T09:12:00.000Z",
  "max_clicks": null,
  "remaining_clicks": null,
  "tags": []
}

Read a link

A link is identified by its alias, regardless of case. Two domains can carry the same alias: for a link on one of your custom domains, add the domain parameter (for example ?domain=go.monsite.fr) to read, edit, delete or get statistics; without it, the link on shortit.fr is used. A link belonging to another account and a link that does not exist get the same response: 404 not_found.

GET/api/v1/links/{alias}

Returns a link from your account, given its alias.

Request
curl https://shortit.fr/api/v1/links/atelier26 \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 200
{
  "alias": "atelier26",
  "domain": "shortit.fr",
  "short_url": "https://shortit.fr/atelier26",
  "destination": "https://exemple-atelier.fr/inscriptions/printemps-2026",
  "title": "Atelier printemps",
  "status": "active",
  "expires_at": null,
  "has_password": false,
  "created_at": "2026-09-14T09:12:00.000Z",
  "max_clicks": null,
  "remaining_clicks": null,
  "tags": []
}

List my links

From newest to oldest, by cursor: as long as next is not null, pass its value in the cursor parameter to get the next page. The cursor is opaque; it remains valid even if links are created or deleted between two pages. Parameters: limit (1 to 100, 20 by default), cursor, status (active, disabled, expired or flagged).

GET/api/v1/links

Lists your links. Filter by state with status.

Request
curl "https://shortit.fr/api/v1/links?status=active&limit=20" \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 200
{
  "data": [
    {
      "alias": "atelier26",
      "domain": "shortit.fr",
      "short_url": "https://shortit.fr/atelier26",
      "destination": "https://exemple-atelier.fr/inscriptions/printemps-2026",
      "title": "Atelier printemps",
      "status": "active",
      "expires_at": null,
      "has_password": false,
      "created_at": "2026-09-14T09:12:00.000Z",
      "max_clicks": null,
      "remaining_clicks": null,
      "tags": []
    }
  ],
  "next": null
}

Edit

Editable fields: destination, title (null clears it), expires_at (null removes it), password (a text changes it, null removes it) and status (active or disabled). Only the fields you send change. The alias never changes. A reported link cannot be edited (409 link_flagged) and a link disabled for a security reason cannot be re-enabled (409 link_locked).

Pro plan: max_clicks sets or changes the click limit, and its counter then starts again from zero (raising the limit of a link that reached it puts it back in service); null removes the limit.

Pro plan: redirect rules (5 at most) are read with GET /api/v1/links/ALIAS/rules and replaced with PUT on the same address, with the body {"rules":[{"type":"country","value":"FR","destination":"https://…"}]}. type is country (ISO country code, “FR”) or device (mobile, tablet or desktop); the first rule that matches the visitor wins, otherwise the link’s destination is used. Each destination is checked like the link’s own (422). On a link with 100 clicks or more, the change is made from the dashboard (403 confirmation_required).

PATCH/api/v1/links/{alias}

Edits a link’s destination, title, expiry date, password or state.

Request
curl -X PATCH https://shortit.fr/api/v1/links/atelier26 \
  -H "Authorization: Bearer VOTRE_CLE" \
  -H "Content-Type: application/json" \
  -d '{"status":"disabled"}'
Response · 200
{
  "alias": "atelier26",
  "domain": "shortit.fr",
  "short_url": "https://shortit.fr/atelier26",
  "destination": "https://exemple-atelier.fr/inscriptions/printemps-2026",
  "title": "Atelier printemps",
  "status": "disabled",
  "expires_at": null,
  "has_password": false,
  "created_at": "2026-09-14T09:12:00.000Z",
  "max_clicks": null,
  "remaining_clicks": null,
  "tags": []
}

Delete

DELETE/api/v1/links/{alias}

Permanently deletes a link. The short address stops working, and an alias that has already been used is never reassigned.

Request
curl -X DELETE https://shortit.fr/api/v1/links/atelier26 \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 204
(empty body)

Statistics for a link

The last 30 days (Europe/Paris days, from oldest to today) and the total since creation. unique_visitors is the sum of each day’s unique visitors: a visitor who came back on three days counts three times (no persistent identifier is stored). variation_pct compares with the previous period and is null when there is none.

Each breakdown (countries, referrers, devices, browsers) is sorted by descending clicks and limited to 5 rows. value is the country code, the referring domain, the device or the browser; null means “unknown”, or, with other set to true, “others” (the least frequent values, grouped together).

GET/api/v1/links/{alias}/stats

Clicks, unique visitors (counted per day), the curve for the last 30 days and breakdowns.

Request
curl https://shortit.fr/api/v1/links/atelier26/stats \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 200
{
  "alias": "atelier26",
  "period_days": 30,
  "clicks": 1397,
  "unique_visitors": 1006,
  "total_clicks": 2310,
  "variation_pct": 12.4,
  "days": [
    {
      "date": "2026-09-12",
      "clicks": 61
    },
    {
      "date": "2026-09-13",
      "clicks": 48
    }
  ],
  "countries": [
    {
      "value": "FR",
      "clicks": 925
    },
    {
      "value": "BE",
      "clicks": 201
    },
    {
      "value": null,
      "clicks": 271,
      "other": true
    }
  ],
  "referrers": [
    {
      "value": "instagram.com",
      "clicks": 512
    },
    {
      "value": null,
      "clicks": 885
    }
  ],
  "devices": [
    {
      "value": "mobile",
      "clicks": 944
    },
    {
      "value": "desktop",
      "clicks": 453
    }
  ],
  "browsers": [
    {
      "value": "Safari",
      "clicks": 601
    },
    {
      "value": "Chrome",
      "clicks": 796
    }
  ]
}

My domains

State of each domain: pending_dns (waiting for your DNS records), verified (ownership verified), cert_pending (certificate in progress), active (the domain redirects), error (verification failed or stopped), disabled (disabled: a record has disappeared, or an administrator’s decision).

GET/api/v1/domains

Lists your custom domains and their verification state.

Request
curl https://shortit.fr/api/v1/domains \
  -H "Authorization: Bearer VOTRE_CLE"
Response · 200
{
  "data": [
    {
      "name": "go.camille.fr",
      "status": "active",
      "created_at": "2026-09-02T08:00:00.000Z"
    },
    {
      "name": "liens.exemple-asso.fr",
      "status": "pending_dns",
      "created_at": "2026-09-14T09:12:00.000Z"
    }
  ]
}

Limits and errors

Each key is limited to 30 requests per minute (300 with the Pro plan, coming soon). Beyond that, the API responds 429 with the Retry-After header, which gives the delay before the next attempt in seconds. Thirty rejected keys within one minute from the same address cause that address to be refused for the rest of the minute.

Every error has the same body: { "error": { "code", "message" } }, plus field and reason for a validation refusal (422). The code is stable; the message is meant for a person and may change. Codes:

unauthorized (401): Clé absente, mal formée ou révoquée.

email_not_verified (403): L'adresse e-mail du compte n'est pas vérifiée.

account_suspended (403): Le compte est suspendu.

link_limit_reached (403): Le plafond de liens du compte est atteint.

pro_required (403): Cette fonction (limite de clics, règles de redirection…) est réservée aux comptes de l'offre Pro.

confirmation_required (403): Ce lien a beaucoup de clics : changer sa destination ou ses règles de redirection, ou le supprimer, se fait depuis le tableau de bord, avec un code reçu par e-mail.

not_found (404): Lien introuvable, ou qui n'est pas à vous.

link_flagged (409): Le lien est signalé : il ne se modifie ni ne se supprime.

link_locked (409): Le lien a été désactivé pour un motif de sécurité : il ne se réactive pas.

invalid_json (400): Le corps n'est pas du JSON valide.

invalid_request (400): Requête invalide : champ inconnu, type inattendu ou paramètre hors limites.

validation_failed (422): Une valeur est refusée : le corps porte field et reason.

payload_too_large (413): Corps de plus de 16 Ko.

length_required (411): L'en-tête Content-Length est obligatoire avec un corps.

unsupported_media_type (415): Content-Type : application/json obligatoire avec un corps.

rate_limited (429): Plus de requêtes par minute que l'offre ne le permet (30 en gratuit, 300 en Pro) avec cette clé : voir Retry-After.

internal_error (500): Erreur du serveur. Réessayez ; elle ne détaille rien.

ERROR429 Too Many Requests

Example of a response when the limit is exceeded.

Response · 429
{
  "error": {
    "code": "rate_limited",
    "message": "Trop de requêtes. Réessayez dans 12 secondes."
  }
}
ERROR422 Unprocessable Entity

Example of a validation refusal: field and reason tell which field and why.

Response · 422
{
  "error": {
    "code": "validation_failed",
    "message": "Déjà pris, choisissez-en un autre.",
    "field": "alias",
    "reason": "taken"
  }
}