FN.Pulseby FuturizeNow
On this page

Pulse API

Pulse lets the members of your product connect their own LinkedIn, Instagram, Facebook and YouTube accounts and post to them. You call the API from your server. Your member never needs a Pulse account.

Words we use. A partner is you. A member is one of your users. A connection is one social account a member linked.

All examples use https://YOUR-PULSE-URL as the base address and $PULSE_KEY as your API key. Replace both. Every API path below starts with /v1/partner. Bodies are JSON, and every time is an ISO 8601 time in UTC.

Quickstart

Five calls, from nothing to a scheduled post.

export PULSE_KEY="fnk_your_key_here"
export PULSE_URL="https://YOUR-PULSE-URL"

1. Create a member

Request
curl -X POST "$PULSE_URL/v1/partner/members" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "user_8412", "name": "Amara Okafor"}'
Response 201
{
  "id": "m_4f9c2a71e0b35d8c6a1f9e24",
  "external_id": "user_8412",
  "name": "Amara Okafor",
  "created_at": "2026-10-07T09:30:00.000Z",
  "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] }
}

2. Create a connect session

Request
curl -X POST "$PULSE_URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/connect-session" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"return_url": "https://app.example.com/settings/social"}'
Response 201
{
  "url": "https://YOUR-PULSE-URL/connect/eyJwYXJ0bmVyIjoiYWNtZSJ9.q3TzV8",
  "expires_at": "2026-10-07T10:00:00.000Z"
}

3. Open the url

Send your member to the url: a link, a redirect, a popup or an iframe (see The connect window). They sign in to each network on its own page. There is nothing to call here. The link works for 30 minutes by default.

4. List connections

Request
curl "$PULSE_URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/connections" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "connections": [
    { "id": "c_66f1a2b3c4d5e6f708192a3b", "network": "linkedin", "label": "Amara Okafor", "status": "active", "connected_at": "2026-10-07T09:41:12.000Z" }
  ]
}

5. Create a post

Request
curl -X POST "$PULSE_URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/posts" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "We just opened our new studio. Come and say hello.", "networks": ["linkedin"]}'
Response 201
{
  "id": "5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3",
  "status": "scheduled",
  "scheduled_at": "2026-10-07T09:45:30.000Z",
  "text": "We just opened our new studio. Come and say hello.",
  "networks": [
    { "network": "linkedin", "connection_id": "c_66f1a2b3c4d5e6f708192a3b", "status": "scheduled", "external_url": null, "error": null }
  ]
}

With no scheduled_at, the post goes out about two minutes from now. The member had one LinkedIn account, so it was used without being named (see Posts).

Authentication

Send your key on every request in the Authorization header:

Authorization: Bearer fnk_your_key_here

Keep the key on your server. Do not put it in a browser, a mobile app or a public repository. If it leaks, email us and we will replace it.

Your key only works on paths that start with /v1/partner (and with the connect window links you create). Anywhere else it returns 403 forbidden_scope. It only ever sees your own members.

A missing, wrong or switched-off key returns 401 unauthorized. An id that belongs to someone else returns 404 not_found, the same as an id that does not exist. A path that does not exist under /v1/partner returns 404 not_found with the message "No such endpoint."

Ids you get back: a member id looks like m_ followed by 24 characters, a connection id starts with c_, and a post id is a UUID. Treat all of them as opaque text.

Members

A member is one of your users. You choose the external_id: use your own user id, 1 to 128 characters. Pulse gives back its own id for all later calls.

Create a member

POST /v1/partner/members

FieldTypeNotes
external_idstringRequired. 1 to 128 characters, no control characters. Your own id for this member.
namestringOptional. Up to 120 characters.
emailstringOptional. Must look like an email address. It is stored and returned, never sent to the networks.

This call is safe to repeat. If a member with the same external_id already exists, you get 200 and the existing member, and name and email in the repeat call are ignored (use update to change them). A new member returns 201. A member always has limits: the numbers that apply to them right now (see Limits). email is only present if you set one. name is null if you did not.

