curl --request POST \
--url https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Team-SID: <api-key>' \
--data '
{
"linkedin_account_sid": "<string>",
"text": "<string>",
"images": [
{
"file_base64": "<string>",
"file_byte_size": 2,
"file_name": "<string>",
"file_type": "<string>",
"alt_text": "<string>"
}
],
"scheduled_at": "<string>",
"mentions": [
{
"profile_id": "<string>",
"start": 1,
"length": 2
}
],
"brand_partnership": true,
"author_organization_id": "<string>",
"group_id": "<string>"
}
'import requests
url = "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post"
payload = {
"linkedin_account_sid": "<string>",
"text": "<string>",
"images": [
{
"file_base64": "<string>",
"file_byte_size": 2,
"file_name": "<string>",
"file_type": "<string>",
"alt_text": "<string>"
}
],
"scheduled_at": "<string>",
"mentions": [
{
"profile_id": "<string>",
"start": 1,
"length": 2
}
],
"brand_partnership": True,
"author_organization_id": "<string>",
"group_id": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Team-SID": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'Team-SID': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
linkedin_account_sid: '<string>',
text: '<string>',
images: [
{
file_base64: '<string>',
file_byte_size: 2,
file_name: '<string>',
file_type: '<string>',
alt_text: '<string>'
}
],
scheduled_at: '<string>',
mentions: [{profile_id: '<string>', start: 1, length: 2}],
brand_partnership: true,
author_organization_id: '<string>',
group_id: '<string>'
})
};
fetch('https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'linkedin_account_sid' => '<string>',
'text' => '<string>',
'images' => [
[
'file_base64' => '<string>',
'file_byte_size' => 2,
'file_name' => '<string>',
'file_type' => '<string>',
'alt_text' => '<string>'
]
],
'scheduled_at' => '<string>',
'mentions' => [
[
'profile_id' => '<string>',
'start' => 1,
'length' => 2
]
],
'brand_partnership' => true,
'author_organization_id' => '<string>',
'group_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Team-SID: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post"
payload := strings.NewReader("{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Team-SID", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post")
.header("Authorization", "Bearer <token>")
.header("Team-SID", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Team-SID"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"operation": "action",
"action": "<string>",
"item": "<unknown>",
"result": {
"activity_urn": "<string>",
"post_urn": "<string>",
"url": "<string>",
"created_at": "<string>",
"text": "<string>",
"activity_log": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}Create LinkedIn post
Publish ONE feed post - as the member, AS a company page it administers (author_organization_id), or INTO a group (group_id) - now or scheduled (scheduled_at) - wire create-post. Public; retract with delete_linkedin_post, or delete a scheduled draft with delete_linkedin_scheduled_post. Identity-bound: linkedin_account_sid REQUIRED, spends the posting bucket (20/day in series of 3 at a 1200 s pause; free plan 4), saturation returns 429. text is REQUIRED as a key but may be an EMPTY string when media is attached; no text, images or video at all is a 422. Media: up to 20 images XOR one video, base64 only, 35 MB decoded total. Body and alt text publish byte for byte, blank lines included. mentions makes spans of text clickable profile links (positions in UTF-16 code units); brand_partnership adds the “Brand partnership” flag; group_id and visibility are mutually exclusive. Nothing is stored here: the response carries the published post (urn, url, time, the body LinkedIn kept) plus the activity-log row.
Contract:
- MCP tool
create_linkedin_post, registry packagemcp.linkedin/linkedin_posting, mountlinkedin.content. - Operation
action, response envelopeaction. - Flags: dangerous: the MCP layer gates it behind a preview/commit token, and the effect cannot be undone through this API.
curl --request POST \
--url https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--header 'Team-SID: <api-key>' \
--data '
{
"linkedin_account_sid": "<string>",
"text": "<string>",
"images": [
{
"file_base64": "<string>",
"file_byte_size": 2,
"file_name": "<string>",
"file_type": "<string>",
"alt_text": "<string>"
}
],
"scheduled_at": "<string>",
"mentions": [
{
"profile_id": "<string>",
"start": 1,
"length": 2
}
],
"brand_partnership": true,
"author_organization_id": "<string>",
"group_id": "<string>"
}
'import requests
url = "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post"
payload = {
"linkedin_account_sid": "<string>",
"text": "<string>",
"images": [
{
"file_base64": "<string>",
"file_byte_size": 2,
"file_name": "<string>",
"file_type": "<string>",
"alt_text": "<string>"
}
],
"scheduled_at": "<string>",
"mentions": [
{
"profile_id": "<string>",
"start": 1,
"length": 2
}
],
"brand_partnership": True,
"author_organization_id": "<string>",
"group_id": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Team-SID": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {
Authorization: 'Bearer <token>',
'Team-SID': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
linkedin_account_sid: '<string>',
text: '<string>',
images: [
{
file_base64: '<string>',
file_byte_size: 2,
file_name: '<string>',
file_type: '<string>',
alt_text: '<string>'
}
],
scheduled_at: '<string>',
mentions: [{profile_id: '<string>', start: 1, length: 2}],
brand_partnership: true,
author_organization_id: '<string>',
group_id: '<string>'
})
};
fetch('https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'linkedin_account_sid' => '<string>',
'text' => '<string>',
'images' => [
[
'file_base64' => '<string>',
'file_byte_size' => 2,
'file_name' => '<string>',
'file_type' => '<string>',
'alt_text' => '<string>'
]
],
'scheduled_at' => '<string>',
'mentions' => [
[
'profile_id' => '<string>',
'start' => 1,
'length' => 2
]
],
'brand_partnership' => true,
'author_organization_id' => '<string>',
'group_id' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json",
"Team-SID: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post"
payload := strings.NewReader("{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Team-SID", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post")
.header("Authorization", "Bearer <token>")
.header("Team-SID", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.gtm-api.com/linkedin/v4/api/linkedin-posting/create-post")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Team-SID"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"linkedin_account_sid\": \"<string>\",\n \"text\": \"<string>\",\n \"images\": [\n {\n \"file_base64\": \"<string>\",\n \"file_byte_size\": 2,\n \"file_name\": \"<string>\",\n \"file_type\": \"<string>\",\n \"alt_text\": \"<string>\"\n }\n ],\n \"scheduled_at\": \"<string>\",\n \"mentions\": [\n {\n \"profile_id\": \"<string>\",\n \"start\": 1,\n \"length\": 2\n }\n ],\n \"brand_partnership\": true,\n \"author_organization_id\": \"<string>\",\n \"group_id\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"operation": "action",
"action": "<string>",
"item": "<unknown>",
"result": {
"activity_urn": "<string>",
"post_urn": "<string>",
"url": "<string>",
"created_at": "<string>",
"text": "<string>",
"activity_log": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}{
"success": false,
"error": {
"code": "validation_failed",
"message": "<string>",
"recoverable": true,
"suggestion": "<string>",
"field_errors": {},
"blockers": [
{
"type": "<string>",
"severity": "hard",
"description": "<string>",
"entity_sid": "<string>",
"resolution": "<string>",
"resolution_hint": "<string>",
"count": 123
}
],
"context": {}
},
"meta": {
"trace_id": "<string>",
"span_id": "<string>",
"timestamp": "<string>",
"duration_ms": 1,
"team_sid": "<string>",
"actor_type": "<string>"
}
}Authorizations
Access token issued by gtm.service.id. Its access_identity claim carries team_sid, actor_sid and actor_type, and that team scope is authoritative.
Team scope for tokens that do not carry one. Ignored when the token already names a team.
Body
Request body of create_linkedin_post.
LinkedIn account sid (ln_ac_…), the authoring account. Identity-bound: REQUIRED, posts publish AS this account.
18^ln_ac_Post body, always present. Empty is legal ONLY alongside an image. 3000 is LinkedIn's own cap, refused here rather than burning a posting slot.
3000Up to 20 images (the node's own cap; array order = carousel order). Mutually exclusive with video. The decoded bytes across ALL media must stay under 35 MB in total; over that is a 422, because a bigger body is refused by the node's JSON parser as a bare 413 with no response envelope. For 2+ images the backend supplies the account's own member id to the wire itself - no profile_id field exists here. Uploads are sequential node-side, so many large images make a SLOW synchronous call - the post can publish even if your client times out first, so do not blind-retry a timeout (a duplicate public post is the cost).
20Show child attributes
Show child attributes
ANYONE (the node default) or CONNECTIONS_ONLY. Omit to let the node apply its own default. Mutually exclusive with group_id.
ANYONE, CONNECTIONS_ONLY Who may comment: ALL (the node default), CONNECTIONS_ONLY, or NONE to disable comments. Works on group posts too.
ALL, CONNECTIONS_ONLY, NONE ONE video, image-shaped minus alt_text. Mutually exclusive with images (LinkedIn does not mix them in a share - 422 here). Shares the 35 MB media budget. The upload is a single large PUT through the browser and is SLOW: the synchronous call can outlive your client timeout while the post still publishes - do not blind-retry.
Show child attributes
Show child attributes
Schedule the post instead of publishing now: ISO 8601 with timezone, must be in the future (a strictly-past time is a 422; within a minute of now is passed through for LinkedIn to judge). A SCHEDULED share legitimately answers with null activity_urn/url - post_urn is the handle. Read and clean the queue with get_linkedin_scheduled_posts / delete_linkedin_scheduled_post. Scheduling is LinkedIn-side: the draft lives in ITS queue, nothing is stored here.
Profile mentions as READY POSITIONS over text (2026-08-21) - unlike create_linkedin_comment's {profile_id, name} search pairs. Require non-blank text.
Show child attributes
Show child attributes
true adds LinkedIn's "Brand partnership" label (paid endorsement). false and absent both send nothing - the wire's own shape.
Post AS a company page the account administers: the bare numeric company id (same form get_linkedin_company_posts takes). Omit to post as the member.
^\d+$Post INTO a group: the bare numeric group id. Mutually exclusive with visibility - a group post sets container visibility, not member-feed visibility.
^\d+$Response
action success envelope.