Gajo URL Shortener API

Shorten links, generate QR codes, and read click analytics over a simple REST API. Every account, including the free plan, gets an API key with no credit card. Upgrade to Pro for batch shortening, device routing, A/B split links, and app deep links with App Store fallback.

Overview

The Gajo REST API shortens URLs, manages links, and returns click analytics. Every Gajo account plan includes the core API, including the Free plan, no credit card required. Pro adds batch shortening, editing destinations, device routing, A/B split links, and app deep links.

Base URL

https://gajo.io/api/v1

Machine-readable spec: /openapi.json (OpenAPI 3.1)

Authentication

Send your API key in the x-api-key header on every request. Create a free account, then generate a key on the API Keys page.

curl -X POST "https://gajo.io/api/v1/shorten" \
     --header "x-api-key: YOUR_API_KEY" \
     --header "Content-Type: application/json" \
     --data '{"url": "https://example.com"}'
Plans & Limits
FreeBasicPro
Price$0, no card$4.99 / month$9.99 / month
URLs per month5050010,000
Requests per minute 10 30 60
Max link lifetime60 days120 daysUnlimited
Short code length6 characters5 characters5 characters
Stats endpointTotals + 7-day historyFull: referrers, devices, geoFull: referrers, devices, geo
Batch, edit, routing, templates, deep links——Yes

The monthly URL allowance is shared with links created in the dashboard. Endpoints marked Pro return 403 on other plans. See pricing for full plan details.

Rate Limits & Errors

Each key may make the plan's number of requests per 60-second sliding window. Every response includes X-RateLimit-Limit and X-RateLimit-Remaining. Over the limit, the API returns 429 Too Many Requests with a Retry-After header (seconds). POST /shorten also returns 429 once the monthly URL allowance is used up.

All responses share one envelope. Errors set success to false and explain why in message:

{
    "success": false,
    "message": "A Pro plan is required for batch shortening"
}
StatusMeaning
400Invalid request (e.g. unsupported URL scheme, bad lifetimeDays, or a destination flagged as phishing or malware by Google Web Risk)
401Missing or invalid x-api-key
403Endpoint or field requires a Pro plan
404Short code not found, or it belongs to another account
409The requested custom code is taken, released, or already linked to a URL
429Rate limit or monthly URL allowance exceeded
AI Assistants (MCP) All plans

Connect Claude, Cursor, VS Code, or any MCP client to https://gajo.io/mcp (Streamable HTTP) and send your API key as the x-api-key header. The assistant can then shorten links and look up stats for you, on your plan's limits.

ToolWhat it does
shorten_urlCreate a short link (Pro: customCode, deep links)
list_urls, get_urlFind your links
get_url_statsClick analytics, bots excluded
update_urlPro: change a link's destination
deactivate_urlTurn a link off
get_usagePlan and remaining monthly quota

Claude Code:

claude mcp add --transport http gajo https://gajo.io/mcp --header "x-api-key: YOUR_API_KEY"

Cursor (~/.cursor/mcp.json):

{
    "mcpServers": {
        "gajo": {
            "url": "https://gajo.io/mcp",
            "headers": { "x-api-key": "YOUR_API_KEY" }
        }
    }
}

VS Code (.vscode/mcp.json):

{
    "servers": {
        "gajo": {
            "type": "http",
            "url": "https://gajo.io/mcp",
            "headers": { "x-api-key": "YOUR_API_KEY" }
        }
    }
}

Clients that only support bearer tokens can send Authorization: Bearer YOUR_API_KEY instead. Tool calls share your key's per-minute rate limit with the REST API.

Account

GET /api/v1/me All plans

Your account and plan capabilities. Useful for testing credentials.

{
    "success": true,
    "data": {
        "plan": "Free",
        "email": "you@example.com",
        "urlCount": 12,
        "apiAccess": true,
        "customCodes": false,
        "routingRules": false,
        "memberSince": "2026-03-22T12:00:00Z"
    }
}
GET /api/v1/usage All plans

This month's usage against your plan. maxLinkLifetimeDays is null when unlimited.

{
    "success": true,
    "data": {
        "plan": "Free",
        "urlsCreatedThisMonth": 8,
        "urlsMonthlyLimit": 50,
        "urlsRemaining": 42,
        "requestsPerMinute": 10,
        "maxLinkLifetimeDays": 60,
        "totalUrls": 12,
        "totalClicks": 318
    }
}

