Matches in other apps
Links API
mount /links · base https://zpe.vc/links · v3.0.0 · 56 operations · 31 write · 0 need no account · stable
the document this app serves now. Contract: https://zpe.vc/links/_/api/v1/openapi.json · sha256 5853a3d6f253
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,
"r2_key": null,
"alt": null,
"body": 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, and its numbers
getPagesByName
The page's fields and every block, in the order a visitor would see them, plus the report the editor draws: views, taps per block, the answers each question holds, the acts the page performs, and the countries and hosts the visits came from. Both doors read the report from one grouped read, so the JSON and the tab cannot disagree. Bots are stored and excluded from every count.
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, with its report. | 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, and its numbers, 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,
"r2_key": null,
"alt": null,
"body": null,
"is_first": false,
"visible": false,
"created_at": 0,
"updated_at": 0
}
],
"report": {
"views": 0,
"taps": 0,
"intents": 3,
"acts": {
"card": 0,
"email": 0,
"share": 0,
"submit": 0
},
"blocks": [
{
"id": 7,
"title": "Book a call",
"taps": 4,
"answers": 3,
"on_page": false
}
],
"rules": [
{
"id": 3,
"kind": "host",
"value": "instagram.com",
"taps": 5,
"on_page": false
}
],
"countries": [
{
"value": "ID",
"count": 12
}
],
"hosts": [
{
"value": "ID",
"count": 12
}
],
"bot_views": 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
| field | type | required | notes |
|---|---|---|---|
headline | string | no | |
bio | string | no | |
email | string | null | no | |
theme | "ink" | "paper" | "midnight" | "forest" | "cocoa" | "sand" | "mono" | "rosewater" | "neon" | no | |
accent | "ember" | "lime" | "mint" | "sky" | "violet" | "rose" | "gold" | "slate" | no |
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 '{
"headline": "string",
"bio": "string",
"email": null,
"theme": "ink",
"accent": "ember"
}'{
"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,
"r2_key": null,
"alt": null,
"body": 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}/submissions
Read a page's answers
getPagesByNameSubmissions
Every answer the page's questions collected in the last 90 days, newest first, each with the block it answers and that block's title as it reads now. The window and the bot rule are the report's own, so a block's `answers` figure and this list count the same rows. An answer whose block was since removed is still listed, with a null title: the answer outlives its question by design.
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 answers in the window, newest first. | 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 a page's answers, with a token from the door.
curl -X GET 'https://zpe.vc/links/_/api/v1/pages/string/submissions' \ -H 'Authorization: Bearer $ZPE_TOKEN'
{
"answers": [
{
"id": 12,
"block_id": 7,
"block_title": "Ask me anything",
"value": "hello@example.test",
"at": 0,
"country": "TH"
}
]
}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
| field | type | required | notes |
|---|---|---|---|
kind | "link" | "app" | "form" | "image" | "text" | no | |
title | string | no | |
subtitle | string | no | |
href | string | no | |
app_id | string | no | |
field | "email" | "text" | "tel" | no | |
label | string | no | |
consent | string | no | |
r2_key | string | no | |
alt | string | no | |
body | string | no | |
is_first | boolean | no | |
visible | boolean | no |
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 '{
"kind": "link",
"title": "string",
"subtitle": "string",
"href": "string",
"app_id": "string",
"field": "email",
"label": "string",
"consent": "string",
"r2_key": "string",
"alt": "string",
"body": "string",
"is_first": false,
"visible": false
}'{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"r2_key": null,
"alt": null,
"body": 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
| field | type | required | notes |
|---|---|---|---|
kind | "link" | "app" | "form" | "image" | "text" | no | |
title | string | no | |
subtitle | string | no | |
href | string | no | |
app_id | string | no | |
field | "email" | "text" | "tel" | no | |
label | string | no | |
consent | string | no | |
r2_key | string | no | |
alt | string | no | |
body | string | no | |
is_first | boolean | no | |
visible | boolean | no |
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 '{
"kind": "link",
"title": "string",
"subtitle": "string",
"href": "string",
"app_id": "string",
"field": "email",
"label": "string",
"consent": "string",
"r2_key": "string",
"alt": "string",
"body": "string",
"is_first": false,
"visible": false
}'{
"id": 0,
"name": "string",
"position": 0,
"kind": "link",
"title": "string",
"subtitle": null,
"href": null,
"app_id": null,
"r2_key": null,
"alt": null,
"body": 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.
GET
/new
The intake page
getNew
**Public.** The form a stranger opens at `zpe.vc/links/new`: the markup, the caps and the script that posts the drop. No key and no session — this is the one address a person who has never heard of the door is sent to. Any other method on this path is the intake's own 404 page, not a second operation.
Parameters
No parameters.
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The intake page. | string |
Example
The intake page, with a token from the door.
curl -X GET 'https://zpe.vc/links/new' \ -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
/new/api/form
The form was opened (the dwell anchor's row)
getNewApiForm
**Public.** The page calls this once when the form first paints. The call writes the dwell anchor's row (a salted hash of the caller, and the first time it was seen) and answers the caps the page shows: the dwell floor, the per-hour send cap and the text cap. It reads nothing back and refuses nothing.
Parameters
No parameters.
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The caps, and when the anchor was first seen. | object |
Example
The form was opened (the dwell anchor's row), with a token from the door.
curl -X GET 'https://zpe.vc/links/new/api/form' \ -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
/new/api/submit
One drop: the answers and the files in one body
postNewApiSubmit
**Public.** The only write a stranger makes. The browser posts the form (multipart) and a script posts JSON; both reach the same fields and the same file walk. A browser is answered `303` to its receipt page; a script is answered `201` with `{receipt, status}`. A replayed drop under the dedupe window answers `201` with the SAME receipt and writes no second row. Every refusal (`413` cap, `429` per-address, `422` fields) is the app's `{ok:false, code, errors}` body.
Parameters
No parameters.
Request body multipart/form-data · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
201 | The drop was stored. The body carries the receipt and the build's status. | object |
303 | A browser's drop: the receipt page, which is where the person waits. | |
413 | The body, one file, or the store's whole submission cap was exceeded. | object |
422 | A field was missing or over its limit; the body names which. | object |
429 | The caller's address is over the per-hour send cap. | object |
Example
One drop: the answers and the files in one body, with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/submit' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/new/api/{receipt}/files
The receipt holder adds files after the drop
postNewApiByReceiptFiles
**Public, with the receipt as the credential.** The receipt page's file picker posts here. The receipt names the build and nothing else authorises the write: a person holding the eight characters is the person the drop belongs to. A file already stored under the same digest is a duplicate and is not written twice.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body multipart/form-data · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | What was stored, what was a duplicate, and what was skipped and why. | object |
404 | No build carries that receipt. | object |
413 | The file, the part count, or the submission's byte cap was exceeded. | object |
422 | No file in the batch could be read. | object |
Example
The receipt holder adds files after the drop, with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/string/files' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/new/api/{receipt}/content
The assembled object (key)
getNewApiByReceiptContent
**Key.** The runner reads what the assembler wrote: the extracted content, the build's status and when it moved. Refused with `401` without an `Authorization: Bearer` account key granted `zpe-links`, and with `403` when the grant is read-only.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The assembled object and the build's status. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | No build carries that receipt. | object |
Example
The assembled object (key), with a token from the door.
curl -X GET 'https://zpe.vc/links/new/api/string/content' \ -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
/new/api/{receipt}/content
The assembler writes the object, once (key)
postNewApiByReceiptContent
**Key.** The one write that puts the assembled object on the build. It is written once: a second write to a build that already holds content is refused rather than merged.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body application/json · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | Written. The body carries the build's new status. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | No build carries that receipt. | object |
409 | The build is past the state where content can be written. | object |
413 | The object was over the record's byte cap. | object |
Example
The assembler writes the object, once (key), with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/string/content' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/new/api/{receipt}/message
The held message, its sign-off and its attempts (key)
getNewApiByReceiptMessage
**Key.** Reads the draft that is held for this build: the subject and body, the recipient, the latest sign-off and every send attempt so far. A caller that only wants to see the words can stop here — nothing is sent by a read.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The held message, its sign-off and its attempts. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | This build holds no message yet. | object |
Example
The held message, its sign-off and its attempts (key), with a token from the door.
curl -X GET 'https://zpe.vc/links/new/api/string/message' \ -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
/new/api/{receipt}/message
Write the held message for a row already read (key)
postNewApiByReceiptMessage
**Key.** Writes the draft the sender will read. A second post to a build that already holds a message is refused: the draft is written once, and a correction is a new build rather than a quiet rewrite of words somebody may have signed.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body application/json · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | The message the build now holds. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | No build carries that receipt. | object |
409 | The build already holds a message, or its recipient is not settled. | object |
422 | The message was missing a field the composer needs. | object |
Example
Write the held message for a row already read (key), with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/string/message' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/new/api/{receipt}/message/sign
Record a sign-off naming the body and the recipient (key)
postNewApiByReceiptMessageSign
**Key.** Records that a person read this exact draft, addressed to this exact recipient: the sign-off names the draft's hash, so a body that moved after the signature no longer matches it. `release` refuses without one.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body application/json · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
200 | The sign-off that is now the newest for this message. | object |
400 | The sign-off named nobody as the reader. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | This build holds no message to sign. | object |
409 | The message has no recipient, so there is nothing to sign for. | object |
Example
Record a sign-off naming the body and the recipient (key), with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/string/message/sign' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'{}The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
POST
/new/api/{receipt}/release
The app's only send (key, and refused without a sign-off)
postNewApiByReceiptRelease
**Key.** The one operation in this app that sends mail. It refuses unless the held message carries a sign-off whose draft hash still matches, so a caller cannot post words nobody read. The refusal codes are the contract `tools/mail-release.mjs` turns into exit codes — `409` is 'nobody signed this', not 'the mail service refused it'.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The send was attempted; the body carries its outcome. | object |
401 | No `Authorization` header, or one the door refuses. The path needs the app's own account key. | object |
403 | The key is valid and its grant does not allow this verb. | object |
404 | This build holds no message to send. | object |
409 | The message is unsigned, or its sign-off names a body that has since moved. | object |
503 | The mail service or the store could not be reached; nothing was sent. | object |
Example
The app's only send (key, and refused without a sign-off), with a token from the door.
curl -X POST 'https://zpe.vc/links/new/api/string/release' \ -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.
GET
/new/r/{receipt}
The page the submitter waits on
getNewRByReceipt
**Public, with the receipt as the credential.** The receipt page: what was received, the files, the held message's state, the address box when the record holds no address, and the confirm link. A receipt nobody holds answers the same page at `404`, so the answer never says whether a receipt exists.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The receipt page. | string |
404 | The same page, saying it is not on this server. | string |
Example
The page the submitter waits on, with a token from the door.
curl -X GET 'https://zpe.vc/links/new/r/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.
POST
/new/r/{receipt}/claim
Keep the published page, once, for the door's principal
postNewRByReceiptClaim
**Door.** Unreachable without the door's session: the receipt opens the record, and only a signed-in principal can become its owner — answered `401` with no session, and `503` when the door itself cannot be reached (which is not a refusal, because the person may well be signed in). A browser is sent back to the receipt page with `303`; a script is told `{ok:true, receipt, owner_principal}`. Only a published row nobody has kept can be claimed, once.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The row now belongs to the caller's principal. | object |
303 | A browser's claim: back to the receipt page. | |
401 | No door session. | object |
404 | No build carries that receipt. | object |
409 | The row is not published, or somebody already owns it. | object |
503 | The door could not be reached, so the claim was not decided. | object |
Example
Keep the published page, once, for the door's principal, with a token from the door.
curl -X POST 'https://zpe.vc/links/new/r/string/claim' \ -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
/new/r/{receipt}/email
The address the page asks for when the record holds none
postNewRByReceiptEmail
**Public, with the receipt as the credential.** The person says where their link should go. This recomposes the held draft and writes nothing anybody can send: the message still waits for a read and a sign-off. A browser is sent back to the receipt page with `303`.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body application/x-www-form-urlencoded · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
303 | The address was recorded; back to the receipt page. | |
404 | No build carries that receipt. | string |
409 | The message is already signed, or the row is past the ask. | string |
422 | That is not an address the app can write to. | string |
Example
The address the page asks for when the record holds none, with a token from the door.
curl -X POST 'https://zpe.vc/links/new/r/string/email' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/new/r/{receipt}/confirm
The correction page
getNewRByReceiptConfirm
**Public, with the receipt as the credential.** The page that lets the person fix a field before the press. It is a read: nothing is confirmed until the POST below.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The correction page. | string |
404 | No build carries that receipt. | string |
409 | The build is closed; the page says so. | string |
Example
The correction page, with a token from the door.
curl -X GET 'https://zpe.vc/links/new/r/string/confirm' \ -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.
POST
/new/r/{receipt}/confirm
The person's press, which is what confirms
postNewRByReceiptConfirm
**Public, with the receipt as the credential.** The only act that moves a build to `confirmed`: the page's press, with the corrected fields. It answers `303` back to the receipt page, which is where the published address appears.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body application/x-www-form-urlencoded · required
The document declares no properties for this body.
Responses
| status | meaning | body |
|---|---|---|
303 | Confirmed; back to the receipt page. | |
404 | No build carries that receipt. | string |
409 | The build is closed, or somebody pressed first. | string |
422 | A corrected field was refused; the page says which. | string |
Example
The person's press, which is what confirms, with a token from the door.
curl -X POST 'https://zpe.vc/links/new/r/string/confirm' \
-H 'Authorization: Bearer $ZPE_TOKEN' \
-H 'content-type: application/json' \
-d '{}'The example is generated from the schema in the contract: the shape is the document's, the values are placeholders.
GET
/new/r/{receipt}/f/{id}
One stored file, streamed through the worker
getNewRByReceiptFById
**Public, with the receipt as the credential.** The bytes of one file on this build, streamed from R2 through the worker so the bucket is never public. The receipt and the file id must belong together: a file id from somebody else's build is not a row and answers `404`.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
id | path | yes | string (uuid) | The file row's UUID, as the receipt page links it. |
receipt | path | yes | string | The eight-character receipt the submit answered with. It is the only key a stranger holds. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The file's own bytes, at the type the row stored. | string (binary) |
404 | That file is not on this server. | object |
Example
One stored file, streamed through the worker, with a token from the door.
curl -X GET 'https://zpe.vc/links/new/r/string/f/00000000-0000-4000-8000-000000000000' \ -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
/u/{slug}
The published page
getPublishedPage
**Public.** The product the intake exists for: the page a drop becomes after the press and the publish, at `zpe.vc/u/<name>`. No key and no session — this is the address the business prints. It answers at that mount only: measured 2026-11-06, `zpe.vc/u/<name>` is 200 and `zpe.vc/links/u/<name>` is 404, which is why this path item carries its own `servers`. A name that holds no live page answers the signpost's own `404`, never somebody else's page.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
slug | path | yes | string | The page's published name, which is also its address: `zpe.vc/u/<name>`. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The published page. | string |
404 | No live page holds that name. | string |
Example
The published page, with a token from the door.
curl -X GET 'https://zpe.vc/u/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
/u/{slug}/card.vcf
The published page's card, as a vCard
getPublishedPageCard
**Public.** The page's 'save my card' act at `zpe.vc/u/<name>/card.vcf`: the page's own name, headline, address and contact as `text/vcard`, built from the same row the page reads. The read records its row before it answers, so the operator can see the card was taken.
Parameters
| name | in | required | type | notes |
|---|---|---|---|---|
slug | path | yes | string | The page's published name, which is also its address: `zpe.vc/u/<name>`. |
Request body
None: this operation takes its arguments in the path and the query.
Responses
| status | meaning | body |
|---|---|---|
200 | The card. | string |
404 | No live page holds that name. | string |
Example
The published page's card, as a vCard, with a token from the door.
curl -X GET 'https://zpe.vc/u/string/card.vcf' \ -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.