API keys and the REST API
Create a key, call the API, and understand scopes, rate limits, and idempotency.
Reviewed 24 Oct 2026
What the API is for
Anything you do by hand in the dashboard, you can do from your own code: create links when a product is published, pull click numbers into an internal report, tidy up links when a campaign ends.
The current surface covers links, per-link analytics, and conversion events.
| Endpoint | Does |
|---|---|
| GET /api/v1/links | List your links. Takes cursor, limit, and search; returns items with nextCursor and hasMore. |
| POST /api/v1/links | Create a link. |
| GET /api/v1/links/{id} | Fetch one link. |
| PATCH /api/v1/links/{id} | Update a link. Can set or clear the same protection and organisation fields as POST (password, expiry, click cap, fallback URL, domain, folder, UTM). |
| DELETE /api/v1/links/{id} | Delete a link. |
| GET /api/v1/analytics/{linkId} | Analytics for one link. |
| POST /api/v1/conversions | Record a conversion event. Needs conversions:write. |
| GET /api/v1/openapi | The full machine-readable specification. |
The OpenAPI document is the authoritative reference: exact parameters, response shapes, and error codes. It is also what you feed to a client generator rather than writing request code by hand.
Keys
Create keys on the Developer page. Authenticate by sending the key as a bearer token.
curl https://shortzen.io/api/v1/links \
-H "Authorization: Bearer sz_live_<your-api-key>"The key is shown once
Each key also records the workspace it acts on: the one you were working in when you created it. Older keys may show "Workspace not recorded" instead. That does not stop the key working, and it will not fill in later: to bind a key to a workspace, revoke it and create a new one while working in that workspace.
You can hold at most 20 live keys at a time. A key also stops authenticating on its own once it turns 365 days old, whether or not you set an expiry date. About 7 days before that effective expiry we email the holder. There is no renewal: create a new key, switch the integration, then revoke the old one.
The API keys page lists the keys you created under "Your keys". Workspace owners and admins also get an "All in this workspace" tab beside it, listing every member's keys with a revoke control. You can revoke your own key at any time. Removing someone's workspace access does not revoke their keys, so treat a key you cannot personally reach as still live until its holder revokes it.
- Do not commit a key to source control, and do not put one in front-end code: anything in a browser is public.
- Use environment variables or a secret manager.
- One key per integration, so you can revoke one thing without breaking the others.
- Revoke immediately if a key may have been exposed. Revocation takes effect at once.
Scopes
A key can be limited to what it actually needs.
| Scope | Allows |
|---|---|
| links:read | Listing and fetching links. |
| links:write | Creating and updating links, and pausing or resuming them. |
| links:delete | Deleting links. Not included in links:write; grant it only to a key that must remove links. |
| analytics:read | Reading analytics. |
| workspace:read | Listing domains and reading plan usage. |
| conversions:write | Recording conversion events. |
| * | Everything. |
Give the least you can
Rate limits
The hourly request budget belongs to the workspace, not to each key. Every key in the workspace draws on the same allowance, so issuing more keys does not buy more requests: splitting one integration across several keys will not raise the ceiling.
- API calls / hour, by plan
- Free 300 · Starter 1,200 · Pro 5,000 · Business 20,000
Handle the limit rather than trying to stay under it by luck: on a rate-limit response, back off and retry after a delay that grows each time. A job that retries immediately in a loop will simply stay limited.
Idempotency
Send your own value in an Idempotency-Key header on a create request and retrying it will not create a second link. Use it whenever a network timeout would otherwise leave you unsure whether the first attempt succeeded.
curl -X POST https://shortzen.io/api/v1/links \
-H "Authorization: Bearer sz_live_<your-api-key>" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f9b1c84-3a5e-4d21-9c7f-0b2e8a41d5cc" \
-d '{"destinationUrl": "https://example.com/spring-sale"}'Repeat that request with the same key and you get the first response back, replayed rather than run again. Repeat it while the first attempt is still in flight and you get a 409 telling you to retry shortly. Send the same key with a different body and it is refused with a 422: the key is bound to the body it was first used with, so a changed payload is a different operation reusing a spent key, not a retry.
Generate a fresh value per logical operation: a UUID, or something derived from the record you are creating a link for.
Use with AI assistants
An AI assistant that supports the Model Context Protocol (MCP) can read and manage your links, with your role, your plan and your quota applied as they are here. Most assistants log in rather than use a key; Cursor and your own scripts use one. Connect an AI assistant has the steps for each assistant, how to check it works, and what to do when it does not.
Legacy keys that carry no scopes
A key minted before scopes were required carries an empty scope list. That empty list used to be read as full access. It is now read as what it literally says (no scope was granted), so the key still authenticates and is then refused at the scope check on every endpoint that takes a key. The symptom is an integration that ran for months suddenly getting 403s on calls it has made from the start, with an INSUFFICIENT_SCOPE code and nothing changed at your end.
You can spot one on the Developer page: every other key names its scopes or reads "Full access", and one of these has a blank scope badge. If you were sent the in-app notification headed "One of your API keys has stopped working", it is pointing at exactly this.
A legacy key cannot be repaired, only replaced
Step by step
Create your first working integration
For: Developers, on every plan including Free
Before you start
- A plan that includes API access.
- Somewhere safe to store a secret.
- A way to make HTTP requests.
Steps
Open Developer and create an API key.
Give it only the scopes this integration needs.
links:write alone if it only creates links; add links:delete only if it must remove them. Resist reaching for the wildcard.
Copy the key straight into your secret store.
Not into a scratch file, not into chat. This is the only time you will see it.
The key is stored where your application can read it and a person cannot.
Make a read request first to prove authentication works.
Start with a GET. If it fails you know the problem is the key, not your payload.
You get back a list of your links.
Fetch the OpenAPI document and read the shape of the create endpoint.
You know exactly which fields to send.
Create one test link, with an idempotency key.
A 2xx response containing the new link.
Confirm it appears in your dashboard Links table.
This proves the whole path, not just that the request was accepted.
Add retry-with-backoff for rate-limit and server-error responses before you go live.
What you should see
A working, least-privilege integration that creates links safely and survives being retried.
How to check it worked
- Requests authenticate and return the data you expect.
- Links created through the API appear in the dashboard.
- Repeating a create with the same idempotency key does not produce a duplicate.
Edge cases
- You get an authentication error.
- Check the header is exactly "Authorization: Bearer <key>", and that the key has not been revoked, expired, or turned 365 days old: all four read back the same generic error.
- You get a permission error on a call that should work.
- The key is missing that scope. Scopes are fixed at creation, so make a new key with the right ones.
- You are being rate-limited.
- You are over your workspace's hourly allowance, which every key shares. Back off, batch your work, or upgrade.
- Your plan does not include API access.
- The API keys page shows an upgrade notice in place of the list. That happens only where a workspace has had API access withdrawn. Every plan, Free included, carries it; what differs is the hourly rate.
If something goes wrong
- A key has leaked.
- If you hold the key, revoke it now from the Developer page and create a replacement; revocation is immediate. Then work out what it did: the key's last-used stamp (or "Never used") tells you whether it authenticated at all and when it last did, and the Links page (newest first) shows anything created in that window. On Business, the Audit Logs page answers it directly instead of leaving you to infer it. Removing a holder's workspace access does not revoke the key itself.
- A key shows "Workspace not recorded" on the Developer page.
- Nothing will fill that in for you. If you need the key tied to a workspace, revoke it and create a replacement while working in that workspace, then deploy the new key.
- A retry created duplicate links.
- Delete the duplicates and add an idempotency key to that call. This is precisely the problem it exists to prevent.