LLM Integration
The linked.is API is designed to be easily consumed by Large Language Models (LLMs) and AI agents. This guide explains how to integrate our API with AI-powered tools.
Quick Start for LLMs
Every page of this API reference is also available as plain Markdown. Add /raw in front of the docs path and .md at the end:
https://linked.is/raw/docs/api.md
https://linked.is/raw/docs/api/authentication.md
https://linked.is/raw/docs/api/links.md
https://linked.is/raw/docs/api/profiles.md
https://linked.is/raw/docs/api/analytics.md
https://linked.is/raw/docs/api/rate-limits.md
These files contain the endpoints, request and response formats, and examples in a format that is easy for LLMs to read.
Common LLM Use Cases
1. URL Shortening
LLMs can shorten URLs on behalf of users:
<script setup lang="ts">
const shortenUrl = async () => {
// Shorten a URL
const response = await fetch('https://linked.is/api/v1/links', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://example.com/very/long/article/path'
})
})
const { data } = await response.json()
// Returns: { id: 42, shortUrl: "https://linked.is/aB3dE9xZ", ... }
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="shortenUrl">Shorten URL</button>
</div>
</template>
2. Analytics Retrieval
Query link performance data:
<script setup lang="ts">
const getAnalytics = async () => {
// Get analytics overview
const response = await fetch('https://linked.is/api/v1/analytics/overview', {
headers: { 'Authorization': `Bearer ${API_TOKEN}` }
})
const { data } = await response.json()
// Returns: { totalClicks: 1500, totalLinks: 25, topLinks: [...], ... }
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="getAnalytics">Get Analytics</button>
</div>
</template>
3. Profile Management
Create and manage link-in-bio profiles:
<script setup lang="ts">
const createProfile = async () => {
// Create a new bio profile
const response = await fetch('https://linked.is/api/v1/profiles', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
displayName: 'AI Assistant',
slug: 'ai-assistant',
bio: 'Links curated by AI'
})
})
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="createProfile">Create AI Profile</button>
</div>
</template>
API Response Handling
Successful responses have success: true and the result in data. Errors carry the error code in data.error, or only a message for authentication, validation and rate limit errors. See Errors.
<script setup lang="ts">
const apiResponse = {
success: true,
data: { /* ... */ }
};
const handleApiResponse = (result: any) => {
if (result.success) {
// Process data
const links = result.data.items;
console.log("Links:", links);
} else {
// Handle error
const errorCode = result.data?.error?.code;
const errorMessage = result.data?.error?.message || result.message;
console.error("Error:", result.statusCode, errorCode, errorMessage);
}
};
</script>
<template>
<div>
<h3>API Response:</h3>
<pre>{{ JSON.stringify(apiResponse, null, 2) }}</pre>
<button @click="handleApiResponse(apiResponse)">Process Response</button>
</div>
</template>
Error Handling
Common errors LLMs should handle:
| Error | Meaning | Suggested Action |
|---|---|---|
400 with statusMessage: "Validation Error" | A field is missing or invalid, for example a URL that isn't http or https | Read message, fix the field and retry |
BLOCKED_URL | The URL is flagged as malicious | Don't retry; tell the user the URL was blocked |
SLUG_TAKEN | The slug is used by another link or a profile | Generate a different slug |
SLUG_RESERVED | The slug is a reserved path | Generate a different slug |
LINK_NOT_FOUND | Link doesn't exist or isn't accessible | Inform user the link was not found |
PROFILE_NOT_FOUND | Profile doesn't exist or isn't accessible | Suggest creating a new profile |
401 | Token is missing, invalid or expired | Ask the user for a valid API token |
429 | Rate limit exceeded | Wait for the Retry-After seconds, then retry |
Best Practices for AI Agents
1. Rate Limiting
Respect rate limits to avoid being blocked. Creating links is limited to 100 requests per minute per token:
<script setup lang="ts">
const makeRequestWithRetry = async (url: string, options: RequestInit, maxRetries = 3) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) { // Rate limited
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
return response.json();
}
throw new Error('Max retries exceeded');
};
const handleRetryRequest = async () => {
try {
const result = await makeRequestWithRetry(
"https://linked.is/api/v1/links",
{
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json"
},
body: JSON.stringify({ url: "https://example.com" })
}
);
console.log("Result:", result);
} catch (error) {
console.error("Max retries exceeded:", error);
}
};
</script>
<template>
<div>
<button @click="handleRetryRequest">Retry Request</button>
</div>
</template>
2. Batch Operations
When processing multiple items, use pagination efficiently:
<script setup lang="ts">
const getAllLinks = async (apiToken: string) => {
const allLinks = [];
let page = 1;
while (true) {
const response = await fetch(
`https://linked.is/api/v1/links?page=${page}&limit=100`,
{
headers: {
"Authorization": `Bearer ${apiToken}`
}
}
);
const result = await response.json();
const data = result.data;
allLinks.push(...data.items);
if (page >= data.pagination.totalPages) {
break;
}
page += 1;
}
return allLinks;
};
const fetchAllLinks = async () => {
try {
const links = await getAllLinks("YOUR_API_TOKEN");
console.log("All links:", links);
} catch (error) {
console.error("Error fetching links:", error);
}
};
</script>
<template>
<div>
<button @click="fetchAllLinks">Fetch All Links</button>
</div>
</template>
3. Idempotent Operations
Use custom slugs for idempotent link creation. SLUG_TAKEN means the slug is in use, but not necessarily by you: another account or a bio profile may own it. Check your own links before reusing the result:
<script setup lang="ts">
const createOrGetLink = async (url: string, slug: string, apiToken: string) => {
const headers = {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
};
// Try to create with specific slug
const response = await fetch(
'https://linked.is/api/v1/links',
{
method: 'POST',
headers,
body: JSON.stringify({url, slug})
}
);
const result = await response.json();
if (result.data?.error?.code === 'SLUG_TAKEN') {
// Look for the slug among your own links
const search = await fetch(
`https://linked.is/api/v1/links?search=${encodeURIComponent(slug)}`,
{ headers }
);
const { data } = await search.json();
const existing = data.items.find((link: { slug: string }) => link.slug === slug);
if (existing) return existing;
throw new Error('Slug is used by someone else, choose another one');
}
return result.data;
};
const handleLinkCreation = async () => {
try {
const link = await createOrGetLink(
"https://example.com/article",
"my-slug",
"YOUR_API_TOKEN"
);
console.log("Link created or retrieved:", link);
} catch (error) {
console.error("Error:", error);
}
};
</script>
<template>
<div>
<button @click="handleLinkCreation">Create/Get Link</button>
</div>
</template>
Function Calling Schema
For LLMs that support function calling, here's the schema:
<script setup lang="ts">
const createShortLinkSchema = {
name: "create_short_link",
description: "Create a shortened URL using linked.is",
parameters: {
type: "object",
properties: {
url: {
type: "string",
description: "The long URL to shorten (http or https)"
},
slug: {
type: "string",
description: "Optional custom slug for the short URL: 3-50 letters, numbers or hyphens"
}
},
required: ["url"]
}
};
const getLinkAnalyticsSchema = {
name: "get_link_analytics",
description: "Get click analytics for a short link",
parameters: {
type: "object",
properties: {
link_id: {
type: "integer",
description: "The ID of the link"
},
start: {
type: "string",
description: "Optional start date (ISO 8601), defaults to 30 days ago"
},
end: {
type: "string",
description: "Optional end date (ISO 8601), defaults to now"
}
},
required: ["link_id"]
}
};
</script>
<template>
<div>
<h3>Create Short Link Schema:</h3>
<pre>{{ JSON.stringify(createShortLinkSchema, null, 2) }}</pre>
<h3>Get Link Analytics Schema:</h3>
<pre>{{ JSON.stringify(getLinkAnalyticsSchema, null, 2) }}</pre>
</div>
</template>
Your code runs the function: create_short_link maps to POST /v1/links and get_link_analytics to GET /v1/links/:id/stats.
MCP Integration
For AI assistants using Model Context Protocol (MCP), you can expose linked.is as a tool in your own MCP server. Define the tool, and have its handler call POST /v1/links with your API token:
<script setup lang="ts">
const mcpSchema = {
tools: [
{
name: "linkedis_shorten",
description: "Shorten a URL using linked.is",
inputSchema: {
type: "object",
properties: {
url: { type: "string" }
},
required: ["url"]
}
}
]
};
</script>
<template>
<div>
<h3>MCP Integration Schema:</h3>
<pre>{{ JSON.stringify(mcpSchema, null, 2) }}</pre>
</div>
</template>
Security Considerations
- Store API tokens securely, never in code
- Use environment variables for token storage
- Call the API from your server, not from the browser
- Implement token rotation policies (tokens expire after one year)
- Log API usage for audit purposes
Example: AI-Powered Link Manager
Here's a complete example of an AI agent managing links:
<script setup lang="ts">
interface LinkData {
id: number;
url: string;
slug: string;
shortUrl: string;
title: string | null;
clicks: number;
createdAt: string;
}
interface StatsData {
totalClicks: number;
countries: Array<{ country: string; code: string | null; clicks: number }>;
devices: Array<{ device: string; clicks: number }>;
browsers: Array<{ browser: string; clicks: number }>;
referrers: Array<{ referrer: string; clicks: number }>;
}
interface LinkedisAgent {
apiToken: string;
baseUrl: string;
headers: Record<string, string>;
}
const createAgent = (apiToken: string): LinkedisAgent => {
return {
apiToken,
baseUrl: 'https://linked.is/api/v1',
headers: {
'Authorization': `Bearer ${apiToken}`,
'Content-Type': 'application/json'
}
};
};
// Create a short link
const shorten = async (agent: LinkedisAgent, url: string, slug?: string): Promise<LinkData> => {
const payload: { url: string; slug?: string } = { url };
if (slug) {
payload.slug = slug;
}
const response = await fetch(
`${agent.baseUrl}/links`,
{
method: 'POST',
headers: agent.headers,
body: JSON.stringify(payload)
}
);
const result = await response.json();
return result.data;
};
// Get link statistics
const getStats = async (agent: LinkedisAgent, linkId: number): Promise<StatsData> => {
const response = await fetch(
`${agent.baseUrl}/links/${linkId}/stats`,
{
headers: agent.headers
}
);
const result = await response.json();
return result.data;
};
// Search links by URL, title or slug
const searchLinks = async (agent: LinkedisAgent, query: string): Promise<LinkData[]> => {
const response = await fetch(
`${agent.baseUrl}/links?search=${encodeURIComponent(query)}`,
{
headers: agent.headers
}
);
const result = await response.json();
return result.data.items;
};
// Usage example
const handleAgentDemo = async () => {
const agent = createAgent("YOUR_API_TOKEN");
try {
const shortLink = await shorten(agent, 'https://example.com/article');
console.log(`Created: ${shortLink.shortUrl}`);
const stats = await getStats(agent, shortLink.id);
console.log("Link stats:", stats);
const searchResults = await searchLinks(agent, "article");
console.log("Search results:", searchResults);
} catch (error) {
console.error("Error:", error);
}
};
</script>
<template>
<div>
<button @click="handleAgentDemo">Run AI Agent Demo</button>
</div>
</template>
Resources
- Full API Reference
- Authentication Guide
- Rate Limits
- Machine-readable docs:
https://linked.is/raw/docs/api.mdand the other pages listed in Quick Start for LLMs