pik.li pik.li
版本 1 · 稳定

pik.li API

用你自己的代码创建、修改短链接并查看数据。这是一个收发 JSON 的 REST API,每个集成使用独立的密钥;每个接口都在本页有说明,并附有请求示例和真实响应。

获取 API 密钥 OpenAPI 3.1
基础 URLhttps://pik.li/api/v1

简介

借助 pik.li API,你的软件可以完成控制台能做的事:创建和编辑短链接、读取统计数据、列出标签和域名,以及管理 Webhook。API 通过 HTTPS 提供,所有路径都以上方显示的基础 URL 开头。

  • 请求体以 JSON 格式发送,并带上 Content-Type: application/jsonGET 请求的参数放在查询字符串中。响应始终是 UTF-8 编码的 JSON,只有二维码例外,它返回的是图片。
  • 版本号是路径的一部分(/api/v1),每个响应都带有 X-Api-Version: 1 响应头。v1 的响应中今后可能会出现新字段:请让你的代码忽略不认识的字段。
  • 日期和时间是带时区偏移的 ISO 8601 字符串(2026-09-23T10:15:42.118+02:00);只有日期时形如 2026-09-23
  • 标识符是整数。也可以用 GET /links/lookup 通过短链接地址查找链接。

快速开始

  1. 在控制台中创建密钥(见创建密钥),并把它保存在环境变量中。
  2. 确认密钥可用:GET /me 会返回你的账户和方案。
  3. POST /links 创建你的第一个短链接。
终端
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"}'

身份验证

GET /ping 外,每次调用都需要 API 密钥,以 Bearer 令牌的形式放在 Authorization 请求头中发送。

请求头
Authorization: Bearer pk_live_3kZ9vQeX1mT0bR7yNw2aLp4sUc8dHf6J

也可以使用 X-API-Key 请求头。为兼容旧客户端,仍然接受 api_key 查询参数,但请尽量避免:URL 会留在日志和浏览器历史记录中。

密钥以 pk_live_ 开头,后跟 32 个字符。密钥能看到的内容与其所属账户完全相同——链接、域名、标签、统计数据和 Webhook;同一账户的所有密钥权限都一样:没有只读密钥。

像对待密码一样对待密钥。只把它放在你的服务器上,绝不要写进在浏览器中运行的代码,也不要放进分发给别人的应用。一旦泄露,就吊销它并创建新的密钥:只需十秒钟。

创建密钥

  1. 登录后,在控制台中打开 API 与密钥 页面。
  2. 给密钥起一个能说明用途的名称(例如 CRM 生产环境),然后点击生成密钥
  3. 立即复制密钥:控制台只会显示一次。
  4. 你最多可以保留 10 个有效密钥。页面会显示每个密钥最近一次使用的时间和已发出的请求数,并可以吊销密钥:被吊销的密钥从下一次调用起会收到 401 unauthorized

谁可以使用 API

API 包含在付费方案中(Premium和Business)。Base 方案不含 API,除非 pik.li 团队为免费方案或你的账户开通了它。无论哪种情况,账户都必须已确认邮箱地址,且未被封禁或暂停。

缺少其中任一条件时,API 会返回 api_disabledemail_unconfirmedaccount_blocked

Base

需由团队开通
每分钟请求数
60
新建链接
每天 3 个
每次批量调用的链接数
10
统计保留时长
90 天
Webhook

Premium

包含 API
每分钟请求数
600
新建链接
每月 2,500 个
每次批量调用的链接数
100
统计保留时长
730 天
Webhook

Business

包含 API
每分钟请求数
3,000
新建链接
每月 10,000 个
每次批量调用的链接数
100
统计保留时长
1,095 天
Webhook

以上是各方案的标准数值,实时读取自价格设置。你自己的数值,包括团队对你账户所做的调整,可以通过 GET /limitsGET /me 获取。

速率限制

每个密钥每分钟可以发出的请求数取决于方案(见上表)。计数在每分钟开始时重新计算。