Request
curl -X POST "https://YOUR-PULSE-URL/v1/partner/members" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_id": "user_8412", "name": "Amara Okafor", "email": "amara@example.com"}'
Response 201 (200 if it already existed)
{
  "id": "m_4f9c2a71e0b35d8c6a1f9e24",
  "external_id": "user_8412",
  "name": "Amara Okafor",
  "email": "amara@example.com",
  "created_at": "2026-10-07T09:30:00.000Z",
  "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] }
}

List members

GET /v1/partner/members?limit=50&cursor=. limit is a whole number from 1 to 100 (default 50). Members come oldest first. Send the next_cursor from one answer as cursor in the next call. When next_cursor is null, you have everything. A cursor that is not one of your member ids returns 422 validation.

Request
curl "https://YOUR-PULSE-URL/v1/partner/members?limit=2" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "members": [
    { "id": "m_4f9c2a71e0b35d8c6a1f9e24", "external_id": "user_8412", "name": "Amara Okafor", "created_at": "2026-10-07T09:30:00.000Z", "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] } },
    { "id": "m_77b0e5c4a91d2f3068b5c7de", "external_id": "user_9100", "name": "Tomas Reyes", "created_at": "2026-10-07T09:52:40.000Z", "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] } }
  ],
  "next_cursor": "m_77b0e5c4a91d2f3068b5c7de"
}

Get a member

GET /v1/partner/members/:id. Returns the member and how many accounts they have connected.

Request
curl "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "id": "m_4f9c2a71e0b35d8c6a1f9e24",
  "external_id": "user_8412",
  "name": "Amara Okafor",
  "created_at": "2026-10-07T09:30:00.000Z",
  "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] },
  "connections": 1
}

Update a member

PATCH /v1/partner/members/:id. Send only the fields you want to change: name, email. Send null to clear one. external_id cannot be changed.

Request
curl -X PATCH "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Amara Okafor-Smith"}'
Response 200
{
  "id": "m_4f9c2a71e0b35d8c6a1f9e24",
  "external_id": "user_8412",
  "name": "Amara Okafor-Smith",
  "created_at": "2026-10-07T09:30:00.000Z",
  "limits": { "max_accounts": 10, "max_per_network": 3, "networks": ["linkedin", "instagram", "facebook", "youtube"] }
}

Delete a member

DELETE /v1/partner/members/:id?confirm=true. You cannot undo this. Without exactly confirm=true you get 400 confirm_required and nothing happens. With it, Pulse does three things in this order:

  1. Cancels every post of the member that is still scheduled.
  2. Disconnects every account the member linked.
  3. Removes all of the member's posts and the member.

If a scheduled post cannot be cancelled, or an account cannot be disconnected, the call stops with 502 provider_unavailable and nothing is removed from Pulse: the member and their posts are still there. Some earlier steps may already have happened, so just call it again. If a post is being prepared at that moment you get 409 conflict: try again in a few seconds.

Request
curl -X DELETE "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24?confirm=true" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 204 (no body)
(empty)
Response 400 without confirm=true
{ "error": { "code": "confirm_required", "message": "Deleting a member can't be undone. Add ?confirm=true to confirm.", "field": "confirm" } }
Response 502 (an account could not be disconnected)
{ "error": { "code": "provider_unavailable", "message": "The posting service isn't answering right now. Please try again in a minute." } }

The connect window

The connect window is a page we host. Your member sees their connected accounts at the top and one button per network below. They click a network, sign in on that network's own page, and come back. All buttons stay active after a connection, so a member can add more accounts until a limit is reached. A network at its limit has its button switched off with the reason next to it. A member can also disconnect an account in the window: it asks "Yes, disconnect" or "Keep it" first. "Powered by futurizenow.co" appears in small type under the window. LinkedIn members choose between their personal profile and the company pages they manage inside the connect window; each page counts as a connected account toward the limits.

Create a connect session

