pik.li pik.li
Versione 1 · stabile

API di pik.li

Crea, modifica e misura i tuoi link corti dal tuo codice. API REST con JSON in entrata e in uscita, una chiave per ogni integrazione, e ogni endpoint documentato qui con una richiesta e una risposta reale.

Ottieni una chiave API OpenAPI 3.1
URL basehttps://pik.li/api/v1

Introduzione

Le API di pik.li permettono al tuo software di fare quello che fai dalla dashboard: creare e modificare link corti, leggerne le statistiche, elencare tag e domini e gestire i webhook. Funzionano su HTTPS e ogni percorso parte dall'URL base indicato sopra.

  • Invia il corpo delle richieste in JSON con Content-Type: application/json; i parametri delle richieste GET vanno nella query string. Le risposte sono sempre JSON in UTF-8, tranne il codice QR, che è un'immagine.
  • La versione fa parte del percorso (/api/v1) e ogni risposta porta l'header X-Api-Version: 1. Col tempo nelle risposte v1 possono comparire nuovi campi: fai in modo che il tuo codice ignori quelli che non conosce.
  • Date e orari sono stringhe ISO 8601 con il loro offset (2026-09-23T10:15:42.118+02:00); le date senza orario hanno la forma 2026-09-23.
  • Gli identificativi sono numeri interi. Un link si può trovare anche dal suo URL corto, con GET /links/lookup.

Avvio rapido

  1. Crea una chiave dalla dashboard (vedi Creare una chiave) e salvala in una variabile d'ambiente.
  2. Verifica che funzioni: GET /me risponde con il tuo account e il tuo piano.
  3. Crea il tuo primo link corto con POST /links.
Terminale
export PIKLI_KEY="pk_live_…"

curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"

curl -X POST https://pik.li/api/v1/links \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/a/very/long/page"}'

Autenticazione

Ogni chiamata, tranne GET /ping, richiede una chiave API, da inviare nell'header Authorization come bearer token.

Header
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

Funziona anche l'header X-API-Key. Il parametro di query api_key è ancora accettato per i client più vecchi, ma evitalo: gli indirizzi finiscono nei log e nella cronologia dei browser.

Le chiavi iniziano con pk_live_ seguito da 32 caratteri. Una chiave vede esattamente ciò che vede il suo account — link, domini, tag, statistiche e webhook — e tutte le chiavi di un account possono fare le stesse cose: non esistono chiavi di sola lettura.

Tratta una chiave come una password. Tienila sul tuo server, mai in codice che gira in un browser o in un'app che distribuisci. Se una chiave viene esposta, revocala e creane un'altra: bastano dieci secondi.

Creare una chiave

  1. Accedi e apri API e chiavi nella dashboard.
  2. Dai alla chiave un nome che ti ricordi dove la usi (per esempio CRM produzione) e premi Genera chiave.
  3. Copia subito la chiave: la dashboard la mostra una sola volta.
  4. Puoi avere fino a 10 chiavi attive. Per ognuna la pagina mostra quando è stata usata l'ultima volta e quante richieste ha fatto, e ti permette di revocarla: dalla chiamata successiva una chiave revocata riceve 401 unauthorized.

Chi può usare le API

Le API sono incluse nei piani a pagamento (Premium e Business). Il piano Base non le comprende, a meno che lo staff di pik.li non le attivi per il piano gratuito o per il tuo account. In ogni caso l'account deve avere un indirizzo e-mail confermato e non deve essere bloccato o sospeso.

Se manca una di queste condizioni, le API rispondono api_disabled, email_unconfirmed o account_blocked.

Base

Solo se lo staff le abilita
Richieste al minuto
60
Nuovi link
3 al giorno
Link per chiamata bulk
10
Statistiche conservate per
90 giorni
Webhook
No

Premium

API incluse
Richieste al minuto
600
Nuovi link
2.500 al mese
Link per chiamata bulk
100
Statistiche conservate per
730 giorni
Webhook

Business

API incluse
Richieste al minuto
3.000
Nuovi link
10.000 al mese
Link per chiamata bulk
100
Statistiche conservate per
1.095 giorni
Webhook