URLs

POST /api/v1/shorten All plans

Create a short URL. Code length and lifetime follow your plan.

FieldTypeRequiredDescription
urlstringYesDestination URL (max 2048 chars): http, https, mailto, tel, ftp, sftp. Pro may also use app deep links such as myapp://path.
customCodestringNoPro: use a custom code (3-50 letters, digits, or hyphens; stored lowercase). The link redirects at https://gajo.io/c/{code}. An unclaimed code is claimed for you, and one of your own codes is reused if it isn't linked to a URL; otherwise 409 (change a linked code's destination with PUT /urls/{code}). Can't be combined with lifetimeDays.
lifetimeDaysintegerNoDays until the link expires. Defaults to, and is capped at, your plan maximum (60 Free, 120 Basic, unlimited Pro).
fallbackUrlstringNoPro, deep links only: a web URL (e.g. the App Store listing) used when the app isn't installed.
{
    "success": true,
    "message": "URL shortened successfully",
    "data": {
        "shortCode": "AbCdEf",
        "originalUrl": "https://example.com/page",
        "fallbackUrl": null,
        "active": true,
        "clickCount": 0,
        "isCustomCode": false,
        "createdDate": "2026-10-07T12:00:00Z",
        "expiresDate": "2026-12-06T12:00:00Z"
    }
}

The short link is https://gajo.io/{shortCode}, or https://gajo.io/c/{shortCode} when isCustomCode is true.

POST /api/v1/shorten/batch Pro

Create up to 100 short URLs in one request. Each item takes the same fields as /shorten and reports its own success or error.

{
    "urls": [
        { "url": "https://example.com/page1", "lifetimeDays": 90 },
        { "url": "https://example.com/page2" }
    ]
}
GET /api/v1/urls All plans

List your short URLs, newest first, a page at a time. data is an array of links.

Query parameterDefaultDescription
page1Page number, starting at 1
limit100Links per page, up to 1000

The X-Total-Count header has the total number of links. When there are more, a Link header points at the next page: </api/v1/urls?page=2&limit=100>; rel="next".

GET /api/v1/urls/{code}

Get one of your short URLs by code.

GET /api/v1/urls/{code}/stats All plans

Click analytics for a URL. Bot traffic (crawlers, link previews, AI fetchers) is excluded. Free returns totalClicks and 7 days of clicksByDay. Basic and Pro return 30 days plus referrer, device, country, and region breakdowns.

{
    "success": true,
    "data": {
        "totalClicks": 318,
        "historyDays": 30,
        "clicksByDay": [ { "date": "2026-10-06", "clicks": 42 } ],
        "topReferrers": [ { "source": "https://news.ycombinator.com/", "count": 120 } ],
        "deviceBreakdown": [ { "device": "Mobile", "percentage": 61.2 } ],
        "topCountries": [ { "country": "United States", "count": 140 } ],
        "topRegions": [ { "region": "California, US", "count": 51 } ]
    }
}
PUT /api/v1/urls/{code} Pro

Change a link's destination or interstitial setting. Takes effect immediately.

FieldTypeDescription
originalUrlstringNew destination (validated like /shorten; deep links allowed)
interstitialEnabledbooleanShow your branded interstitial page before redirecting
DELETE /api/v1/urls/{code} All plans

Deactivate a link. The redirect stops working; generated codes are never reassigned to anyone else.

Routing Pro

Route Templates Pro

Reusable device-routing templates. A template only defines which device slots exist: Android, IOS, Mobile, Tablet, Desktop, Default. The concrete destinations are set per link when you bind it. OS-specific slots win over device classes, and Default catches everything else.

GET /api/v1/route-templates  ·  GET /api/v1/route-templates/{name}
POST /api/v1/route-templates  ·  PUT /api/v1/route-templates/{name}
{
    "name": "app-store-router",
    "description": "Phones to the app, everyone else to the website",
    "rules": [
        { "deviceType": "IOS" },
        { "deviceType": "Android" },
        { "deviceType": "Default" }
    ]
}
PATCH /api/v1/route-templates/{name}

Rename: { "newName": "app-router-v2" }

DELETE /api/v1/route-templates/{name}

