- Home
- Developers
Build with toa.ee
A REST API, an MCP server for Claude and other AI assistants, and ready-made apps for Zapier, Make and your browser. On every plan, including Free.
Quick start
- Sign up or log in, and open Settings → Integrations.
- Create an API key.
- Make your first short link:
curl https://toa.ee/api/v1/links \
-H "Authorization: Bearer $TOA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"destination":"https://example.com/spring-sale","slug":"spring"}'Response:
{
"id": "019a3f2c-8b1e-7d40-9c2a-1f6e4b8d0a11",
"slug": "spring",
"short_url": "https://toa.ee/spring",
"destination": "https://example.com/spring-sale",
"final_url": "https://example.com/spring-sale",
"title": null,
"utm": null,
"redirect_type": 301,
"status": "active",
"hold_ends_at": null,
"clicks": 0,
"dashboard_url": "https://toa.ee/app/links/019a3f2c-8b1e-7d40-9c2a-1f6e4b8d0a11",
"created_at": "2026-10-29T12:00:00.000Z",
"updated_at": "2026-10-29T12:00:00.000Z"
}Authentication
API keys use Authorization: Bearer toa_sk_…. Each key is read only, or read and write. Free allows 1 key; Ultra allows up to 50.
Apps sign in with OAuth 2.1 (authorization code + PKCE). Scopes are read and write. Discovery lives at /.well-known/oauth-authorization-server. Clients register with a Client ID Metadata Document or Dynamic Client Registration.
Use toa.ee in Claude and other AI assistants
Server URL
Claude (web, desktop, mobile): Settings → Connectors → Add custom connector, paste the URL, then sign in with your toa.ee account.
Claude Code: claude mcp add --transport http toa https://toa.ee/mcp, then /mcp → Authenticate.
Other MCP clients (Cursor, VS Code): Add a remote (Streamable HTTP) server with the URL above. Clients without OAuth can send an API key: Authorization: Bearer toa_sk_….
Returns the signed-in toa.ee account's email, plan, and how many short links, digital business cards, API keys and API requests this month it has used against the plan's limits.
Lists the account's short links, newest first or by clicks. Search matches title, slug or destination. Returns up to 50 per page with a cursor for the next page.
Returns one short link by id or by its exact slug (slugs are case-sensitive), with its destination, UTM tags, status and total clicks.
Creates a short link at toa.ee/{slug} that redirects to the destination URL. Without a slug, toa.ee picks a random one. Slugs use letters, numbers, hyphens and underscores; accented letters are converted (ä → a). UTM tags are added to the destination.
Changes a short link's destination, slug, title, UTM tags or redirect type. Printed QR codes and shared links follow the new destination right away. Changing the slug breaks the old toa.ee address.
Deletes a short link and its click stats. The toa.ee address stops working. Pass the link's current slug in confirm_slug. Links younger than 24 hours can't be deleted.
Returns click stats for one short link over a time range: totals, unique visitors, bot clicks, a time series, and top referrers, countries, devices and browsers. Stats are cookie-free and exclude bots from totals.
Compares two to five short links or digital business cards over the same time range: totals and a time series for each.
Lists the account's digital business cards with their toa.ee address and status. Contact details are not included.
Returns views, unique visitors, contact saves, taps per field, countries and devices for one digital business card over a time range.
Returns a PNG QR code for a short link or digital business card, in the account's saved colours and logo. PNG, SVG and PDF downloads are on the item's page in the toa.ee dashboard.
Lists the account's saved UTM templates, to use with create_short_link's utm_template_id.
Example prompts
- Shorten https://example.com/spring-sale as toa.ee/spring with utm_source=newsletter.
- Which of my short links got the most clicks last week, and from which countries?
- Compare toa.ee/spring and toa.ee/summer for the last 30 days.
- Make a QR code for toa.ee/menu.
Limits by plan
| Plan | Requests a month | Requests a minute | API keys | New links an hour |
|---|---|---|---|---|
| Free | 1,000 | 30 | 1 | 60 |
| Pro | 10,000 | 60 | 3 | 60 |
| Max | 100,000 | 300 | 10 | 300 |
| Ultra | 1,000,000 | 600 | 50 | 1,000 |
Every counted API and MCP call shares one monthly quota and one per-minute limit for the account, across every key and connected app.
Not counted toward the monthly quota (still counted per minute): GET /api/v1/me, the MCP get_account tool, Zapier and Make list polls, MCP discovery messages, GET /api/v1/openapi.json, and every OAuth endpoint.
The monthly quota resets on the 1st of each month, UTC.
Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Quota-Limit, X-Quota-Remaining and X-Quota-Reset. A 429 also sends Retry-After.
Errors
Every error uses the same shape: { "error": { "code", "message", "field?", "docs" } }. field is set only when one input is at fault.
| HTTP | Code | When | Message |
|---|---|---|---|
| 400 | bad_request | Body isn't valid JSON | The request body isn't valid JSON. |
| 401 | unauthorized | Missing, unknown, expired or revoked credential; account deleted or blocked | Use a valid API key or sign in again. |
| 403 | key_over_limit | API key beyond the plan's key limit after a downgrade | This API key is over your plan's limit of keys. Revoke a key in Settings → Integrations or upgrade. |
| 403 | insufficient_scope | Write with read-only access | This key or app has read-only access. |
| 403 | limit_reached | Live-link limit reached | You've hit this plan's link limit. Upgrade or delete a link. |
| 404 | not_found | Unknown id, not owned, or wrong kind | We couldn't find that short link. / We couldn't find that digital business card. |
| 409 | slug_taken | Slug already in use | That slug isn't available. Try a different one. |
| 409 | delete_too_soon | Link younger than 24 hours | You can delete a link only after 24 hours. |
| 409 | in_plan_grace | Edit of a held link after a downgrade | This link is in a plan grace period and can't be edited yet. |
| 409 | idempotency_conflict | Same Idempotency-Key, different request | That Idempotency-Key was already used for a different request. |
| 409 | idempotency_in_progress | Same key while the first request is still running | A request with this Idempotency-Key is still running. Try again in a moment. |
| 409 | qr_unavailable | QR for a disabled, flagged or expired link, or a paused digital business card | A QR code isn't available for this item right now. |
| 422 | validation_error | A field failed validation | The first field error, with field set. |
| 422 | unsafe_destination | Private IP, SSRF or shortener nesting | That destination isn't allowed. |
| 429 | rate_limited | Per-minute limit | Too many requests. Try again in {seconds} seconds. |
| 429 | quota_exceeded | Monthly request quota used | You've used this month's API and MCP requests. See /pricing. |
| 500 | internal | Unexpected server error | Something went wrong on our side. Try again. |
Pagination and idempotency
List endpoints return { data, next_cursor }. Pass limit (1–100, default 25) and the opaque cursor from the previous page. Timestamps are ISO 8601 UTC with Z; field names are snake_case.
POST routes marked idempotent accept an Idempotency-Key header (1–255 characters). A repeat with the same key and body replays the first response; a different body with the same key returns 409 idempotency_conflict. Keys are per account and kept for 24 hours.
Versioning and changelog
Changes to /api/v1 are additive only. A breaking change means /api/v2, with at least six months' notice on this page.
- On launch — API v1, MCP server (Claude and ChatGPT), Zapier, Make and browser extension.