Base URL, sandbox, versioning, data format, pagination and authentication: what to know before your first LinkHarbor REST API call.
The REST API lets you control LinkHarbor from your own programs: create pages and links, edit and publish drafts, generate short links or pull statistics into your own reporting. This article is for developers and explains the ground rules that apply to every endpoint.
Which route fits?
| You want to … | Route | More |
|---|---|---|
| Manage pages, links and content from a script or server | REST API with an API key | This section |
| Get notified when something happens (e.g. a page is published) | Webhooks | Webhooks |
| Match sales from your shop to a link click | Sale callbacks | Callbacks |
| Show live data from your own tools on your page | “Custom (API)” integration | Callbacks |
| Work in a chat with Claude or ChatGPT | MCP server | AI assistants |
Base URL and versioning
- Base URL
- https://dock.at-harbor.com – all product endpoints live under /v1, for example https://dock.at-harbor.com/v1/pages.
- Version
- There is currently only version v1. The version number is part of the path.
- Protocol
- HTTPS only.
- OpenAPI
- The machine-readable description (OpenAPI 3.1) is at https://dock.at-harbor.com/openapi.yaml. You can load it into Postman, Insomnia or a code generator.
Testing in the sandbox
For development and testing there is a separate sandbox. It behaves like the real API but is completely separate from it: its own accounts, its own data, and none of it ends up in your real workspace.
- API
- https://drydock.at-harbor.com, with the same paths as above, for example https://drydock.at-harbor.com/v1/pages.
- Dashboard
- https://sandbox-deck.linkharbor.com, for example to create API keys.
- Pages
- Published pages appear at https://<handle>.sandbox.linkharbor.com.
- Account
- Your real LinkHarbor account does not work there. Create a separate sandbox account.
The sandbox does not send e-mails. If you register via the API (POST /v1/auth/register), the verification code comes back directly in the response, in the _dev_verification field. The same applies to password resets and e-mail changes. This field only exists in the sandbox, so do not rely on it in your code.
- Push notifications are switched off
- Payments run through Stripe's test mode, no real money moves
- Rate limits are about a third of the real values, and new accounts are limited to 5 per hour and IP address
- On the 1st of every month at 3:00 UTC the sandbox is wiped completely, and all accounts and data are gone afterwards
- Every response carries the header X-Environment: sandbox
Authentication in brief
- As an owner or admin, create an API key with the scopes you need in the dashboard under “Callback & API” (from Creator Pro)
- Exchange the key ID and key secret for an access token via POST /v1/token
- Send the token with every request in the header Authorization: Bearer …
- Get a new token after 15 minutes
All the details are in API keys and tokens, the permissions in Scopes.
Requests and responses
- Format
- Requests and responses are JSON in UTF-8. File uploads also use multipart/form-data.
- Content-Type
- Send the header Content-Type: application/json with every request that has a body. Any other type is rejected with 400 bad_request – including the one curl sets automatically for -d when this header is missing.
- Unknown fields
- Fields an endpoint does not know lead to 422 validation_failed. The response names the unknown fields.
- Broken JSON
- Leads to 400 invalid_json.
- Size
- Depending on the endpoint, a body may be at most 64 KB, 1 MB or 2 MB, and a file upload 10 MB. If it is larger, the API responds with 413 request_too_large. All limits are listed under Rate limits.
- Lists
- Come as an object with the field items, for example {"items":[…]}.
- Creating
- Responds with HTTP 201 and the new ID, for example {"id":123}.
- Actions without a return value
- Respond with {"ok":true}.
- Errors
- Always have a fixed format with a machine-readable code. See Error format and error codes.
Pagination
There is no single pagination scheme. Most lists are small and come back complete; only a few endpoints have parameters:
| Endpoint | Behaviour | Parameters |
|---|---|---|
| GET /v1/pages, /v1/pages/{id}/links, /v1/pages/{id}/blocks, /v1/forms | Complete list | – |
| GET /v1/forms/{id}/submissions | Newest first, paged with a cursor: next_cursor from the response is the cursor for the next page, null on the last one | limit 1–500 (default 100), cursor |
| GET /v1/short-links | Paged, with total in the response | limit 1–100 (default 50), offset, sort, q |
| GET /v1/push/history | Paged | limit up to 100 (default 50), offset |
| Statistics endpoints | By period | period, from, to, granularity |
For sort on short links there are created_desc, created_asc, redirects_desc and redirects_asc; q searches slug, title and target URL (up to 100 characters, not case-sensitive). For statistics, period accepts 24h, 7d, 30d, 90d, this_month, last_month, last_30d and custom; alternatively you pass from and to. granularity is hour or day.
Request ID and language
- Every response carries the header X-Request-Id – include it when you contact support
- If you send an X-Request-Id header yourself, the API uses it
- The error text in message is in the account language of the workspace owner, otherwise it follows Accept-Language; the code in error always stays the same
Calling it from the browser?
Technically, the API allows calls from the browser (CORS). But a key secret never belongs in code that runs on the visitor's side – neither in a website nor in an app. Always call the API from your own server.
Plans
| Plan | API keys | Webhooks | Callback secrets |
|---|---|---|---|
| Creator Free | – | – | – |
| Creator Pro | up to 5 | up to 5 | up to 5 |
| Creator Agency | up to 20 | up to 25 | up to 5 |
| Enterprise | up to 50 (adjustable) | up to 100 (adjustable) | up to 5 |
There is no quota of API calls per plan. Only the per-minute rate limits apply. Your plan's content limits – such as pages, links or blocks per page – apply via the API just as they do in the dashboard. See Plans compared.
Your first call
# Exchange the key for a token
curl -X POST https://dock.at-harbor.com/v1/token \
-H "Content-Type: application/json" \
-d '{"key_id":"6F668EA6598B0692","key_secret":"lt_…"}'
# → {"token":"eyJ…","expires_in":900}
export LH_TOKEN="eyJ…"
# List pages (scope read:pages)
curl https://dock.at-harbor.com/v1/pages \
-H "Authorization: Bearer $LH_TOKEN" A complete example that adds a link, changes the design and publishes is in Update a page via the API.
You might also find this useful
Nothing found?
Tell us what you need, and our support team will help you personally.