Questi sono i valori standard di ogni piano, letti in tempo reale dalle impostazioni dei prezzi. I tuoi valori, con le eventuali modifiche fatte dallo staff al tuo account, li restituiscono GET /limits e GET /me.

Rate limit

Ogni chiave può fare un certo numero di richieste al minuto, che dipende dal piano (vedi la tabella sopra). Il conteggio riparte all'inizio di ogni minuto.

X-RateLimit-Limit
richieste consentite al minuto a questa chiave
X-RateLimit-Remaining
richieste rimaste nel minuto in corso
X-RateLimit-Reset
quando riparte il conteggio, come timestamp Unix in secondi
Retry-After
solo nelle risposte 429: quanti secondi aspettare

Oltre il limite, le API rispondono 429 con l'errore rate_limited e un header Retry-After. Aspetta quel tempo e riprendi: riprovare subito non fa che intaccare il minuto successivo.

Risposta
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
Retry-After: 18
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1790151360

{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Check the X-RateLimit-* headers and retry after the window resets.",
    "details": { "limit": 600, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
    "docs_url": "https://pik.li/help#error-rate_limited"
  },
  "request_id": "2b8e51d0-7a4c-4f1e-9d3b-5c6a0e8f7d21"
}
  • Le chiamate senza chiave sono limitate a 30 al minuto per indirizzo IP (GET /ping non viene contato).
  • Inoltre ogni indirizzo IP ha un tetto di 600 richieste al minuto verso tutto pik.li, qualunque sia il piano.

Errori

Una richiesta che non va a buon fine riceve sempre una risposta con la stessa struttura, qualunque sia l'endpoint, e uno stato HTTP che corrisponde al problema.

Risposta
{
  "error": {
    "code": "quota_exceeded",
    "message": "Link quota reached: 2500 links per month on your plan.",
    "details": { "limit": 2500, "used": 2500, "period": "month", "resets_at": "2026-09-30T22:00:00Z" },
    "docs_url": "https://pik.li/help#error-quota_exceeded"
  },
  "request_id": "6f1c2a9e-4b0d-4c55-9a51-8f2f0d7c1e3b"
}
code
Identificativo stabile, pensato per il tuo codice. Non cambia mai e non viene mai tradotto.
message
Una frase per le persone, nella lingua della richiesta (vedi Lingue).
details
Dati strutturati, quando servono: i campi non validi, i valori ammessi, i numeri di una quota. Non sempre presente.
docs_url
Il link alla spiegazione di questo codice, in questa pagina.
request_id
L'identificativo della richiesta, inviato anche nell'header X-Request-Id. Indicalo quando scrivi all'assistenza.

Ogni endpoint che richiede una chiave può rispondere anche unauthorized account_blocked email_unconfirmed api_disabled rate_limited: non vengono ripetuti sotto ciascun endpoint.

unauthorized401
La chiave manca, è sbagliata o è stata revocata. Controlla l'header Authorization e che la chiave risulti ancora attiva nella dashboard.
account_blocked403
L'account è bloccato, sospeso o disattivato. Le API restano chiuse finché l'account non torna attivo.
email_unconfirmed403
L'indirizzo e-mail dell'account non è ancora stato confermato. Apri il link nell'e-mail di conferma, poi riprova.
api_disabled403
Il piano di questo account non include le API, oppure le API sono state disattivate per l'account. Vedi Chi può usare le API.
forbidden403
La chiave non può eseguire questa azione. riservato
feature_required402
Il piano non include la funzione che serve a questo endpoint. details.feature indica quale e details.plan il primo piano che la offre.
quota_exceeded402
La quota di link del giorno o del mese è esaurita. details contiene limit, used, period e resets_at, il momento da cui potrai di nuovo creare link.
not_found404
La risorsa non esiste, è stata eliminata o appartiene a un altro account.
conflict409
La richiesta è in conflitto con lo stato attuale della risorsa. riservato
invalid422
Alcuni dati non hanno superato la validazione: details.fields elenca i problemi campo per campo, details.messages come frasi già pronte.
domain_not_allowed422
Il tuo account non può usare questo dominio. GET /domains elenca quelli che può usare.
bad_request400
Manca un parametro obbligatorio; details.parameter indica quale.
invalid_json400
Il corpo non è JSON valido. Controlla le virgolette e l'header Content-Type.
invalid_parameter400
Un parametro ha un valore che l'endpoint non conosce. Il messaggio indica il parametro e details.allowed, se presente, elenca i valori accettati.
bulk_empty400
L'array links di una chiamata bulk è vuoto o manca.
bulk_too_many400
Troppi link in una sola chiamata bulk. details.max è il limite del tuo piano: dividi l'elenco in più chiamate.
idempotency_key_invalid400
L'header Idempotency-Key supera i 128 caratteri.
rate_limited429
Troppe richieste in questo minuto. Aspetta i secondi indicati in Retry-After, poi riprendi.

