URL Shortener API

The TinyTo REST API: create short links, read their stats and manage them from your own code.

v1 REST · JSON Base URL: https://tinyto.me/api Advanced plan Open as Markdown ↗

Overview

Every request is JSON over HTTPS. Responses always use the same envelope: on success data holds the payload and error is null; on failure data is null and the HTTP status plus error.code tell you what happened.

The API is scoped to the account that owns the key. You cannot read or modify links from another account, and there is no endpoint that returns data across accounts.

Availability: the API is included in the Advanced plan. If the account drops to a lower plan, existing keys stop working immediately with 403 forbidden.

Quickstart

  1. Create a key. Open Dashboard → API, give it a name and copy it. It is shown once; we store only a hash.
  2. Check it works. GET /v1/me is the cheapest call and returns your plan and link usage.
  3. Create your first link and keep the id: every other endpoint keys off it.
curl https://tinyto.me/api/v1/me \
  -H "Authorization: Bearer tto_live_..."
const res = await fetch("https://tinyto.me/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.TINYTO_KEY}` },
});
console.log(await res.json());
import os, requests

res = requests.get(
    "https://tinyto.me/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['TINYTO_KEY']}"},
    timeout=10,
)
print(res.json())

A healthy response looks like this:

{
  "data": {
    "plan": "advanced",
    "link_usage": { "used": 42, "limit": null, "unlimited": true },
    "key_scope": "write"
  },
  "error": null
}

Authentication

Send the key as a bearer token. X-API-Key is accepted as an alias when a tool cannot set an Authorization header.

Authorization: Bearer tto_live_9fJ2k...
# or
X-API-Key: tto_live_9fJ2k...
PropertyBehaviour
Formattto_live_ followed by 43 base64url characters
StorageOnly a SHA-256 hash and a 16-character prefix are kept
Scopesread or write; write implies read
Expiry30, 90 or 365 days, or never
RevocationImmediate, on the next request
Limit10 active keys per account
Keys are secrets. Keep them out of client-side code and version control; rotate by creating a new key and revoking the old one.

Conventions

TopicRule
Content typeRequests with a body must send Content-Type: application/json
TimestampsISO 8601 in UTC, e.g. 2026-10-30T09:14:02.000Z
IdentifiersUUIDs. Pass the id, never the slug
Paginationlimit (max 100) and offset, plus a pagination object
CachingResponses are no-store; do not cache
IdempotencyCreating two links with the same body creates two links; use a unique slug if you need to detect duplicates

Endpoints

Six endpoints. read keys can call every GET; write keys are required for POST and DELETE.

GET /v1/me read scope

Account and key

Confirms the key works and returns the plan, link usage and the key's own scope.

FieldInTypeRequiredNotes
— — — — No parameters.
curl https://tinyto.me/api/v1/me \
  -H "Authorization: Bearer tto_live_..."
const res = await fetch("https://tinyto.me/api/v1/me", {
  headers: { Authorization: `Bearer ${process.env.TINYTO_KEY}` },
});
res = requests.get(
    "https://tinyto.me/api/v1/me",
    headers={"Authorization": f"Bearer {os.environ['TINYTO_KEY']}"},
    timeout=10,
)

Response · 200 OK

{
  "data": {
    "plan": "advanced",
    "link_usage": { "used": 42, "limit": null, "unlimited": true },
    "key_scope": "write"
  },
  "error": null
}

MCP server

The same account is reachable over the Model Context Protocol, so Claude and ChatGPT can create and read links as a tool rather than through raw HTTP. The endpoint is https://tinyto.me/mcp.

Authentication is OAuth 2.1 with PKCE. The client registers itself, sends the user through a consent screen and receives a bearer token — there is no API key to paste. Unlike the REST API, the MCP server is available on every plan; link limits follow the plan.

ToolScopeWhat it does
list_linkslinks:readList links with click counts, newest first
get_linklinks:readOne link with its destination and clicks
get_link_statslinks:readClicks, countries and the weekday/hour heatmap
account_infolinks:readPlan, active links and remaining allowance
create_linklinks:createShorten a URL, optionally with a slug or expiry

No tool deletes or edits an existing link: the assistant can create and read, nothing else. create_link is the only write operation and is marked non-destructive, so clients show it without extra warnings.

Connecting a client

Every client takes the same URL and walks the OAuth flow from there: Claude, Claude Code, ChatGPT, OpenCode, Cursor, VS Code, Codex CLI, Gemini CLI, Windsurf, Zed, Cline and Antigravity. Step-by-step instructions, including the exact config for each one, live on their own page:

Connect TinyTo to Claude, ChatGPT and other MCP clients →

Discovery endpoints

Clients discover everything from these, so no manual configuration is needed:

URLPurpose
/.well-known/oauth-protected-resourceRFC 9728 metadata: which authorization server protects /mcp
/.well-known/oauth-authorization-serverRFC 8414 metadata: authorize, token, registration and JWKS URLs
/.well-known/jwks.jsonPublic keys to verify access tokens
Access tokens are RS256 JWTs with the audience set to https://tinyto.me/mcp, valid for one hour, refreshed with a rotating refresh token. Users disconnect an app from Dashboard → API, which invalidates its tokens immediately; clients can also call POST /oauth/revoke (RFC 7009).

Errors

Failures never throw a different shape. Treat error.code as the stable contract and the HTTP status as a hint.

{
  "data": null,
  "error": {
    "code": "invalid_request",
    "message": "slug must be 3-50 alphanumeric characters."
  }
}
StatusCodeWhen
400 invalid_request A required field is missing, has the wrong type, or the slug/expiry is invalid.
401 unauthorized No key, or the key is invalid, expired or revoked.
402 plan_limit_reached The account has no active links left on its plan.
403 forbidden Read-only key used for a write call, or the plan does not include API access.
404 not_found The link does not exist, or it belongs to another account.
429 rate_limited More than 120 requests in a minute for this key.
500 server_error Unexpected failure on our side. Safe to retry with backoff.

Rate limits

120 requests per minute per key, counted in a rolling window. Exceeding it returns 429 with Retry-After and the X-RateLimit-* headers.

HTTP/1.1 429 Too Many Requests
Retry-After: 17
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1793352680

Back off on Retry-After rather than retrying immediately, and batch reads where you can.

Changelog

DateChange
2026-10-30v1 published: links, per-link stats and account endpoints.
Machine-readable contract: /openapi.json, generated from the same list this page renders. Breaking changes will ship under /v2; v1 keeps working for at least 6 months after a successor is announced.