Skip to content
API Reference

Profiles API

API endpoints for managing bio profiles

Create and manage bio profiles programmatically.

Create Profile

Create a new bio profile.

POST /v1/profiles

Query Parameters

ParameterTypeRequiredDescription
teamIdnumberNoCreate the profile in this team instead of your personal workspace. See Team Workspaces

Request Body

ParameterTypeRequiredDescription
displayNamestringYesDisplay name shown on the profile, 1–100 characters
slugstringYesURL 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
biostringNoShort biography text, up to 500 characters
avatarstringNoAvatar image URL, up to 2048 characters
themestringNoProfile 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

ParameterTypeDescription
displayNamestringDisplay name, up to 100 characters
slugstringURL slug, 3–50 letters, numbers, hyphens or underscores. Must be free, as in Create Profile
biostringBiography text, up to 500 characters
avatarstringAvatar URL, up to 2048 characters
themestringProfile 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

ParameterTypeRequiredDescription
teamIdnumberNoList 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:

FieldTypeDescription
idnumberBlock ID
typestringBlock type, see Block Types
labelstring | nullBlock label/title
contentstring | nullBlock content, depending on the type
iconstring | nullIcon name
settingsobject | nullAdditional settings
ordernumberPosition on the profile, lower is shown first
isVisiblebooleanWhether 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

ParameterTypeRequiredDescription
typestringYesBlock type, see Block Types
labelstringNoBlock label/title
contentstringNoBlock content (URL for links, text for others)
iconstringNoIcon name, for example i-simple-icons-github
settingsobjectNoAdditional settings
isVisiblebooleanNoShow 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
ParameterTypeDescription
typestringBlock type
labelstring | nullBlock label/title. null clears it
contentstring | nullBlock content. null clears it
iconstring | nullIcon name. null clears it
settingsobject | nullAdditional settings. null clears them
ordernumberPosition on the profile. To move several blocks, use Reorder Blocks
isVisiblebooleanShow 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

ParameterTypeRequiredDescription
blocksarrayYesEvery 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:

TypeDescription
linkClickable link button. content is the URL, label the button text
headingText heading. content is the text
paragraphText paragraph. content is the text
avatarRound image. content is the image URL
imageImage display. content is the image URL, label the alt text
socialsSocial media icons. content is a JSON array of { "label", "url", "icon" }
dividerVisual separator
spacerEmpty space. Height in pixels in settings.height (default 20)
email-collectorEmail signup form. content is the placeholder, label the button text
phone-collectorPhone number form. content is the placeholder, label the button text
contact-formContact form with name, email and message. label is the title
countdownCountdown timer. content is the target date, label the title
faqQuestions and answers. content is a JSON array of { "question", "answer" }
youtubeYouTube video embed. content is the video URL
spotifySpotify embed. content is the Spotify URL
soundcloudSoundCloud embed. content is the track URL
vimeoVimeo video embed. content is the video URL
twitchTwitch embed. content is the channel or video URL
tiktokTikTok embed. content is the video URL
twitterX (Twitter) post embed. content is the post URL
instagramInstagram post embed. content is the post URL

Error Codes

CodeDescription
INVALID_REQUESTThe profile ID in the path is not a number
PROFILE_NOT_FOUNDThe profile doesn't exist or you don't have access to it
SLUG_TAKENThe slug is already used by a profile or a short link
SLUG_RESERVEDThe slug is a reserved path (for example dashboard or api)
BLOCK_NOT_FOUNDThe block doesn't exist on this profile
INVALID_BLOCKSA reorder request didn't list every block exactly once
CREATE_FAILEDThe profile or block couldn't be created
UPDATE_FAILEDThe 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.