Una risposta 500 significa che qualcosa si è rotto dalla nostra parte e che nella tua richiesta non c'è niente da correggere. Riprova un po' più tardi; se continua a succedere, scrivi all'assistenza indicando il valore dell'header X-Request-Id.

Paginazione

Gli elenchi che possono diventare lunghi sono divisi in pagine: GET /links, GET /links/active e GET /links/:id/clicks.

  • page: la pagina che vuoi, a partire da 1.
  • per_page: quanti elementi per pagina, da 1 a 100 (predefinito 50). limit è accettato come sinonimo.

La risposta contiene un oggetto pagination, e il totale si trova anche nell'header X-Total-Count. Continua a chiedere next_page finché non vale null.

Risposta
{
  "links": [],
  "pagination": { "page": 2, "per_page": 50, "total": 137, "total_pages": 3, "next_page": 3, "prev_page": 1 }
}

Idempotenza

POST /links, POST /links/bulk e POST /links/generate accettano un header Idempotency-Key: un testo qualsiasi scelto da te, fino a 128 caratteri, uno per operazione — un numero d'ordine, un UUID.

Se lo stesso valore arriva di nuovo, con la stessa chiave API, entro 24 ore, pik.li non crea nulla: restituisce la prima risposta, con l'header Idempotent-Replayed: true. Una richiesta ripetuta dopo un timeout non crea mai un duplicato.

Stessa richiesta, stessa chiave, entro 24 ore
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

Le risposte con stato 5xx non vengono memorizzate, quindi se ripeti la richiesta con lo stesso valore viene eseguita di nuovo. Se il valore è troppo lungo, la risposta è idempotency_key_invalid.

Lingue

I messaggi di errore sono scritti in nove lingue: en it zh ar ru fr de es pt-BR. Le API scelgono in base al parametro locale, poi all'header Accept-Language, poi alla lingua del tuo account; in mancanza usano l'inglese. Cambia solo il messaggio: codici e nomi dei campi restano gli stessi, e così docs_url, che apre questa pagina nella stessa lingua.

Terminale
curl "https://pik.li/api/v1/links?status=nope" \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Accept-Language: it"

{
  "error": {
    "code": "invalid_parameter",
    "message": "Valore non valido per il parametro status.",
    "details": { "allowed": ["all", "active", "disabled", "review", "expired"] },
    "docs_url": "https://pik.li/help?locale=it#error-invalid_parameter"
  },
  "request_id": "0d5c9b7e-1f2a-4e3b-8c6d-7a9e0f1b2c3d"
}

Finestre delle statistiche

Gli endpoint delle statistiche leggono una finestra di tempo e la confrontano con quella immediatamente precedente, della stessa durata: da qui vengono previous e delta_pct.

period
24h, 7d, 30d, 90d, 1y o all (tutto lo storico che il tuo piano conserva). Funziona anche un numero di giorni: period=45, o il vecchio days=45. Il valore predefinito è 7d per l'account, 30d per un singolo link e all per l'elenco dei click.
from, to
In alternativa a period: una data (2026-09-01) o un orario ISO 8601. Se non li indichi, to corrisponde al momento attuale e from a 30 giorni prima di to; una data in to comprende tutto quel giorno.
interval
hour o day, il passo della serie temporale. Le finestre fino a 48 ore sono orarie per impostazione predefinita, quelle più lunghe giornaliere; una serie oraria copre al massimo 7 giorni.
window.clamped
Una finestra non va mai più indietro dello storico che il tuo piano conserva (retention_days). Quando è stata tagliata, window.clamped è true.
bots
I click dei bot sono esclusi. Aggiungi bots=1 per contarli; le statistiche di un singolo link li riportano anche a parte, in period.bots.