X-RateLimit-Limit
此密钥每分钟允许的请求数
X-RateLimit-Remaining
当前这一分钟内剩余的请求数
X-RateLimit-Reset
计数重新开始的时间,以秒为单位的 Unix 时间
Retry-After
仅在 429 响应中出现:需要等待的秒数

超出限制时,API 返回 429,并附带错误 rate_limitedRetry-After 响应头。等待相应的时长后再继续:立即重试只会占用下一分钟的额度。

响应
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"
}
  • 不带密钥的调用,每个 IP 地址每分钟最多 30 次(GET /ping 不计入)。
  • 此外,无论使用哪个方案,每个 IP 地址对整个 pik.li 每分钟最多发出 600 次请求。

错误

请求失败时,无论调用的是哪个接口,响应的结构都相同,HTTP 状态码则对应具体的问题。

响应
{
  "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
稳定的标识符,供你的代码使用。它永远不会改变,也不会被翻译。
message
写给人看的一句话,使用请求的语言(见“语言”)。
details
在有帮助时提供的结构化数据:无效的字段、允许的值、配额的数字。并非总会出现。
docs_url
指向本页中该错误代码说明的链接。
request_id
请求的标识符,也会通过 X-Request-Id 响应头发送。联系支持时请附上它。

每个需要密钥的接口还可能返回 unauthorized account_blocked email_unconfirmed api_disabled rate_limited:这些错误不会在各个接口下重复列出。

unauthorized401
密钥缺失、错误或已被吊销。检查 Authorization 请求头,并确认该密钥在控制台中仍显示为有效。
account_blocked403
账户已被封禁、暂停或停用。在账户恢复正常之前,API 保持关闭。
email_unconfirmed403
账户的邮箱地址尚未确认。点击确认邮件中的链接,然后重试。
api_disabled403
此账户的方案不包含 API,或者 API 已对该账户关闭。见“谁可以使用 API”。
forbidden403
该密钥无法执行此操作。 预留
feature_required402
方案不包含此接口所需的功能。details.feature 给出功能名称,details.plan 给出第一个包含该功能的方案。
quota_exceeded402
当天或当月的链接配额已用完。details 包含 limit、used、period 和 resets_at,即可以再次创建链接的时间。
not_found404
资源不存在、已被删除或属于其他账户。
conflict409
请求与资源的当前状态冲突。 预留
invalid422
部分数据未通过验证:details.fields 按字段列出问题,details.messages 则以现成的句子给出。
domain_not_allowed422
你的账户无法使用此域名。GET /domains 会列出可以使用的域名。
bad_request400
缺少必填参数;details.parameter 给出参数名。
invalid_json400
请求体不是有效的 JSON。检查引号和 Content-Type 请求头。
invalid_parameter400
某个参数的值不在接口认可的范围内。错误消息会指出是哪个参数;如果有 details.allowed,它会列出可以接受的值。
bulk_empty400
批量调用的 links 数组为空或缺失。
bulk_too_many400
单次批量调用的链接过多。details.max 是你的方案的上限:请把列表拆分成多次调用。
idempotency_key_invalid400
Idempotency-Key 请求头超过了 128 个字符。
rate_limited429
这一分钟内的请求过多。等待 Retry-After 给出的秒数后再继续。

500 响应表示我们这边出了问题,你的请求并没有需要修改的地方。请稍后重试;如果问题持续出现,请联系支持,并附上 X-Request-Id 响应头的值。

分页

可能很长的列表会分页返回:GET /linksGET /links/activeGET /links/:id/clicks

  • page:要获取的页码,从 1 开始。
  • per_page:每页的条目数,1 到 100(默认 50)。也可以用 limit 代替。

响应中带有 pagination 对象,总数也会放在 X-Total-Count 响应头中。持续请求 next_page,直到它为 null

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

幂等性

POST /linksPOST /links/bulkPOST /links/generate 接受 Idempotency-Key 请求头:由你自己选定的任意文本,最多 128 个字符,每个操作使用一个——例如订单号或 UUID。

当同一个 API 密钥在 24 小时内再次发送相同的幂等键时,pik.li 不会创建任何内容,而是返回第一次的响应,并带上 Idempotent-Replayed: true 响应头。这样,超时后重发的请求绝不会产生重复数据。

同一请求、同一密钥,24 小时内再次发送
HTTP/1.1 201 Created
Idempotent-Replayed: true
Content-Type: application/json; charset=utf-8

状态码为 5xx 的响应不会被记录,因此用同一个幂等键重发请求时会重新执行。幂等键过长时会返回 idempotency_key_invalid

语言

错误消息提供九种语言:en it zh ar ru fr de es pt-BR。API 依次根据 locale 参数、Accept-Language 请求头和你账户的语言来选择,都没有时使用英语。变化的只有消息本身:代码和字段名保持不变;docs_url 则会以同一种语言打开本页。

终端
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"
}