POST /v1/partner/members/:id/connect-session

FieldTypeNotes
networksarrayOptional. A non-empty list of linkedin, instagram, facebook, youtube. Leave out for every network the member is allowed. A network that is not switched on for this member returns 422 network_not_enabled. An unknown name returns 422 validation.
return_urlhttps urlOptional. Adds a "Done" button that sends the member back to you. It must be a public https link (see the link rules under Posts), otherwise 422 validation.
expires_insecondsOptional. A whole number from 60 to 3600. Default 1800 (30 minutes).

If every network in the session is already at its limit, nothing could be connected, so you get 409 limit_reached and no link. If at least one network can still take an account, you get the link.

Request
curl -X POST "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/connect-session" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"networks": ["linkedin", "instagram"], "return_url": "https://app.example.com/settings/social", "expires_in": 900}'
Response 201
{
  "url": "https://YOUR-PULSE-URL/connect/eyJwYXJ0bmVyIjoiYWNtZSJ9.q3TzV8",
  "expires_at": "2026-10-07T09:45:00.000Z"
}
Response 409 (every requested network is full)
{ "error": { "code": "limit_reached", "message": "You've reached the limit of 3 accounts on LinkedIn." } }

The link works for one member only, and only for the networks you named. Make a new session each time you want to show the window.

What the member goes through

You never call these addresses yourself. They are here so you know what happens.

  1. The member opens /connect/:token. This is the window.
  2. They press a network button. The window sends POST /connect/:token/start, and the answer is a 303 redirect to that network's sign-in page.
  3. After signing in, the network sends them to /connect/:token/done. Pulse then checks the member's limits once more and keeps the new account only if there is room. If there is not (for example two windows were open at once), the extra account is disconnected again and the window says "That account was not kept because the limit is reached."
  4. Pulse sends the member back to the window, which shows the result.

The same token covers every step, so the link must still be valid when the member comes back from the network.

Open it as a full page

window.location.href = url;

Open it as a popup

window.open(url, "pulse-connect", "width=480,height=720");

Open it in an iframe

<iframe src="https://YOUR-PULSE-URL/connect/TOKEN"
        title="Connect your accounts"
        style="width:100%;max-width:480px;height:720px;border:0"></iframe>

The window works as a page, a popup or an iframe. The network sign-in happens inside the same frame. The connect window can be framed by any site. Every other page and every API response cannot be framed.

Know when something changed: postMessage

After a member connects or disconnects an account, the window sends a message to your page (window.parent for an iframe, window.opener for a popup). It carries no secrets. It is sent only after a change, never when the window is first opened and never after a cancelled attempt. status is connected or disconnected.

{
  "type": "pulse.connection",
  "member_id": "m_4f9c2a71e0b35d8c6a1f9e24",
  "network": "linkedin",
  "status": "connected"
}

Listen for it like this, then confirm with GET /connections before you trust it:

window.addEventListener("message", (event) => {
  if (event.origin !== "https://YOUR-PULSE-URL") return;
  if (event.data && event.data.type === "pulse.connection") {
    // event.data.member_id, event.data.network, event.data.status
    refreshConnections(event.data.member_id);
  }
});
Always check event.origin. The message is sent to any origin, so do not act on a message from a page you do not know.

return_url

If you passed return_url, the window shows a "Done" button that sends the member there with a status added to the address (any status already in your address is replaced):

StatusMeaning
connectedThe member connected an account while this window was open.
cancelledAnything else: the member left without connecting an account, or the new account was not kept because of a limit.
https://app.example.com/settings/social?status=connected

There is no other status. The status is a hint for your screen. The truth is in the list of connections.

Links that do not work

HTTPWhat the member seesWhen
410"This link has expired"The link is past its expires_at. This also applies to the sign-in steps.
400"This link doesn't work"The link was changed or is not a link we made. The page does not say which.
410"This link doesn't work"The member was deleted, or all of your keys were switched off.
429"Too many tries"More than 120 requests an hour for one link. A Retry-After header says how long to wait.
502"We couldn't check your accounts just now."The posting service did not answer. The buttons are hidden until it does.