Endpoint

Ogni endpoint con i suoi parametri, una richiesta da incollare nel terminale e una risposta reale, accorciata dove un elenco sarebbe lungo. Gli esempi leggono la chiave da $PIKLI_KEY, come nell'avvio rapido.

Servizio

Se le API sono attive e quanto resta del tuo piano.

GET /ping Senza chiave

Controlla che le API rispondano

Non richiede chiave: è pensato per gli health check. Restituisce l'ora del server e gli indirizzi di questa documentazione e del file OpenAPI.

Richiesta
curl https://pik.li/api/v1/ping
Risposta · 200
{
  "ok": true,
  "service": "pik.li",
  "version": "v1",
  "time": "2026-09-23T08:15:42Z",
  "docs": "https://pik.li/help",
  "openapi": "https://pik.li/openapi.json"
}
Errori Nessuno: questo endpoint non richiede una chiave.
GET /limits

Quanto resta delle tue quote

I link ancora disponibili oggi o questo mese, i click tracciati, la finestra di rate limit in corso, le chiavi e i domini personalizzati in uso e la dimensione massima di una chiamata bulk. Ogni numero tiene conto delle modifiche che lo staff può aver fatto al tuo account.

Richiesta
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "plan": "premium",
  "plan_name": "Premium",
  "links": {
    "period": "month", "limit": 2500, "used": 412, "remaining": 2088,
    "resets_at": "2026-09-30T22:00:00Z",
    "today": 9, "this_month": 412, "per_day": 0, "per_month": 2500
  },
  "clicks": { "limit": 150000, "used": 38120, "remaining": 111880, "resets_at": "2026-09-30T22:00:00Z" },
  "rate_limit": { "limit": 600, "remaining": 597, "period_seconds": 60, "resets_at": "2026-09-23T08:16:00Z" },
  "api_keys": { "used": 2, "limit": 10 },
  "custom_domains": { "used": 1, "limit": 3 },
  "bulk": { "max_items": 100 },
  "retention_days": 730,
  "link_ttl_days": 0,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"]
}
Errori Solo quelli che può restituire qualsiasi endpoint.

Account

Chi sta chiamando: l'account a cui appartiene la chiave.

GET /me

Il tuo account e la chiave in uso

Profilo, piano e relativa scadenza, funzioni, limiti e consumi, e la chiave che ha fatto la chiamata, con il suo contatore di richieste.

Richiesta
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "id": 1042,
  "username": "anna",
  "email": "[email protected]",
  "name": "Anna Rossi",
  "status": "active",
  "confirmed": true,
  "locale": "it",
  "plan": "premium",
  "plan_name": "Premium",
  "interval": "monthly",
  "plan_expires_at": "2026-10-14T23:59:59.000+02:00",
  "api_enabled": true,
  "features": ["qr", "analytics_full", "tags", "export", "custom_slug", "password", "expiry", "utm", "bulk", "api", "webhooks"],
  "limits": {
    "links_per_day": 0, "links_per_month": 2500, "clicks_per_month": 150000,
    "retention_days": 730, "link_ttl_days": 0, "custom_domains": 3, "api_rpm": 600
  },
  "usage": {
    "links_today": 9, "links_this_month": 412, "clicks_this_month": 38120,
    "links_quota_used": 412, "links_quota_limit": 2500, "links_total": 3187
  },
  "api_key": {
    "id": 55, "name": "CRM production", "prefix": "pk_live_Xq3v",
    "created_at": "2026-09-02T16:40:10.311+02:00", "last_used_at": "2026-09-23T10:15:42.020+02:00", "requests_count": 18230
  },
  "created_at": "2026-03-11T09:12:47.664+01:00"
}
Errori Solo quelli che può restituire qualsiasi endpoint.

Statistiche dell'account

I numeri di tutti i tuoi link insieme, con i filtri della dashboard: finestra, dominio, tag, bot. Per i parametri comuni vedi Finestre delle statistiche.

GET /stats

Statistiche dell'account in una chiamata

