API Reference
The linked.is API allows you to programmatically manage your short links, bio profiles and profile blocks, and access analytics data.
Base URL
All API requests should be made to:
https://linked.is/api/v1
Paths in this reference such as POST /v1/links are relative to https://linked.is/api, so POST /v1/links means POST https://linked.is/api/v1/links.
Authentication
All API requests require authentication using an API token. You can generate an API token from your account settings. See Authentication for details.
Include your API token in the Authorization header:
curl -X GET "https://linked.is/api/v1/links" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<script setup lang="ts">
const authenticatedApiCall = async () => {
const response = await fetch("https://linked.is/api/v1/links", {
method: "GET",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="authenticatedApiCall">Call Authenticated API</button>
</div>
</template>
Request Format
Send request bodies as JSON with the Content-Type: application/json header. IDs of links, profiles, blocks and teams are integers. Dates in responses are ISO 8601 strings in UTC, for example 2024-01-15T10:30:00.000Z.
Response Format
Successful responses are returned in JSON format with success and data:
<script setup lang="ts">
const successResponse = {
success: true,
data: { /* ... */ }
};
</script>
<template>
<div>
<h3>Success Response:</h3>
<pre>{{ JSON.stringify(successResponse, null, 2) }}</pre>
</div>
</template>
The only exception is Get QR Code, which returns the image itself for the png and svg formats.
Errors
Errors use the standard error body of the server. When the API knows what went wrong, the error code and a readable message are in data.error:
<script setup lang="ts">
const errorResponse = {
error: true,
url: "https://linked.is/api/v1/links/42",
statusCode: 404,
statusMessage: "Server Error",
message: "",
data: {
success: false,
error: {
code: "LINK_NOT_FOUND",
message: "The specified link does not exist"
}
}
};
</script>
<template>
<div>
<h3>Error Response:</h3>
<pre>{{ JSON.stringify(errorResponse, null, 2) }}</pre>
</div>
</template>
Some errors have no data.error, only a top-level message:
- Authentication errors (
401), for example"message": "Invalid API token". See Authentication Errors. - Validation errors (
400) when the request body doesn't match the documented fields.statusMessageis"Validation Error"andmessageis a JSON array of the problems, each with the fieldpathand amessage. - Team access errors (
403) with"message": "Not a member of this team". See Team Workspaces. - Rate limit errors (
429). See Rate Limits.
To read any error, use body.data?.error?.code and fall back to body.message.
Error Codes
| Code | Status | Description |
|---|---|---|
INVALID_REQUEST | 400 | The ID in the path is missing or not a number |
SLUG_TAKEN | 400 | The slug is already used by a short link or a bio profile |
SLUG_RESERVED | 400 | The slug is a reserved path (for example dashboard or api) |
BLOCKED_URL | 400 | The destination URL is flagged as malicious |
INVALID_BLOCKS | 400 | A reorder request didn't list every block of the profile exactly once |
LINK_NOT_FOUND | 404 | The link doesn't exist or you don't have access to it |
PROFILE_NOT_FOUND | 404 | The profile doesn't exist or you don't have access to it |
BLOCK_NOT_FOUND | 404 | The block doesn't exist on this profile |
CREATE_FAILED | 500 | The item couldn't be created |
UPDATE_FAILED | 500 | The item couldn't be updated |
HTTP Status Codes
| Code | Description |
|---|---|
200 | Success (also when an item is created or deleted) |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
429 | Rate Limit Exceeded |
500 | Internal Server Error |
Rate Limits
Creating links (POST /v1/links) is limited to 100 requests per minute for each API token. The limit is the same on every plan. See Rate Limits.
Rate limit information is included in the response headers of POST /v1/links:
<script setup lang="ts">
const rateLimitHeaders = {
"X-RateLimit-Limit": 100,
"X-RateLimit-Remaining": 99,
"X-RateLimit-Reset": 1640000000
};
</script>
<template>
<div>
<h3>Rate Limit Headers:</h3>
<pre>{{ JSON.stringify(rateLimitHeaders, null, 2) }}</pre>
</div>
</template>
Team Workspaces
By default the API works in your personal workspace. To work in a team you belong to, add the teamId query parameter to these endpoints:
GET /v1/linksandPOST /v1/linksGET /v1/profilesandPOST /v1/profilesGET /v1/analytics/overview
curl -X GET "https://linked.is/api/v1/links?teamId=7" \
-H "Authorization: Bearer YOUR_API_TOKEN"
If you are not a member of the team, the request fails with 403. A teamId that isn't a positive number is ignored and your personal workspace is used.
Endpoints that take a link or profile ID (for example GET /v1/links/:id) don't need teamId. They work for your own personal items and for the items of every team you are a member of.
List Teams
List the teams you are a member of, with your role in each.
GET /v1/teams
curl -X GET "https://linked.is/api/v1/teams" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<script setup lang="ts">
const teamsResponse = {
success: true,
data: {
items: [
{
id: 7,
name: "Marketing",
slug: "marketing",
avatar: null,
role: "owner"
}
]
}
};
</script>
<template>
<pre>{{ JSON.stringify(teamsResponse, null, 2) }}</pre>
</template>
role is owner, admin or member.