These are plain pages, not JSON. The window does not count against your 60 requests a minute (see Rate limits).

Connections

List a member's connections

GET /v1/partner/members/:id/connections. status is active, or needs_reconnect when the network has asked the member to sign in again. Open a new connect session for them. label is the account name when the network gives one, otherwise the network's name.

Request
curl "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/connections" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "connections": [
    { "id": "c_66f1a2b3c4d5e6f708192a3b", "network": "linkedin", "label": "Amara Okafor", "status": "active", "connected_at": "2026-10-07T09:41:12.000Z" },
    { "id": "c_9d3e7a50b1c2486f0a5e1d37", "network": "youtube", "label": "Okafor Studio", "status": "needs_reconnect", "connected_at": "2026-09-28T14:03:55.000Z" }
  ]
}

This call also checks the member's accounts against the limits. An account that appeared while no room was left is disconnected and does not show up. If the posting service does not answer, you get 502 provider_unavailable.

Disconnect one

DELETE /v1/partner/members/:id/connections/:cid. This disconnects the account at the posting service. It does not need confirm=true. A post that was scheduled for that account will not go out through another one. If the account cannot be disconnected, you get 502 provider_unavailable and the connection stays. An unknown connection id, or one that belongs to another member, returns 404 not_found.

Request
curl -X DELETE "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/connections/c_9d3e7a50b1c2486f0a5e1d37" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 204 (no body)
(empty)

Limits

Limits decide how many accounts a member can connect and to which networks. There are three levels. The first one that is set wins:

  1. Member. Set by you with the call below.
  2. Partner default. Set by us on your key. Ask us if you want different defaults for all your members.
  3. Global default. max_accounts 10 in total, max_per_network 3, and every network except X (linkedin, instagram, facebook, youtube). X is only available if we switch it on for you.

The numbers in force for a member are always in the limits field of the member, with all three fields filled in (the "effective" values).

A member's number can never go above your partner default. This is on purpose: you can lower a member, never raise one past what your key allows. If you send a higher number, the call is refused with 422 validation and the message "max_accounts can be at most 10 for your account. Ask us if you need more." (the number is your own default). It is not quietly reduced. The same goes for networks: a network outside your partner default is refused with 422 network_not_enabled. If you need more than your default, ask us to change the default on your key.

When a limit is reached, the button for that network in the connect window is switched off with the reason next to it, and a connect session for networks that are all full answers 409 limit_reached. Lowering a limit does not disconnect accounts the member already has. It only stops new ones from being kept. A member's own numbers can only lower your account's default, never raise it: if your default is lowered later, a member who was set higher is held to the new number. networks in a limits call must name at least one network (send null to go back to your default); an empty list returns 422 validation.

Set a member's limits

PUT /v1/partner/members/:id/limits. All fields are optional. A field you leave out stays as it is. Send null to clear a field and fall back to the next level.

FieldTypeNotes
max_accountsintegerAccounts across all networks. A whole number from 1 up to your partner default.
max_per_networkintegerAccounts on any one network. A whole number from 1 up to your partner default.
networksarrayNetworks this member may connect and post to. Each must be switched on for your account.
Request
curl -X PUT "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/limits" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_accounts": 4, "max_per_network": 1, "networks": ["linkedin", "youtube"]}'
Response 200 (the limits now in force for this member)
{
  "max_accounts": 4,
  "max_per_network": 1,
  "networks": ["linkedin", "youtube"]
}
Response 422 (above your partner default)
{ "error": { "code": "validation", "message": "max_accounts can be at most 10 for your account. Ask us if you need more.", "field": "max_accounts" } }
Response 422 (X is not switched on)
{ "error": { "code": "network_not_enabled", "message": "X is not switched on for your account.", "field": "networks" } }

To clear one field, send null:

