Profiles API
Create and manage bio profiles programmatically.
Create Profile
Create a new bio profile.
POST /v1/profiles
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
teamId | number | No | Create the profile in this team instead of your personal workspace. See Team Workspaces |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
displayName | string | Yes | Display name shown on the profile, 1–100 characters |
slug | string | Yes | URL slug, 3–50 letters, numbers, hyphens or underscores. The profile is published at https://linked.is/{slug}. It can't match another profile, a short link or a reserved path such as dashboard |
bio | string | No | Short biography text, up to 500 characters |
avatar | string | No | Avatar image URL, up to 2048 characters |
theme | string | No | Profile theme: simple (default), modern, minimal, bold, elegant or dark. Up to 50 characters; an unknown theme is shown as simple |
Example Request
curl -X POST "https://linked.is/api/v1/profiles" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"displayName": "John Doe",
"slug": "johndoe",
"bio": "Developer & Creator",
"avatar": "https://example.com/avatar.jpg"
}'
<script setup lang="ts">
const createProfile = async () => {
const response = await fetch("https://linked.is/api/v1/profiles", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
displayName: "John Doe",
slug: "johndoe",
bio: "Developer & Creator",
avatar: "https://example.com/avatar.jpg"
}),
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="createProfile">Create Profile</button>
</div>
</template>
Example Response
<script setup lang="ts">
const profileResponse = {
success: true,
data: {
id: 15,
displayName: "John Doe",
slug: "johndoe",
bio: "Developer & Creator",
avatar: "https://example.com/avatar.jpg",
profileUrl: "https://linked.is/johndoe",
createdAt: "2024-01-15T10:30:00.000Z"
}
};
</script>
<template>
<pre>{{ JSON.stringify(profileResponse, null, 2) }}</pre>
</template>
Get Profile
Retrieve a specific profile by ID, with its blocks in display order. Works for your personal profiles and for profiles of teams you are a member of.
GET /v1/profiles/:id
Example Request
curl -X GET "https://linked.is/api/v1/profiles/15" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<script setup lang="ts">
const getProfile = async () => {
const response = await fetch("https://linked.is/api/v1/profiles/15", {
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="getProfile">Get Profile</button>
</div>
</template>
Example Response
<script setup lang="ts">
const profileResponse = {
success: true,
data: {
id: 15,
displayName: "John Doe",
slug: "johndoe",
bio: "Developer & Creator",
avatar: "https://example.com/avatar.jpg",
theme: "simple",
profileUrl: "https://linked.is/johndoe",
createdAt: "2024-01-15T10:30:00.000Z",
updatedAt: "2024-01-15T10:30:00.000Z",
blocks: [
{
id: 101,
type: "link",
label: "My Website",
content: "https://example.com",
icon: null,
settings: null,
order: 0,
isVisible: true
}
]
}
};
</script>
<template>
<pre>{{ JSON.stringify(profileResponse, null, 2) }}</pre>
</template>
Update Profile
Update an existing profile. Send only the fields you want to change.
PATCH /v1/profiles/:id
Request Body
| Parameter | Type | Description |
|---|---|---|
displayName | string | Display name, up to 100 characters |
slug | string | URL slug, 3–50 letters, numbers, hyphens or underscores. Must be free, as in Create Profile |
bio | string | Biography text, up to 500 characters |
avatar | string | Avatar URL, up to 2048 characters |
theme | string | Profile theme, see Create Profile |
Example Request
curl -X PATCH "https://linked.is/api/v1/profiles/15" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"bio": "Updated bio text"
}'
<script setup lang="ts">
const updateProfile = async () => {
const response = await fetch("https://linked.is/api/v1/profiles/15", {
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
bio: "Updated bio text"
}),
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="updateProfile">Update Profile</button>
</div>
</template>
Example Response
<script setup lang="ts">
const updateProfileResponse = {
success: true,
data: {
id: 15,
displayName: "John Doe",
slug: "johndoe",
bio: "Updated bio text",
avatar: "https://example.com/avatar.jpg",
theme: "simple",
profileUrl: "https://linked.is/johndoe",
createdAt: "2024-01-15T10:30:00.000Z",
updatedAt: "2024-01-16T14:20:00.000Z"
}
};
</script>
<template>
<pre>{{ JSON.stringify(updateProfileResponse, null, 2) }}</pre>
</template>
Delete Profile
Delete a profile permanently. Its blocks, view and click history, and form submissions are deleted with it.
DELETE /v1/profiles/:id
Example Request
curl -X DELETE "https://linked.is/api/v1/profiles/15" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<script setup lang="ts">
const deleteProfile = async () => {
const response = await fetch("https://linked.is/api/v1/profiles/15", {
method: "DELETE",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
},
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="deleteProfile" class="danger">Delete Profile</button>
</div>
</template>
Example Response
<script setup lang="ts">
const deleteProfileResponse = {
success: true,
data: {
deleted: true
}
};
</script>
<template>
<pre>{{ JSON.stringify(deleteProfileResponse, null, 2) }}</pre>
</template>
List Profiles
Get all the profiles in your personal workspace, or in a team with teamId. This endpoint is not paginated.
GET /v1/profiles
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
teamId | number | No | List the profiles of this team. See Team Workspaces |
Example Response
<script setup lang="ts">
const listProfilesResponse = {
success: true,
data: {
items: [
{
id: 15,
displayName: "John Doe",
slug: "johndoe",
bio: "Developer & Creator",
avatar: "https://example.com/avatar.jpg",
theme: "simple",
createdAt: "2024-01-15T10:30:00.000Z",
updatedAt: "2024-01-15T10:30:00.000Z",
profileUrl: "https://linked.is/johndoe"
}
]
}
};
</script>
<template>
<pre>{{ JSON.stringify(listProfilesResponse, null, 2) }}</pre>
</template>
Profile Blocks
Blocks are the items shown on a profile, such as links, headings and embeds. Every block endpoint returns blocks in this shape:
| Field | Type | Description |
|---|---|---|
id | number | Block ID |
type | string | Block type, see Block Types |
label | string | null | Block label/title |
content | string | null | Block content, depending on the type |
icon | string | null | Icon name |
settings | object | null | Additional settings |
order | number | Position on the profile, lower is shown first |
isVisible | boolean | Whether the block is shown on the profile |
List Blocks
Get all blocks of a profile in display order.
GET /v1/profiles/:id/blocks
<script setup lang="ts">
const listBlocksResponse = {
success: true,
data: {
items: [
{
id: 101,
type: "link",
label: "My GitHub",
content: "https://github.com/johndoe",
icon: "i-simple-icons-github",
settings: null,
order: 0,
isVisible: true
}
]
}
};
</script>
<template>
<pre>{{ JSON.stringify(listBlocksResponse, null, 2) }}</pre>
</template>
Add Block
Add a new block to a profile. The block is added at the end of the profile.
POST /v1/profiles/:id/blocks
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Block type, see Block Types |
label | string | No | Block label/title |
content | string | No | Block content (URL for links, text for others) |
icon | string | No | Icon name, for example i-simple-icons-github |
settings | object | No | Additional settings |
isVisible | boolean | No | Show the block on the profile. Default true |
Example Request
curl -X POST "https://linked.is/api/v1/profiles/15/blocks" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "link",
"label": "My GitHub",
"content": "https://github.com/johndoe",
"icon": "i-simple-icons-github"
}'
<script setup lang="ts">
const addBlock = async () => {
const response = await fetch("https://linked.is/api/v1/profiles/15/blocks", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "link",
label: "My GitHub",
content: "https://github.com/johndoe",
icon: "i-simple-icons-github"
}),
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="addBlock">Add Block</button>
</div>
</template>
Example Response
<script setup lang="ts">
const addBlockResponse = {
success: true,
data: {
id: 101,
type: "link",
label: "My GitHub",
content: "https://github.com/johndoe",
icon: "i-simple-icons-github",
settings: null,
order: 0,
isVisible: true
}
};
</script>
<template>
<pre>{{ JSON.stringify(addBlockResponse, null, 2) }}</pre>
</template>
Update Block
Update a block. Send only the fields you want to change. The response is the updated block.
PATCH /v1/profiles/:id/blocks/:blockId
| Parameter | Type | Description |
|---|---|---|
type | string | Block type |
label | string | null | Block label/title. null clears it |
content | string | null | Block content. null clears it |
icon | string | null | Icon name. null clears it |
settings | object | null | Additional settings. null clears them |
order | number | Position on the profile. To move several blocks, use Reorder Blocks |
isVisible | boolean | Show or hide the block |
curl -X PATCH "https://linked.is/api/v1/profiles/15/blocks/101" \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "GitHub",
"isVisible": false
}'
Delete Block
Delete a block permanently.
DELETE /v1/profiles/:id/blocks/:blockId
curl -X DELETE "https://linked.is/api/v1/profiles/15/blocks/101" \
-H "Authorization: Bearer YOUR_API_TOKEN"
<script setup lang="ts">
const deleteBlockResponse = {
success: true,
data: {
deleted: true
}
};
</script>
<template>
<pre>{{ JSON.stringify(deleteBlockResponse, null, 2) }}</pre>
</template>
Reorder Blocks
Set the display order of a profile's blocks.
PUT /v1/profiles/:id/blocks/reorder
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
blocks | array | Yes | Every block of the profile as { id, order } (1–500 items). id is a positive integer and order an integer. Lower order is shown first |
Each block of the profile must appear exactly once, otherwise the request fails with INVALID_BLOCKS. Positions are saved as 0, 1, 2… following the order values you sent.
<script setup lang="ts">
const reorderBlocks = async () => {
const response = await fetch("https://linked.is/api/v1/profiles/15/blocks/reorder", {
method: "PUT",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
blocks: [
{ id: 12, order: 0 },
{ id: 15, order: 1 },
{ id: 13, order: 2 }
]
}),
});
const data = await response.json();
console.log(data);
return data;
};
</script>
<template>
<div>
<button @click="reorderBlocks">Reorder Blocks</button>
</div>
</template>
Example Response
<script setup lang="ts">
const reorderResponse = {
success: true,
data: {
items: [
{ id: 12, order: 0 },
{ id: 15, order: 1 },
{ id: 13, order: 2 }
]
}
};
</script>
<template>
<pre>{{ JSON.stringify(reorderResponse, null, 2) }}</pre>
</template>
Block Types
The API accepts any non-empty type, but only these types are shown on a profile:
| Type | Description |
|---|---|
link | Clickable link button. content is the URL, label the button text |
heading | Text heading. content is the text |
paragraph | Text paragraph. content is the text |
avatar | Round image. content is the image URL |
image | Image display. content is the image URL, label the alt text |
socials | Social media icons. content is a JSON array of { "label", "url", "icon" } |
divider | Visual separator |
spacer | Empty space. Height in pixels in settings.height (default 20) |
email-collector | Email signup form. content is the placeholder, label the button text |
phone-collector | Phone number form. content is the placeholder, label the button text |
contact-form | Contact form with name, email and message. label is the title |
countdown | Countdown timer. content is the target date, label the title |
faq | Questions and answers. content is a JSON array of { "question", "answer" } |
youtube | YouTube video embed. content is the video URL |
spotify | Spotify embed. content is the Spotify URL |
soundcloud | SoundCloud embed. content is the track URL |
vimeo | Vimeo video embed. content is the video URL |
twitch | Twitch embed. content is the channel or video URL |
tiktok | TikTok embed. content is the video URL |
twitter | X (Twitter) post embed. content is the post URL |
instagram | Instagram post embed. content is the post URL |
Error Codes
| Code | Description |
|---|---|
INVALID_REQUEST | The profile ID in the path is not a number |
PROFILE_NOT_FOUND | The profile doesn't exist or you don't have access to it |
SLUG_TAKEN | The slug is already used by a profile or a short link |
SLUG_RESERVED | The slug is a reserved path (for example dashboard or api) |
BLOCK_NOT_FOUND | The block doesn't exist on this profile |
INVALID_BLOCKS | A reorder request didn't list every block exactly once |
CREATE_FAILED | The profile or block couldn't be created |
UPDATE_FAILED | The profile or block couldn't be updated |
An invalid body, for example a missing displayName or a slug with other characters, fails with a 400 validation error. See Errors.