Matches in other apps
Links API
mount /links · base https://zpe.vc/links · v1.0.0 · 37 operations · 22 write · 0 need no account · stable
archived: committed 2026-10-26, from 5e42ec30. Contract: https://zpe.vc/links/_/api/v1/openapi.json
GET
/_/api/v1/links
List all short links
getLinks
List all short links
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | array of object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
500 | Server error. | object |
Example
List all short links, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
[
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}
]The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links
Create a short link
postLinks
Create a short link
Parameters
No parameters.
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
url | string (uri) | yes | |
label | string | no | |
slug_length | integer | no | |
expires_at | integer | no | |
allow_duplicate | boolean | no |
Responses
| status | meaning | body |
|---|---|---|
200 | Existing link returned (duplicate URL). | object |
201 | Created. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
409 | Conflict. | object |
500 | Server error. | object |
Example
Create a short link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"url": "https://example.com",
"label": "string",
"slug_length": 0,
"expires_at": 0,
"allow_duplicate": false
}'{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}
Get a link with click stats
getLinksById
Get a link with click stats
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
500 | Server error. | object |
Example
Get a link with click stats, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
PUT
/_/api/v1/links/{id}
Update a link's URL, label, or expiry
putLinksById
Owner only. The identity behind the API key must be the link's `created_by`, otherwise the call is refused with 403.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
url | string (uri) | no | |
label | string | null | no | |
expires_at | integer | null | no |
Responses
| status | meaning | body |
|---|---|---|
200 | Updated. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Update a link's URL, label, or expiry, with a token from the door.
curl -X PUT 'https://zpe.vc/links/_/api/v1/links/42' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"url": "https://example.com",
"label": null,
"expires_at": null
}'{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
DELETE
/_/api/v1/links/{id}
Delete a link permanently
deleteLinksById
Owner only, and only while the link has recorded zero clicks. Once any click exists the link is permanent and this call returns 400; disable it instead. Deleting removes the link, its slugs, and frees those slugs for reuse by anyone.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Deleted. | object |
400 | Refused. The link has recorded at least one click, so it cannot be deleted: `Cannot delete a link with clicks, disable it instead`. A clicked short link is already in circulation, in email, in print and in QR codes, and deleting it frees the slug for reuse. Call `POST /links/{id}/disable` instead, which stops the redirect and keeps the history. The lifetime count is unfiltered, so a single bot click is enough. Also returned for a malformed request. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Delete a link permanently, with a token from the door.
curl -X DELETE 'https://zpe.vc/links/_/api/v1/links/42' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"deleted": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links/{id}/disable
Disable a link
postLinksByIdDisable
Owner only. Stops every slug on the link from redirecting while keeping the link and its click history. This is the correct operation for a link that is already in circulation, and the only one available once the link has recorded a click.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Disabled. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Disable a link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links/42/disable' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links/{id}/enable
Re-enable a disabled link
postLinksByIdEnable
Owner only. Clears `expires_at` outright, including an expiry set at create or update, so the link resolves again and no longer expires.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Enabled. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Re-enable a disabled link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links/42/enable' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links/{id}/slugs
Add a custom slug to a link
postLinksByIdSlugs
Open to any authenticated caller with the `create` scope, including on a link owned by someone else. Adding a slug creates an additional address for the existing destination; it cannot change, disable or delete anything. Removing or disabling that slug afterwards is owner-gated.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
slug | string | yes |
Responses
| status | meaning | body |
|---|---|---|
201 | Slug added. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
409 | Conflict. | object |
Example
Add a custom slug to a link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links/42/slugs' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"slug": "string"
}'{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links/{id}/slugs/{slug}/disable
Disable a specific slug on a link
postLinksByIdSlugsBySlugDisable
Owner of the parent link only. Custom slugs only: the system-generated slug cannot be disabled. The slug stops resolving and keeps its click history.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
slug | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Disabled. | object |
400 | Refused. The system-generated slug cannot be disabled, only custom slugs can. Disable the whole link instead. Also returned for a malformed request. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Disable a specific slug on a link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links/42/slugs/string/disable' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/links/{id}/slugs/{slug}/enable
Re-enable a disabled slug on a link
postLinksByIdSlugsBySlugEnable
Owner of the parent link only.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
slug | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Enabled. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Re-enable a disabled slug on a link, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/links/42/slugs/string/enable' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
DELETE
/_/api/v1/links/{id}/slugs/{slug}
Remove a custom slug from a link
deleteLinksByIdSlugsBySlug
Owner of the parent link only, custom slugs only, and only while the slug has recorded zero clicks. Once any click exists the slug is permanent and this call returns 400; disable it instead.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
slug | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Removed. | object |
400 | Refused. Either the slug has recorded at least one click (`Cannot remove a slug with clicks, disable it instead`), or it is the link's system-generated slug, which can never be removed. For a clicked custom slug, call `POST /links/{id}/slugs/{slug}/disable` instead: it stops that slug resolving and keeps its click history. For the system-generated slug, disable the whole link with `POST /links/{id}/disable`. Also returned for a malformed request. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the link owner. Ownership is fixed at creation and is not transferable: only the identity that created a link can change it. Reading is open to every authenticated key. | object |
404 | Not found. | object |
Example
Remove a custom slug from a link, with a token from the door.
curl -X DELETE 'https://zpe.vc/links/_/api/v1/links/42/slugs/string' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"removed": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}/qr
Get a QR code SVG for a link
getLinksByIdQr
Get a QR code SVG for a link
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
size | query | no | integer | QR pixel dimensions (square). Integer 1-2048; defaults to 220. |
slug | query | no | string | Optional specific slug. Defaults to the link's primary slug. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | SVG image | string |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
Get a QR code SVG for a link, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42/qr?size=0&slug=string' \ -H 'Authorization: Bearer $ZPE_TOKEN'
"string"
The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}/bundles
List bundles a link belongs to
getLinksByIdBundles
List bundles a link belongs to
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | array of object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
List bundles a link belongs to, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42/bundles' \ -H 'Authorization: Bearer $ZPE_TOKEN'
[
{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0
}
]The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}/analytics
Get analytics for a link (defaults to all-time)
getLinksByIdAnalytics
Get analytics for a link (defaults to all-time)
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
500 | Server error. | object |
Example
Get analytics for a link (defaults to all-time), with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42/analytics?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"total_clicks": 0,
"countries": [
{
"name": "string",
"count": 0
}
],
"referrers": [
{
"name": "string",
"count": 0
}
],
"referrer_hosts": [
{
"name": "string",
"count": 0
}
],
"devices": [
{
"name": "string",
"count": 0
}
],
"os": [
{
"name": "string",
"count": 0
}
],
"browsers": [
{
"name": "string",
"count": 0
}
],
"link_modes": [
{
"name": "string",
"count": 0
}
],
"channels": [
{
"name": "string",
"count": 0
}
],
"clicks_over_time": [
{
"date": "string",
"count": 0
}
],
"slug_clicks": [
{
"slug": "string",
"count": 0
}
],
"num_countries": 0,
"num_referrers": 0,
"num_referrer_hosts": 0,
"num_os": 0,
"num_browsers": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}/timeline
Get click timeline for a link (defaults to all-time)
getLinksByIdTimeline
Get click timeline for a link (defaults to all-time)
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
500 | Server error. | object |
Example
Get click timeline for a link (defaults to all-time), with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42/timeline?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"range": "24h",
"buckets": [
{
"label": "string",
"count": 0
}
],
"summary": {
"last_24h": 0,
"last_7d": 0,
"last_30d": 0,
"last_90d": 0,
"last_1y": 0
}
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/links/{id}/breakdown
Page through a link's countries, sources, or domains breakdown
getLinksByIdBreakdown
Page through a link's countries, sources, or domains breakdown
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
dimension | query | yes | "countries" | "referrers" | "referrer_hosts" | Which panel to page through: countries, referrers (sources), or referrer_hosts (domains). |
limit | query | no | integer | Page size, 1-100. Defaults to 10. |
offset | query | no | integer | null | Row offset into the ranked list. Defaults to 0. |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range. Defaults to all-time. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
500 | Server error. | object |
Example
Page through a link's countries, sources, or domains breakdown, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/links/42/breakdown?dimension=countries&limit=0&offset=<offset>&range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"items": [
{
"name": "string",
"count": 0
}
],
"total": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/slugs/{slug}
Look up a link by its slug
getSlugsBySlug
Open to every authenticated key, whoever owns the link. Reads are not owner-scoped.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
slug | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Slug not found. | object |
500 | Server error. | object |
Example
Look up a link by its slug, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/slugs/string' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/bundles
List bundles
getBundles
Every bundle on the deployment, not only the caller's. Reads are open across owners.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
archived | query | no | "1" | "true" | "only" | "all" | true|1|only -> archived only. all -> include archived. Omit -> active only. |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | array of object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
Example
List bundles, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/bundles?archived=1&range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
[
{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0,
"link_count": 0,
"total_clicks": 0,
"delta_pct": 0,
"sparkline": [
0
],
"top_links": [
{
"slug": "string",
"click_count": 0
}
]
}
]The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/bundles
Create a bundle
postBundles
The identity behind the API key becomes the bundle's owner and is the only one who can later change it.
Parameters
No parameters.
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
name | string | yes | |
description | string | null | no | |
icon | string | null | no | |
accent | "orange" | "red" | "green" | "blue" | "purple" | no |
Responses
| status | meaning | body |
|---|---|---|
201 | Created. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
Example
Create a bundle, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/bundles' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"name": "string",
"description": null,
"icon": null,
"accent": "orange"
}'{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/bundles/{id}
Get a bundle
getBundlesById
Open to every authenticated key, whoever owns the bundle.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
Get a bundle, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/bundles/42?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0,
"link_count": 0,
"total_clicks": 0,
"delta_pct": 0,
"sparkline": [
0
],
"top_links": [
{
"slug": "string",
"click_count": 0
}
]
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
PUT
/_/api/v1/bundles/{id}
Update a bundle
putBundlesById
Owner only. Refused with 403 for any other identity.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
name | string | no | |
description | string | null | no | |
icon | string | null | no | |
accent | "orange" | "red" | "green" | "blue" | "purple" | no |
Responses
| status | meaning | body |
|---|---|---|
200 | Updated. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key. | object |
404 | Not found. | object |
Example
Update a bundle, with a token from the door.
curl -X PUT 'https://zpe.vc/links/_/api/v1/bundles/42' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"name": "string",
"description": null,
"icon": null,
"accent": "orange"
}'{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
DELETE
/_/api/v1/bundles/{id}
Delete a bundle permanently
deleteBundlesById
Owner only. Unlike a link, a bundle carries no zero-click rule: a bundle is a grouping, not an address, so deleting one drops the memberships and leaves every member link and its click history in place. Archive instead when you only want it out of the default listing.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Deleted. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key. | object |
404 | Not found. | object |
Example
Delete a bundle permanently, with a token from the door.
curl -X DELETE 'https://zpe.vc/links/_/api/v1/bundles/42' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"deleted": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/bundles/{id}/archive
Archive a bundle
postBundlesByIdArchive
Owner only. Hides the bundle from the default listing without deleting anything. Reversible through unarchive.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Archived. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key. | object |
404 | Not found. | object |
Example
Archive a bundle, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/bundles/42/archive' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/bundles/{id}/unarchive
Unarchive a bundle
postBundlesByIdUnarchive
Owner only. Returns an archived bundle to the default listing.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Unarchived. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key. | object |
404 | Not found. | object |
Example
Unarchive a bundle, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/bundles/42/unarchive' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"id": 0,
"name": "string",
"description": null,
"icon": null,
"accent": "orange",
"archived_at": null,
"created_via": null,
"created_by": "string",
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/bundles/{id}/analytics
Get bundle analytics. Defaults to all-time if no range given.
getBundlesByIdAnalytics
Get bundle analytics. Defaults to all-time if no range given.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range for analytics. Defaults are documented per endpoint. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
Get bundle analytics. Defaults to all-time if no range given., with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/bundles/42/analytics?range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"total_clicks": 0,
"countries": [
{
"name": "string",
"count": 0
}
],
"referrers": [
{
"name": "string",
"count": 0
}
],
"referrer_hosts": [
{
"name": "string",
"count": 0
}
],
"devices": [
{
"name": "string",
"count": 0
}
],
"os": [
{
"name": "string",
"count": 0
}
],
"browsers": [
{
"name": "string",
"count": 0
}
],
"link_modes": [
{
"name": "string",
"count": 0
}
],
"channels": [
{
"name": "string",
"count": 0
}
],
"clicks_over_time": [
{
"date": "string",
"count": 0
}
],
"slug_clicks": [
{
"slug": "string",
"count": 0
}
],
"num_countries": 0,
"num_referrers": 0,
"num_referrer_hosts": 0,
"num_os": 0,
"num_browsers": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/bundles/{id}/breakdown
Page through a bundle's countries, sources, or domains breakdown
getBundlesByIdBreakdown
Page through a bundle's countries, sources, or domains breakdown
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
dimension | query | yes | "countries" | "referrers" | "referrer_hosts" | Which panel to page through: countries, referrers (sources), or referrer_hosts (domains). |
limit | query | no | integer | Page size, 1-100. Defaults to 10. |
offset | query | no | integer | null | Row offset into the ranked list. Defaults to 0. |
range | query | no | "24h" | "7d" | "30d" | "90d" | "1y" | "all" | Time range. Defaults to all-time. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
Page through a bundle's countries, sources, or domains breakdown, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/bundles/42/breakdown?dimension=countries&limit=0&offset=<offset>&range=24h' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"items": [
{
"name": "string",
"count": 0
}
],
"total": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/bundles/{id}/links
List links in a bundle
getBundlesByIdLinks
Open to every authenticated key, whoever owns the bundle or its links.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | OK. | array of object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
List links in a bundle, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/bundles/42/links' \ -H 'Authorization: Bearer $ZPE_TOKEN'
[
{
"id": 42,
"url": "https://example.com/long-url",
"label": "Marketing landing",
"created_at": 0,
"expires_at": null,
"created_via": null,
"created_by": "string",
"slugs": [
{
"link_id": 0,
"slug": "string",
"is_custom": 0,
"is_primary": 0,
"click_count": 0,
"created_at": 0,
"disabled_at": null
}
],
"total_clicks": 137,
"delta_pct": 0
}
]The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/bundles/{id}/links
Add a link to a bundle
postBundlesByIdLinks
Open to any authenticated caller with the `create` scope: adding requires owning neither the bundle nor the link. Idempotent. Removing the link again is owner-gated.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer |
Request body application/json · optional
| field | type | required | notes |
|---|---|---|---|
link_id | integer | yes |
Responses
| status | meaning | body |
|---|---|---|
200 | Added. | object |
400 | Validation error. | object |
401 | Missing or invalid bearer token. | object |
403 | Scope insufficient. | object |
404 | Not found. | object |
Example
Add a link to a bundle, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/bundles/42/links' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{
"link_id": 0
}'{
"added": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
DELETE
/_/api/v1/bundles/{id}/links/{linkId}
Remove a link from a bundle
deleteBundlesByIdLinksByLinkId
Bundle owner only, whoever owns the link. Owning the link does not grant removal from someone else's bundle. The link itself is not deleted.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | |
linkId | path | yes | integer |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | Removed. | object |
401 | Missing or invalid bearer token. | object |
403 | Refused. Either the key lacks the `create` scope, or the caller is not the bundle owner. Ownership is fixed at creation and is not transferable: only the identity that created a bundle can change it. Reading a bundle, its links and its analytics is open to every authenticated key. | object |
404 | Not found. | object |
Example
Remove a link from a bundle, with a token from the door.
curl -X DELETE 'https://zpe.vc/links/_/api/v1/bundles/42/links/17' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"removed": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/pages
List the pages this key can write
getPages
Every name the key's owner holds that carries a page, with its blocks. An account holds at most five pages, so this is one page of results and takes no paging parameters.
Parameters
No parameters.
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The owner's pages. | array of object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
List the pages this key can write, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/pages' \ -H 'Authorization: Bearer $ZPE_TOKEN'
[
{
"name": "string",
"headline": "string",
"bio": "string",
"email": null,
"theme": "ink",
"accent": "string",
"created_at": 0,
"updated_at": 0,
"blocks": [
{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}
]
}
]The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/_/api/v1/pages/{name}
Read one page
getPagesByName
The page's fields and every block, in the order a visitor would see them.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
name | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The page. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Read one page, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/pages/string' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"name": "string",
"headline": "string",
"bio": "string",
"email": null,
"theme": "ink",
"accent": "string",
"created_at": 0,
"updated_at": 0,
"blocks": [
{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}
]
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
PUT
/_/api/v1/pages/{name}
Write a page's fields
putPagesByName
The one line, the longer note, the address and the look. The first save creates the page row, which is what publishing requires.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
name | path | yes | string |
Request body application/json · optional
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | The page as written. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Write a page's fields, with a token from the door.
curl -X PUT 'https://zpe.vc/links/_/api/v1/pages/string' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{
"name": "string",
"headline": "string",
"bio": "string",
"email": null,
"theme": "ink",
"accent": "string",
"created_at": 0,
"updated_at": 0,
"blocks": [
{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}
]
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/pages/{name}/blocks
Add a block
postPagesByNameBlocks
A page holds 12 blocks. The first ask is exclusive: adding a block with is_first clears whichever one held it.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
name | path | yes | string |
Request body application/json · optional
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
201 | The block as written. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Add a block, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/pages/string/blocks' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
PATCH
/_/api/v1/pages/{name}/blocks/{id}
Change one block
patchPagesByNameBlocksById
Only the keys sent are written. The kind cannot change. Hiding the first ask clears it, because an ask nobody can see is not an ask.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | null | |
name | path | yes | string |
Request body application/json · optional
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | The block as written. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Change one block, with a token from the door.
curl -X PATCH 'https://zpe.vc/links/_/api/v1/pages/string/blocks/{id}' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
DELETE
/_/api/v1/pages/{name}/blocks/{id}
Remove one block
deletePagesByNameBlocksById
The taps a block earned are kept: the report says what happened, and a removed block does not un-happen it.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | integer | null | |
name | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
204 | Removed. | |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Remove one block, with a token from the door.
curl -X DELETE 'https://zpe.vc/links/_/api/v1/pages/string/blocks/{id}' \
-H 'Authorization: Bearer $ZPE_TOKEN'The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/pages/{name}/publish
Publish a page
postPagesByNamePublish
The name starts answering at its address with no destination beside it. Refused when the page was never written or has nothing visible.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
name | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The name now answers. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Publish a page, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/pages/string/publish' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"name": "string",
"state": "claimed",
"published": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/_/api/v1/pages/{name}/unpublish
Unpublish a page
postPagesByNameUnpublish
The address stops answering and the page is left whole, so publishing again brings the same page back.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
name | path | yes | string |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The name stopped answering. | object |
401 | Missing or invalid bearer token. | object |
403 | The key's scope does not allow this verb. | object |
404 | No such page for this key's owner. | object |
409 | The name is retired. | object |
422 | The request is well formed and the page cannot honour it. | object |
Example
Unpublish a page, with a token from the door.
curl -X POST 'https://zpe.vc/links/_/api/v1/pages/string/unpublish' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"name": "string",
"state": "claimed",
"published": false
}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.