Request
curl -X PUT "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/limits" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"max_per_network": null}'
Response 200 (max_per_network is back to your partner default, here the global 3)
{
  "max_accounts": 4,
  "max_per_network": 3,
  "networks": ["linkedin", "youtube"]
}

Posts

A post goes out through the accounts this member has connected, on each network you list. If the member has not connected an account on a network you list, the request is refused with a plain message. A network must be switched on for the member.

Create a post

POST /v1/partner/members/:id/posts

FieldTypeNotes
textstringRequired. The text for every network. Up to 20,000 characters, and each network has its own shorter limit (see the table below).
networksarrayRequired. Where to post, for example ["linkedin","facebook"]. Repeats are ignored. A network outside the member's effective networks returns 422 network_not_enabled.
connection_idsarrayOptional. Which account to use, when you need to say. Ids come from the list of connections. See "Which account is used" below.
textsobjectOptional. Different text for one network, for example {"linkedin": "..."}. Only networks that are in networks are allowed. Other networks use text.
media_urlsarrayOptional. Up to 10 links to images or video that we can fetch. See the link rules below.
youtube_titlestringThe video title. Up to 100 characters, no < or >.
scheduled_atISO 8601Optional. When to post, like 2026-11-01T09:00:00Z. Leave out to post in about 2 minutes. It cannot be in the past (a minute of slack is allowed) and cannot be more than a year ahead (366 days). Otherwise 422 validation.

Which account is used

Rules for media links

Every link in media_urls (and every return_url) must be a public https link. These are refused with 422 validation:

Request
curl -X POST "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/posts" \
  -H "Authorization: Bearer $PULSE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Our October workshop is open for booking.",
    "texts": { "linkedin": "Our October workshop is open for booking. Details and dates in the comments." },
    "media_urls": ["https://cdn.example.com/workshop.mp4"],
    "networks": ["linkedin", "youtube"],
    "connection_ids": ["c_66f1a2b3c4d5e6f708192a3b"],
    "youtube_title": "October workshop: what to expect",
    "scheduled_at": "2026-10-09T08:00:00Z"
  }'
Response 201
{
  "id": "5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3",
  "status": "scheduled",
  "scheduled_at": "2026-10-09T08:00:00.000Z",
  "text": "Our October workshop is open for booking.",
  "networks": [
    { "network": "linkedin", "connection_id": "c_66f1a2b3c4d5e6f708192a3b", "status": "scheduled", "external_url": null, "error": null },
    { "network": "youtube", "connection_id": "c_9d3e7a50b1c2486f0a5e1d37", "status": "scheduled", "external_url": null, "error": null }
  ]
}

In this example the member has one YouTube account, so it was used without being named.

Response 422 (a rule was broken)
{ "error": { "code": "validation", "message": "LinkedIn allows 3000 characters — this is 3412." } }
Response 422 (several accounts, none named)
{ "error": { "code": "ambiguous_account", "message": "This member has 2 LinkedIn accounts. Say which one with connection_ids.", "field": "networks" } }
Response 422 (nothing connected on a network)
{ "error": { "code": "not_connected", "message": "This member hasn't connected Instagram (or it needs reconnecting).", "field": "networks" } }

What a post looks like

FieldNotes
idThe post id (a UUID).
statusOne of scheduled, publishing, published, partial (some networks published, some failed) or failed.
scheduled_atWhen the post goes out. null if there is none.
textThe text you sent.
errorOnly present when something went wrong with the post as a whole. A plain sentence.
networks[].networkThe network.
networks[].connection_idThe account this network uses. null if none was fixed.
networks[].statusscheduled, publishing, published or failed. Each network has its own.
networks[].external_urlThe link to the live post, once published. Otherwise null.
networks[].errorA plain sentence when that network failed. Otherwise null.

Which accounts a member can connect

Members connect their own accounts, but each network decides which kinds of account an app may post to. Tell your members before they start, so the sign-in does not surprise them.