Delete a template. Bound links fall back to their original destination, and the name can be used again.

A/B Templates Pro

Reusable split tests. Define variant names and traffic percentages (they must sum to 100). Each click is bucketed independently. Destinations are set per link when you bind it.

GET /api/v1/ab-templates  ·  GET /api/v1/ab-templates/{name}
POST /api/v1/ab-templates  ·  PUT /api/v1/ab-templates/{name}
{
    "name": "homepage-test",
    "description": "New homepage vs. current",
    "variants": [
        { "variantName": "Control", "splitPercentage": 50 },
        { "variantName": "New-Design", "splitPercentage": 50 }
    ]
}
PATCH /api/v1/ab-templates/{name}  ·  DELETE /api/v1/ab-templates/{name}

Rename ({ "newName": "..." }) or delete a template. Deleting frees the name for reuse. Variant names can't contain :.

URL Template Binding Pro

Attach one template to a link and supply a concrete destination for each slot. A link has at most one binding.

GET /api/v1/urls/{code}/binding

The current binding, or { "bound": false }. templateName is the bound template (null if it was deleted), and chained endpoints include routeTemplateName.

POST /api/v1/urls/{code}/binding
FieldTypeRequiredDescription
templateTypestringYesDevice or AbTest
templateNamestringYesName of your route or A/B template
endpoints[].slotKeystringYesA device slot (e.g. IOS, Default) or a variant name
endpoints[].targetUrlstringYesDestination for the slot. May be an app deep link (custom scheme).
endpoints[].fallbackUrlstringNoWeb fallback when targetUrl is a deep link and the app isn't installed
endpoints[].targetTypestringNoUrl (default), or RouteTemplate to chain an A/B variant into device routing
endpoints[].routeTemplateNamestringWith RouteTemplateThe route template the variant chains to

Device routing with deep links and store fallbacks:

{
    "templateType": "Device",
    "templateName": "app-store-router",
    "endpoints": [
        { "slotKey": "IOS", "targetUrl": "myapp://product/42", "fallbackUrl": "https://apps.apple.com/app/example/id123456" },
        { "slotKey": "Android", "targetUrl": "myapp://product/42", "fallbackUrl": "https://play.google.com/store/apps/details?id=com.example" },
        { "slotKey": "Default", "targetUrl": "https://example.com/product/42" }
    ]
}

A/B split:

{
    "templateType": "AbTest",
    "templateName": "homepage-test",
    "endpoints": [
        { "slotKey": "Control", "targetUrl": "https://example.com" },
        { "slotKey": "New-Design", "targetUrl": "https://example.com/v2" }
    ]
}

A/B split where one variant also routes by device. Give the variant "targetType": "RouteTemplate" and add its per-device destinations as endpoints keyed Variant:Device. Include every device slot of the route template, or a Variant:Default:

{
    "templateType": "AbTest",
    "templateName": "homepage-test",
    "endpoints": [
        { "slotKey": "Control", "targetType": "RouteTemplate", "routeTemplateName": "app-store-router" },
        { "slotKey": "Control:IOS", "targetUrl": "https://apps.apple.com/app/example/id123456" },
        { "slotKey": "Control:Default", "targetUrl": "https://example.com" },
        { "slotKey": "New-Design", "targetUrl": "https://example.com/v2" }
    ]
}
DELETE /api/v1/urls/{code}/binding

Remove the binding. The link returns to its original destination.

Per-URL Routing Rules Pro

One-off device or A/B rules stored on a single link. Prefer templates and bindings for anything reusable. A link's binding takes precedence over its per-URL rules. GET also reports the binding.

GET POST DELETE /api/v1/urls/{code}/routing  ·  PUT DELETE /api/v1/urls/{code}/routing/{ruleId}
{
    "rules": [
        { "ruleType": "Device", "deviceType": "Mobile", "targetUrl": "https://m.example.com" },
        { "ruleType": "Device", "deviceType": "Desktop", "targetUrl": "https://example.com" }
    ]
}

Device rules use Mobile, Tablet, or Desktop. A/B rules use "ruleType": "AbTest" with variantName and splitPercentage (summing to 100). POST replaces the link's rules. PUT updates one rule's targetUrl, splitPercentage, or active; active A/B splits must still total 100, so to change several splits at once, POST the full set.