Totali dell'account, la finestra confrontata con la precedente, la serie temporale, i primi 10 referrer e paesi, i primi 6 dispositivi, browser e sistemi operativi e i 10 link più cliccati. Gli altri endpoint /stats restituiscono ogni parte da sola, con elenchi più lunghi.

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • interval string query string

    hour o day, il passo della serie temporale.

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

Richiesta
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "totals": {
    "links": 3187, "active_links": 2954, "clicks": 402118, "unique_clicks": 288930,
    "clicks_today": 1204, "links_active_24h": 214, "links_created_in_window": 61
  },
  "period": { "clicks": 21480, "uniques": 15522, "links_clicked": 690, "clicks_per_link": 31.1 },
  "previous": { "clicks": 19870, "uniques": 14410, "links_clicked": 655, "clicks_per_link": 30.3, "delta_pct": 8.1, "uniques_delta_pct": 7.7 },
  "timeseries": {
    "interval": "day",
    "points": [
      { "at": "2026-09-17", "clicks": 2890, "uniques": 2104 },
      { "at": "2026-09-18", "clicks": 3120, "uniques": 2240 }
    ]
  },
  "referrers": [{ "referrer": "instagram.com", "clicks": 5210, "share": 24.3 }],
  "direct": 8740,
  "countries": [{ "country": "IT", "clicks": 12877, "share": 59.9 }],
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "top_links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}
GET /stats/timeseries

Click nel tempo

Click e visitatori unici per ora o per giorno, con il totale della finestra precedente e la variazione percentuale.

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • interval string query string

    hour o day, il passo della serie temporale.

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

Richiesta
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-22T10:15:42+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 1,
    "interval": "hour",
    "period": "24h",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-21T10:15:42+02:00", "to": "2026-09-22T10:15:42+02:00" }
  },
  "filters": { "bots": false },
  "interval": "hour",
  "clicks": 3308,
  "previous_clicks": 2977,
  "delta_pct": 11.1,
  "points": [
    { "at": "2026-09-22T08:00:00Z", "clicks": 131, "uniques": 97 },
    { "at": "2026-09-22T09:00:00Z", "clicks": 158, "uniques": 120 }
  ]
}
GET /stats/referrers

Da dove arrivano i click

I siti che hanno portato più click, ciascuno con la sua quota sul totale, e i click senza referrer (direct).

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

  • limit integer query string

    Quante righe per elenco, da 1 a 50 (predefinito 10).

Richiesta
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "direct": 8740,
  "referrers": [
    { "referrer": "instagram.com", "clicks": 5210, "share": 24.3 },
    { "referrer": "google.com", "clicks": 3011, "share": 14.0 }
  ]
}
GET /stats/countries

Paesi e città

Paesi come codici ISO, con la loro quota; cities è disponibile con i piani che includono le statistiche complete.

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

  • limit integer query string

    Quante righe per elenco, da 1 a 50 (predefinito 10).

Richiesta
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "countries": [
    { "country": "IT", "clicks": 12877, "share": 59.9 },
    { "country": "DE", "clicks": 2630, "share": 12.2 }
  ],
  "cities": [
    { "city": "Milano", "clicks": 3120, "share": 14.5 },
    { "city": "Roma", "clicks": 2210, "share": 10.3 }
  ]
}
GET /stats/devices

Dispositivi, browser, sistemi e lingue

Quattro elenchi in una chiamata, ciascuno con i click e la quota sul totale.

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

  • limit integer query string

    Quante righe per elenco, da 1 a 50 (predefinito 10).

Richiesta
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "devices": [{ "device": "mobile", "clicks": 14002, "share": 65.2 }, { "device": "desktop", "clicks": 7100, "share": 33.1 }],
  "browsers": [{ "browser": "Chrome", "clicks": 9406, "share": 43.8 }],
  "os": [{ "os": "iOS", "clicks": 7340, "share": 34.2 }],
  "languages": [{ "language": "it", "clicks": 12690, "share": 59.1 }]
}
GET /stats/top

I link più cliccati

I link con più click nella finestra, ciascuno con clicks_in_window e uniques_in_window.