NetworkWhat the member needs
LinkedInTheir personal profile works. If they also manage a company page, they can pick it during sign-in when LinkedIn offers it.
InstagramA Business or Creator account. Instagram does not let any app post to a personal account. Switching is free in the Instagram app and takes a minute.
FacebookA Page they manage. Facebook does not let any app post to a personal profile or timeline. A member with no Page can create one for free.
YouTubeTheir own channel. Videos only.

If a member signs in with an account the network will not allow, that network's connection does not complete, or a later post to it comes back failed with a plain error.

Rules for each network

These are checked when you create the post. They count the text that network will get (texts if you gave one for it, otherwise text).

NetworkWhat is checked
LinkedInUp to 3,000 characters.
InstagramNeeds at least one image or video in media_urls. Up to 2,200 characters and no more than 30 hashtags.
FacebookUp to 63,206 characters.
YouTubeNeeds at least one link in media_urls, and it must be a video. Up to 5,000 characters. youtube_title is up to 100 characters and cannot contain < or >.
XNot offered unless we switch it on for you. A post that lists x otherwise returns 422 network_not_enabled.

If a rule is broken, the 422 validation message tells you which one in plain words. A network can still refuse a post later for its own reasons, such as the type of account or the video format. When that happens, that network's status becomes failed and its error says why.

List posts

GET /v1/partner/members/:id/posts. The 100 newest posts, newest first. There is no paging.

Request
curl "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/posts" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "posts": [
    {
      "id": "5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3",
      "status": "partial",
      "scheduled_at": "2026-10-09T08:00:00.000Z",
      "text": "Our October workshop is open for booking.",
      "networks": [
        { "network": "linkedin", "connection_id": "c_66f1a2b3c4d5e6f708192a3b", "status": "published", "external_url": "https://www.linkedin.com/feed/update/urn:li:share:7000000000000000000", "error": null },
        { "network": "youtube", "connection_id": "c_9d3e7a50b1c2486f0a5e1d37", "status": "failed", "external_url": null, "error": "YouTube did not accept the video. Check that the file is a supported video format." }
      ]
    }
  ]
}

Get one post

GET /v1/partner/members/:id/posts/:pid. Same shape as one item above.

Request
curl "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/posts/5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 200
{
  "id": "5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3",
  "status": "published",
  "scheduled_at": "2026-10-09T08:00:00.000Z",
  "text": "Our October workshop is open for booking.",
  "networks": [
    { "network": "linkedin", "connection_id": "c_66f1a2b3c4d5e6f708192a3b", "status": "published", "external_url": "https://www.linkedin.com/feed/update/urn:li:share:7000000000000000000", "error": null }
  ]
}

Cancel a post

DELETE /v1/partner/members/:id/posts/:pid. Cancels the post before it goes out, and removes it. Only a post whose status is scheduled can be cancelled. Any other post returns 409 conflict. If the post is being prepared at that moment you also get 409 conflict: try again in a few seconds. If the cancel cannot be passed on, you get 502 provider_unavailable and the post stays.

Request
curl -X DELETE "https://YOUR-PULSE-URL/v1/partner/members/m_4f9c2a71e0b35d8c6a1f9e24/posts/5b0e8f1c-3a7d-4c52-9e64-0d2f7a91b8c3" \
  -H "Authorization: Bearer $PULSE_KEY"
Response 204 (no body)
(empty)
Response 409 (already gone out)
{ "error": { "code": "conflict", "message": "This post has already gone out or can no longer be cancelled." } }

Errors

Every error has the same shape. code is for your code. message is a plain sentence that you can show to a person. field names the input at fault, when there is one. Error messages never contain keys, internal ids or the name of the service behind Pulse.