统计时间窗口

统计接口读取一段时间窗口,并与紧挨在它之前、长度相同的窗口进行比较:previous 和 delta_pct 就是这样得出的。

period
24h7d30d90d1yall(你的方案保留的全部历史)。也可以直接写天数:period=45,或旧写法 days=45。默认值:账户统计为 7d,单个链接为 30d,点击列表为 all
from, to
用来代替 period:一个日期(2026-09-01)或 ISO 8601 时间。to 默认为当前时间,from 默认为 to 之前 30 天;to 中的日期会一直算到当天结束。
interval
hourday,即时间序列的步长。不超过 48 小时的窗口默认按小时,更长的窗口按天;按小时的序列最多覆盖 7 天。
window.clamped
窗口不会超出你的方案保留的历史范围(retention_days)。窗口被截断时,window.clampedtrue
bots
来自机器人的点击不计入。加上 bots=1 可以把它们也计算在内;单个链接的统计还会在 period.bots 中单独列出。

接口

每个接口都列出了参数、可以直接粘贴到终端的请求和一个真实响应,列表较长时有所删减。示例从 $PIKLI_KEY 读取密钥,与快速开始中一致。

服务

API 是否正常运行,你的方案还剩多少额度。

GET /ping 无需密钥

检查 API 是否正常运行

无需密钥,用于健康检查。返回服务器时间,以及本文档和 OpenAPI 文件的地址。

请求
curl https://pik.li/api/v1/ping
响应 · 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"
}
错误 无:此接口无需密钥。
GET /limits

配额还剩多少

今天或本月还能创建的链接数、已追踪的点击、当前的速率限制窗口、已使用的密钥和自定义域名,以及单次批量调用的上限。所有数字都已包含团队可能对你账户所做的调整。

请求
curl https://pik.li/api/v1/limits \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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"]
}
错误 仅列出所有接口都可能返回的错误。

账户

谁在调用:拥有该密钥的账户。

GET /me

你的账户和当前使用的密钥

个人资料、方案及其到期时间、功能、限额与用量,以及发起此次调用的密钥及其请求计数。

请求
curl https://pik.li/api/v1/me \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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"
}
错误 仅列出所有接口都可能返回的错误。

账户统计

你所有链接的汇总数据,支持控制台中的筛选条件:时间窗口、域名、标签、机器人。通用参数见“统计时间窗口”。

GET /stats

一次调用获取账户统计

账户总计、当前窗口与上一个窗口的对比、时间序列、前 10 个来源和国家、前 6 个设备、浏览器和操作系统,以及点击最多的 10 个链接。其他 /stats 接口会分别单独返回各个部分,列表也更长。

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • interval string 查询字符串

    hourday,即时间序列的步长。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

请求
curl "https://pik.li/api/v1/stats?period=7d" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

点击随时间的变化

按小时或按天统计的点击数和独立访客数,并附上一个窗口的总计和变化百分比。

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • interval string 查询字符串

    hourday,即时间序列的步长。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

