Create Instagram Post With User Tags
User tags let you tag Instagram accounts at a specific point on an image. They are attached per image, on the asset's metadata.userTags field - not on the Instagram post metadata - so the same approach works for Instagram posts and stories.
Each tag is a UserTagInput with a handle and an x/y position. The coordinates are normalized decimal floats between 0.0 and 1.0, representing the percentage distance from the left edge (x) and the top edge (y) of the image. So { x: 0.5, y: 0.5 } is the center of the image, and { x: 0.0, y: 0.0 } is the top-left corner.
Common pitfall: pass
xandyas numbers, not strings, and as normalized0.0-1.0values - not raw pixel coordinates. Sending pixel values (e.g.x: 540on a 1080px-wide image) or strings causes Instagram's Graph API to reject the post with an error likeFailed to create media container for instagram: (#100) Param user_tags[0]['x'] must be a number less than or equal to 1. To convert a pixel position, divide by the image's dimension:x = pixelX / imageWidth,y = pixelY / imageHeight.
Note that metadata.altText is required whenever you provide image metadata, so it's included alongside userTags below.
mutation CreateInstagramPostWithUserTags {
createPost(
input: {
text: "Your post caption goes here"
channelId: "your_instagram_channel_id"
schedulingType: automatic
mode: addToQueue
assets: [
{
image: {
url: "https://images.unsplash.com/photo-1742850541164-8eb59ecb3282?q=80&w=3388&auto=format&fit=crop"
metadata: {
altText: "Describe the image for accessibility"
userTags: [
{ handle: "first_account_handle", x: 0.5, y: 0.7 }
{ handle: "second_account_handle", x: 0.5, y: 0.95 }
]
}
}
}
]
metadata: { instagram: { type: post, shouldShareToFeed: true } }
}
) {
... on PostActionSuccess {
post {
id
text
assets {
id
mimeType
}
}
}
... on MutationError {
message
}
}
}
curl -X POST 'https://api.buffer.com' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-d '{"query": "mutation CreateInstagramPostWithUserTags {\n createPost(\n input: {\n text: \"Your post caption goes here\"\n channelId: \"your_instagram_channel_id\"\n schedulingType: automatic\n mode: addToQueue\n assets: [\n {\n image: {\n url: \"https://images.unsplash.com/photo-1742850541164-8eb59ecb3282?q=80&w=3388&auto=format&fit=crop\"\n metadata: {\n altText: \"Describe the image for accessibility\"\n userTags: [\n { handle: \"first_account_handle\", x: 0.5, y: 0.7 }\n { handle: \"second_account_handle\", x: 0.5, y: 0.95 }\n ]\n }\n }\n }\n ]\n metadata: { instagram: { type: post, shouldShareToFeed: true } }\n }\n ) {\n ... on PostActionSuccess {\n post {\n id\n text\n assets {\n id\n mimeType\n }\n }\n }\n ... on MutationError {\n message\n }\n }\n}"}'
async function createInstagramPostWithUserTags() {
const response = await fetch('https://api.buffer.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY',
},
body: JSON.stringify({
query: `
mutation CreateInstagramPostWithUserTags {
createPost(
input: {
text: "Your post caption goes here"
channelId: "your_instagram_channel_id"
schedulingType: automatic
mode: addToQueue
assets: [
{
image: {
url: "https://images.unsplash.com/photo-1742850541164-8eb59ecb3282?q=80&w=3388&auto=format&fit=crop"
metadata: {
altText: "Describe the image for accessibility"
userTags: [
{ handle: "first_account_handle", x: 0.5, y: 0.7 }
{ handle: "second_account_handle", x: 0.5, y: 0.95 }
]
}
}
}
]
metadata: { instagram: { type: post, shouldShareToFeed: true } }
}
) {
... on PostActionSuccess {
post {
id
text
assets {
id
mimeType
}
}
}
... on MutationError {
message
}
}
}
`,
}),
});
const data = await response.json();
console.log(JSON.stringify(data, null, 2));
}
createInstagramPostWithUserTags();
import requests
query = """
mutation CreateInstagramPostWithUserTags {
createPost(
input: {
text: "Your post caption goes here"
channelId: "your_instagram_channel_id"
schedulingType: automatic
mode: addToQueue
assets: [
{
image: {
url: "https://images.unsplash.com/photo-1742850541164-8eb59ecb3282?q=80&w=3388&auto=format&fit=crop"
metadata: {
altText: "Describe the image for accessibility"
userTags: [
{ handle: "first_account_handle", x: 0.5, y: 0.7 }
{ handle: "second_account_handle", x: 0.5, y: 0.95 }
]
}
}
}
]
metadata: { instagram: { type: post, shouldShareToFeed: true } }
}
) {
... on PostActionSuccess {
post {
id
text
assets {
id
mimeType
}
}
}
... on MutationError {
message
}
}
}
"""
response = requests.post(
"https://api.buffer.com",
headers={
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_API_KEY",
},
json={
"query": query,
},
)
data = response.json()
print(data)
<?php
$query = '
mutation CreateInstagramPostWithUserTags {
createPost(
input: {
text: "Your post caption goes here"
channelId: "your_instagram_channel_id"
schedulingType: automatic
mode: addToQueue
assets: [
{
image: {
url: "https://images.unsplash.com/photo-1742850541164-8eb59ecb3282?q=80&w=3388&auto=format&fit=crop"
metadata: {
altText: "Describe the image for accessibility"
userTags: [
{ handle: "first_account_handle", x: 0.5, y: 0.7 }
{ handle: "second_account_handle", x: 0.5, y: 0.95 }
]
}
}
}
]
metadata: { instagram: { type: post, shouldShareToFeed: true } }
}
) {
... on PostActionSuccess {
post {
id
text
assets {
id
mimeType
}
}
}
... on MutationError {
message
}
}
}
';
$payload = [
'query' => $query,
];
$ch = curl_init('https://api.buffer.com');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer YOUR_API_KEY',
],
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
print_r($data);
The x/y validation rules are enforced by Instagram, so the same constraints apply whether you tag one account or many. See the UserTag reference for the field definitions, and Create an Image Post for the basics of attaching images.