{ "error": { "code": "validation", "message": "A YouTube title is at most 100 characters — this is 112." } }
HTTPcodeWhen
400confirm_requiredYou called DELETE on a member without ?confirm=true.
400invalid_jsonThe body is not valid JSON, or is not a JSON object. An empty body counts as {}.
401unauthorizedThe key is missing, wrong or switched off.
403forbidden_scopeYour key was used on a path outside /v1/partner.
404not_foundThe member, connection, post or endpoint does not exist, or is not yours.
409conflictThe post cannot be cancelled now (it already went out), or it, or a post of a member you are deleting, is being prepared. Wait a few seconds and retry.
409limit_reachedA connect session was asked for, and a limit leaves no room on any of its networks. The message names the limit.
413body_too_largeThe request body is over 1 MB.
422validationSomething you sent is not allowed, for example too many characters, a bad date or a bad link.
422network_not_enabledYou asked for a network that is not switched on for this member or for you, such as X.
422not_connectedThe member has no working account on a network you listed, or the account you named needs reconnecting.
422ambiguous_accountThe member has several accounts on a network and you did not name exactly one in connection_ids.
429rate_limitedToo many requests. Wait for the time in Retry-After.
500internalSomething went wrong on our side. Try again.
502provider_unavailableThe posting service did not answer, or would not do what Pulse asked of it. Try again in a minute.

400 confirm_required

{ "error": { "code": "confirm_required", "message": "Deleting a member can't be undone. Add ?confirm=true to confirm.", "field": "confirm" } }

400 invalid_json

{ "error": { "code": "invalid_json", "message": "The request body isn't valid JSON." } }

401 unauthorized

{ "error": { "code": "unauthorized", "message": "Send your partner key as 'Authorization: Bearer fnk_...'." } }

403 forbidden_scope

{ "error": { "code": "forbidden_scope", "message": "This key can only be used with the partner API (/v1/partner)." } }

404 not_found

{ "error": { "code": "not_found", "message": "No such member." } }

Other messages: "No such connection.", "No such post.", "No such endpoint."

409 conflict

{ "error": { "code": "conflict", "message": "This post has already gone out or can no longer be cancelled." } }

409 limit_reached

{ "error": { "code": "limit_reached", "message": "You've reached the limit of 3 accounts on LinkedIn." } }

413 body_too_large

{ "error": { "code": "body_too_large", "message": "That request is too large (1 MB at most)." } }

422 validation

{ "error": { "code": "validation", "message": "Instagram requires at least one media attachment." } }

When the problem is one named input, field is set, for example scheduled_at, media_urls, texts or return_url:

{ "error": { "code": "validation", "message": "scheduled_at can be at most a year ahead.", "field": "scheduled_at" } }

422 network_not_enabled

{ "error": { "code": "network_not_enabled", "message": "X is not switched on for this member.", "field": "networks" } }

422 not_connected

{ "error": { "code": "not_connected", "message": "This member hasn't connected Instagram (or it needs reconnecting).", "field": "networks" } }

422 ambiguous_account

{ "error": { "code": "ambiguous_account", "message": "This member has 2 LinkedIn accounts. Say which one with connection_ids.", "field": "networks" } }

429 rate_limited

{ "error": { "code": "rate_limited", "message": "Too many requests. Try again in 20 seconds." } }

500 internal

{ "error": { "code": "internal", "message": "Something went wrong on our side. Please try again." } }

502 provider_unavailable

{ "error": { "code": "provider_unavailable", "message": "The posting service isn't answering right now. Please try again in a minute." } }

Rate limits

Each key can make 60 requests per minute. Past that, you get 429 rate_limited and a Retry-After header with the number of seconds to wait. Requests with a missing or wrong key are not counted against any key. The connect window is not counted: a member using the window never uses up your 60 (the window has its own cap of 120 requests an hour for each link).

HTTP/1.1 429 Too Many Requests
Retry-After: 20
Content-Type: application/json

{ "error": { "code": "rate_limited", "message": "Too many requests. Try again in 20 seconds." } }

A request body can be 1 MB at most. A larger one is refused with 413 body_too_large.

Security and privacy

Coming later

These are not in this version.

Changelog

Support

Need a key, a limit changed, or something is not working? Email fasahath@gmail.com. Tell us the request you sent and the error you got back.

The machine-readable description of this API is at /docs/openapi.json.