请求
curl "https://pik.li/api/v1/stats/timeseries?period=24h" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

点击从哪里来

带来点击最多的网站,每个都附有占总数的比例,以及没有来源的点击(direct)。

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

  • limit integer 查询字符串

    每个列表的行数,1 到 50(默认 10)。

请求
curl "https://pik.li/api/v1/stats/referrers?period=30d&limit=5" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

国家和城市

国家以 ISO 代码表示,并附有占比;cities 仅在包含完整统计的方案中提供。

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

  • limit integer 查询字符串

    每个列表的行数,1 到 50(默认 10)。

请求
curl "https://pik.li/api/v1/stats/countries?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

设备、浏览器、系统和语言

一次调用返回四个列表,每一项都附有点击数和占总数的比例。

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

  • limit integer 查询字符串

    每个列表的行数,1 到 50(默认 10)。

请求
curl "https://pik.li/api/v1/stats/devices?period=30d" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

点击最多的链接

窗口内点击最多的链接,每个都带有 clicks_in_windowuniques_in_window

参数
  • period string 查询字符串

    时间窗口:24h7d(默认)、30d90d1yall。见“统计时间窗口”。

  • from string 查询字符串

    窗口起点,用来代替 period:日期或 ISO 8601 时间。

  • to string 查询字符串

    窗口终点(默认为当前时间)。

  • domain string 查询字符串

    仅限此域名,按主机名指定(例如 lnkz.li)。必须是你的域名之一:见 GET /domains

  • tag string 查询字符串

    仅限带有此标签的链接。

  • bots string 查询字符串

    设为 1 时,来自机器人的点击也计算在内。

  • limit integer 查询字符串

    每个列表的行数,1 到 50(默认 10)。

请求
curl "https://pik.li/api/v1/stats/top?period=7d&limit=10" \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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
  }]
}

标签

你给链接加上的标签。

GET /tags

你的标签

你的链接用到的所有标签,附带使用该标签的链接数和这些链接获得的点击数,最常用的排在前面。

参数
  • q string 查询字符串

    仅限包含此文本的标签。

请求
curl https://pik.li/api/v1/tags \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 200
{
  "tags": [
    { "name": "promo", "links": 214, "clicks": 90211 },
    { "name": "newsletter", "links": 58, "clicks": 20473 }
  ],
  "total": 2
}
错误 仅列出所有接口都可能返回的错误。

域名

你的短链接可以使用的域名。

GET /domains

你可以使用的域名

你的方案允许使用的 pik.li 域名和你已验证的自定义域名、哪个是默认域名,以及你的方案允许多少个自定义域名。创建链接时,把 hostname 作为 domain 传入。

请求
curl https://pik.li/api/v1/domains \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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 }
}
错误 仅列出所有接口都可能返回的错误。

Webhook方案:Premium · Business

在代码中管理 Webhook,而不必在控制台的 Webhook 页面中手动设置。投递的工作方式见下方的“Webhook”一节。

GET /webhooks

列出 Webhook

你的 Webhook 以及可以订阅的事件。签名密钥在创建之后不会再次显示。

请求
curl https://pik.li/api/v1/webhooks \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

读取单个 Webhook

Webhook 及其最近 20 次投递,以及你的服务器对每次投递返回的状态码。

参数
  • id integer 路径 必填

    Webhook 的 id

请求
curl https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 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

创建 Webhook

响应中带有用于签名投递的 secret,而且只有这一次:请立即保存。

参数
  • url string JSON 请求体 必填

    接收投递的地址,http://https://,必须能从互联网访问。

  • events array | string JSON 请求体

    要接收的事件,数组或逗号分隔的文本均可。省略时接收全部事件。事件列表见下文。

  • active boolean JSON 请求体

    设为 false 时,创建后即处于暂停状态。默认为 true

请求
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"]}'
响应 · 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

修改 Webhook

只发送有变化的内容:地址、事件,或者用 active 暂停和恢复投递。