Parametri
  • period string query string

    La finestra: 24h, 7d (predefinita), 30d, 90d, 1y, all. Vedi Finestre delle statistiche.

  • from string query string

    Inizio della finestra, in alternativa a period: una data o un orario ISO 8601.

  • to string query string

    Fine della finestra (predefinito: il momento attuale).

  • domain string query string

    Solo questo dominio, indicato per hostname (per esempio lnkz.li). Deve essere uno dei tuoi: vedi GET /domains.

  • tag string query string

    Solo i link con questo tag.

  • bots string query string

    1 per contare anche i click dei bot.

  • limit integer query string

    Quante righe per elenco, da 1 a 50 (predefinito 10).

Richiesta
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "window": {
    "from": "2026-09-17T00:00:00+02:00",
    "to": "2026-09-23T10:15:42+02:00",
    "days": 7,
    "interval": "day",
    "period": "7d",
    "clamped": false,
    "retention_days": 730,
    "previous": { "from": "2026-09-10T00:00:00+02:00", "to": "2026-09-17T00:00:00+02:00" }
  },
  "filters": { "bots": false },
  "clicks": 21480,
  "links": [{
    "id": 4821,
    "short_url": "https://lnkz.li/spring-sale",
    "slug": "spring-sale",
    "domain": "lnkz.li",
    "target_url": "https://example.com/shop/spring-sale",
    "target_host": "example.com",
    "title": "Spring sale",
    "description": null,
    "tags": ["promo", "newsletter"],
    "status": "active",
    "moderation": "allowed",
    "active": true,
    "expired": false,
    "password_protected": false,
    "clicks": 1284,
    "unique_clicks": 902,
    "last_clicked_at": "2026-09-23T09:58:12.431+02:00",
    "expires_at": null,
    "max_clicks": null,
    "disabled_reason": null,
    "utm": { "source": "newsletter", "medium": "email", "campaign": "spring" },
    "preview_url": "https://lnkz.li/spring-sale+",
    "qr_url": "https://pik.li/api/v1/links/4821/qr",
    "created_at": "2026-09-01T08:30:05.117+02:00",
    "updated_at": "2026-09-01T08:30:05.117+02:00",
    "clicks_in_window": 311,
    "uniques_in_window": 240
  }]
}

Tag

Le etichette che metti sui tuoi link.

GET /tags

I tuoi tag

Tutti i tag usati sui tuoi link, con il numero di link che li portano e i click che hanno raccolto, a partire dai più usati.

Parametri
  • q string query string

    Solo i tag che contengono questo testo.

Richiesta
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
Errori Solo quelli che può restituire qualsiasi endpoint.

Domini

Dove possono vivere i tuoi link corti.

GET /domains

I domini che puoi usare

I domini pik.li consentiti dal tuo piano e i tuoi domini personalizzati verificati, qual è quello predefinito e quanti domini personalizzati consente il tuo piano. Quando crei un link, passa hostname come domain.

Richiesta
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "domains": [
    { "id": 1, "hostname": "lnkz.li", "base_url": "https://lnkz.li", "kind": "system", "min_plan": "free", "status": "active", "verified": true, "default": true },
    { "id": 318, "hostname": "go.example.com", "base_url": "https://go.example.com", "kind": "custom", "min_plan": "free", "status": "active", "verified": true, "default": false }
  ],
  "default": "lnkz.li",
  "custom_domains": { "used": 1, "limit": 3 }
}
Errori Solo quelli che può restituire qualsiasi endpoint.

WebhookPiani: Premium · Business

Gestisci dal tuo codice i webhook che altrimenti configureresti nella pagina Webhook della dashboard. Come funzionano le consegne è spiegato più sotto, nella sezione Webhook.

GET /webhooks

Elenca i tuoi webhook

I tuoi webhook e gli eventi a cui puoi iscriverti. Il secret non viene più mostrato dopo la creazione.

Richiesta
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "webhooks": [{
    "id": 17,
    "url": "https://example.com/hooks/pikli",
    "events": ["link.created", "link.disabled"],
    "active": true,
    "failures_count": 0,
    "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
    "created_at": "2026-09-10T11:20:33.004+02:00",
    "updated_at": "2026-09-10T11:20:33.004+02:00",
    "signature_header": "X-Pikli-Signature"
  }],
  "events": ["link.created", "link.clicked", "link.disabled", "link.deleted"]
}
GET /webhooks/:id

Leggi un webhook