也接受 PUT,参数相同。

参数
  • id integer 路径 必填

    Webhook 的 id

  • url string JSON 请求体

    新的地址。

  • events array | string JSON 请求体

    新的事件列表;不能为空。

  • active boolean JSON 请求体

    false 暂停投递,true 恢复投递。

请求
curl -X PATCH https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"active":false}'
响应 · 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

删除 Webhook

投递随之停止,投递历史也会一并删除。

参数
  • id integer 路径 必填

    Webhook 的 id

请求
curl -X DELETE https://pik.li/api/v1/webhooks/17 \
  -H "Authorization: Bearer $PIKLI_KEY"
响应 · 204
没有响应体:状态码已说明一切。
POST /webhooks/:id/test

发送测试投递

向 Webhook 地址排队发送一个 ping,签名方式与所有投递相同:这是检查签名验证代码最快的方法。它会在稍后发出,并出现在 GET /webhooks/:id 中。

参数
  • id integer 路径 必填

    Webhook 的 id

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

Webhook方案:Premium · Business

Webhook 是你服务器上的一个地址,每当你的账户中发生某件事时,pik.li 都会用 POST 请求调用它。你可以在控制台的 Webhook 页面中创建,也可以通过 Webhook 接口 创建。

事件

ping
测试投递,在你点击控制台中的“测试”或调用 POST /webhooks/:id/test 时发送。
link.created
有链接被创建,无论是通过控制台还是 API。 即将推出
link.clicked
有人点击了你的某个链接。 即将推出
link.disabled
有链接被停用,无论是由你还是由审核停用。 即将推出
link.deleted
有链接被删除。 即将推出
链接事件已经可以在 Webhook 中选择,但相应的投递尚未开始:目前 pik.li 只发送测试 ping。

投递是什么样的

一个 POST 请求,JSON 请求体包含三个字段:event,事件名称;created_at,发送时间;data,事件详情。同时附带以下请求头:

你的服务器收到的内容
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
始终为 application/json。
X-Pikli-Event
事件名称,与请求体中的相同:可以在读取请求体之前据此分发请求。
X-Pikli-Signature
sha256= 后跟请求体的签名,详见下文。

验证签名

每次投递都用所属 Webhook 的签名密钥签名,这个密钥只在创建 Webhook 时提供一次。X-Pikli-Signature 请求头的内容是 sha256= 后跟原始请求体的 HMAC-SHA256,以十六进制表示。对你收到的原始字节计算同样的 HMAC——要在解析成 JSON 之前计算,因为解析会改变这些字节——然后用恒定时间比较两个字符串。如果不一致,就返回 401 并忽略该请求。

用来测试你的代码:使用上面示例中的签名密钥 ec307f56cfac71856a10d641eee7dd58e0eb8286,这里展示的投递请求体计算出的签名与其请求头中的签名完全一致。

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);

响应、重试与失败

  • 尽快返回任意 2xx 状态码,把耗时的工作留到之后处理。
  • 如果 pik.li 根本无法连接到你的地址——网络错误、连接被拒绝、地址不是公网地址——这次投递最多会尝试 5 次,间隔越来越长:最后一次尝试大约在第一次之后六分钟。
  • 状态码为 400 及以上的响应会被记录,但不会重试。它会让 Webhook 的 failures_count 加一;第一次投递成功后,该计数会归零。
  • 最近 20 次投递及你的服务器返回的状态码,可以在 GET /webhooks/:id 和控制台的 Webhook 页面中查看。
  • 地址必须是公网地址:pik.li 不会调用私有网络或本地网络。请使用 https://,让投递内容加密传输。

OpenAPI

同一份参考文档的机器可读版本发布在 /openapi.json(OpenAPI 3.1)。你可以把它导入 Postman、Insomnia 或 Bruno,也可以用 openapi-generator 生成客户端。

发现缺了什么,或者某些地方和本页描述的不一样?请写信至 [email protected]