Il webhook con le ultime 20 consegne e lo stato con cui il tuo server ha risposto a ciascuna.

Parametri
  • id integer nel percorso obbligatorio

    L'id del webhook.

Richiesta
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "deliveries": [
    { "id": 903, "event": "ping", "response_code": 200, "attempts": 1, "delivered_at": "2026-09-22T18:04:11.520+02:00", "created_at": "2026-09-22T18:04:11.520+02:00" }
  ]
}
POST /webhooks

Crea un webhook

La risposta contiene il secret usato per firmare le consegne, solo questa volta: salvalo subito.

Parametri
  • url string corpo JSON obbligatorio

    L'indirizzo che riceve le consegne, http:// o https://, raggiungibile da internet.

  • events array | string corpo JSON

    Gli eventi da ricevere, come array o separati da virgole. Se non li indichi: tutti. Vedi l'elenco degli eventi più sotto.

  • active boolean corpo JSON

    false per crearlo in pausa. Predefinito true.

Richiesta
curl -X POST https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/pikli","events":["link.created","link.disabled"]}'
Risposta · 201
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": true,
  "failures_count": 0,
  "last_delivered_at": null,
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-10T11:20:33.004+02:00",
  "signature_header": "X-Pikli-Signature",
  "secret": "ec307f56cfac71856a10d641eee7dd58e0eb8286"
}
PATCH /webhooks/:id

Modifica un webhook

Invia solo ciò che cambia: indirizzo, eventi, oppure active per sospendere e riprendere le consegne.

Si può usare anche PUT, con gli stessi parametri.

Parametri
  • id integer nel percorso obbligatorio

    L'id del webhook.

  • url string corpo JSON

    Un nuovo indirizzo.

  • events array | string corpo JSON

    Il nuovo elenco di eventi; non può essere vuoto.

  • active boolean corpo JSON

    false sospende le consegne, true le riprende.

Richiesta
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
Risposta · 200
{
  "id": 17,
  "url": "https://example.com/hooks/pikli",
  "events": ["link.created", "link.disabled"],
  "active": false,
  "failures_count": 0,
  "last_delivered_at": "2026-09-22T18:04:11.520+02:00",
  "created_at": "2026-09-10T11:20:33.004+02:00",
  "updated_at": "2026-09-23T10:15:42.118+02:00",
  "signature_header": "X-Pikli-Signature"
}
DELETE /webhooks/:id

Elimina un webhook

Le consegne si fermano e il loro storico viene eliminato insieme al webhook.

Parametri
  • id integer nel percorso obbligatorio

    L'id del webhook.

Richiesta
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 204
Nessun corpo: il codice di stato dice già tutto.
POST /webhooks/:id/test

Invia una consegna di prova

Mette in coda un ping verso l'indirizzo del webhook, firmato come ogni consegna: il modo più rapido per provare il codice che verifica la firma. Parte poco dopo e compare in GET /webhooks/:id.

Parametri
  • id integer nel percorso obbligatorio

    L'id del webhook.

Richiesta
curl -X POST https://pik.li/api/v1/webhooks/17/test \
  -H "Authorization: Bearer $PIKLI_KEY"
Risposta · 202
{
  "queued": true,
  "event": "ping",
  "webhook_id": 17
}

WebhookPiani: Premium · Business

Un webhook è un indirizzo sul tuo server che pik.li chiama con un POST quando succede qualcosa nel tuo account. Puoi crearne uno nella pagina Webhook della dashboard o tramite gli endpoint dei webhook.

Eventi

ping
La consegna di prova, inviata quando premi Test nella dashboard o chiami POST /webhooks/:id/test.
link.created
È stato creato un link, dalla dashboard o dalle API. in arrivo
link.clicked
Qualcuno ha cliccato uno dei tuoi link. in arrivo
link.disabled
Un link è stato disattivato, da te o dalla moderazione. in arrivo
link.deleted
Un link è stato eliminato. in arrivo
Gli eventi dei link si possono già scegliere in un webhook, ma le loro consegne non sono ancora partite: per ora pik.li invia solo il ping di prova.

Com'è fatta una consegna

Un POST con un corpo JSON di tre campi: event, il nome dell'evento; created_at, quando è stata inviata; data, i dettagli dell'evento. Arriva con questi header:

Cosa riceve il tuo server
POST /hooks/pikli HTTP/1.1
Content-Type: application/json
X-Pikli-Event: ping
X-Pikli-Signature: sha256=bdaf35d42413498dab7fea8be1f292c36b690152e5ec7d66e6dd2c2cd0b2d61c

{"event":"ping","created_at":"2026-09-23T10:15:42+02:00","data":{"message":"pik.li webhook test","at":"2026-09-23T10:15:42+02:00","via":"api"}}
Content-Type
sempre application/json.
X-Pikli-Event
il nome dell'evento, lo stesso del corpo: ti permette di smistare la richiesta prima di leggerla.
X-Pikli-Signature
sha256= seguito dalla firma del corpo, come descritto più sotto.

Verificare la firma

Ogni consegna è firmata con il secret del suo webhook, che ricevi una sola volta, quando lo crei. L'header X-Pikli-Signature contiene sha256= seguito dall'HMAC-SHA256 del corpo grezzo, in esadecimale. Calcola lo stesso HMAC sui byte che hai ricevuto — prima di decodificarli come JSON, operazione che li cambierebbe — e confronta le due stringhe in tempo costante. Se sono diverse, rispondi 401 e ignora la richiesta.

Per provare il tuo codice: con il secret ec307f56cfac71856a10d641eee7dd58e0eb8286 dell'esempio qui sopra, il corpo della consegna mostrata qui produce esattamente la firma che compare nel suo header.

Node.js
import crypto from "node:crypto";
import express from "express";

const app = express();

app.post("/hooks/pikli", express.raw({ type: "application/json" }), (req, res) => {
  const expected = "sha256=" + crypto
    .createHmac("sha256", process.env.PIKLI_WEBHOOK_SECRET)
    .update(req.body)
    .digest("hex");
  const received = req.get("X-Pikli-Signature") || "";
  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  res.sendStatus(204);
});
Python
import hashlib, hmac, os
from flask import Flask, abort, request

app = Flask(__name__)
SECRET = os.environ["PIKLI_WEBHOOK_SECRET"].encode()

@app.post("/hooks/pikli")
def pikli_hook():
    body = request.get_data()
    expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, request.headers.get("X-Pikli-Signature", "")):
        abort(401)
    event = request.get_json()
    return "", 204
Ruby
class PikliHooksController < ActionController::API
  def create
    body = request.raw_post
    expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("PIKLI_WEBHOOK_SECRET"), body)
    received = request.headers["X-Pikli-Signature"].to_s
    return head(:unauthorized) unless ActiveSupport::SecurityUtils.secure_compare(expected, received)

    event = JSON.parse(body)
    head :no_content
  end
end
PHP
<?php
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, getenv('PIKLI_WEBHOOK_SECRET'));
$received = $_SERVER['HTTP_X_PIKLI_SIGNATURE'] ?? '';
if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit;
}
$event = json_decode($body, true);
http_response_code(204);

Risposte, nuovi tentativi e fallimenti

  • Rispondi il prima possibile con un qualsiasi stato 2xx e rimanda a dopo le operazioni lente.
  • Se pik.li non riesce proprio a raggiungere il tuo indirizzo — errore di rete, connessione rifiutata, un indirizzo non pubblico — la consegna viene tentata fino a 5 volte, con pause sempre più lunghe: l'ultimo tentativo arriva circa sei minuti dopo il primo.
  • Una risposta con stato 400 o superiore viene registrata e non ripetuta. Aumenta di uno il failures_count del webhook, che torna a zero alla prima consegna riuscita.
  • Le ultime 20 consegne, con lo stato con cui ha risposto il tuo server, si trovano in GET /webhooks/:id e nella pagina Webhook della dashboard.
  • L'indirizzo deve essere pubblico: pik.li non chiama reti private o locali. Usa https://, così le consegne viaggiano cifrate.

OpenAPI

Lo stesso riferimento, in un formato leggibile dalle macchine, è pubblicato su /openapi.json (OpenAPI 3.1). Importalo in Postman, Insomnia o Bruno, oppure genera un client con openapi-generator.

Manca qualcosa, o qualcosa non funziona come dice questa pagina? Scrivi a [email protected].