# Connect to the MCP Server
Source: https://www.ayrshare.com/docs/additional/mcp-action-connect
Connect any MCP client to the Ayrshare MCP Server — transport, endpoint, and authentication.
The Ayrshare [MCP Server](/docs/additional/mcp-action-server) lets an AI agent drive the Ayrshare API. This page covers how to connect to it and how authentication works.
## Endpoint and transport
The production MCP Server is available at:
`https://api.ayrshare.com/mcp`
It uses **Streamable HTTP** transport and is **stateless** — there is no session to maintain between calls.
## Supported clients
The Action MCP Server authenticates **only** with a static `Authorization: Bearer` request header. That one requirement decides everything below: a client that can set a custom HTTP header on a Streamable HTTP connection can authenticate, and a client that cannot, cannot. Find your client before you start setup.
| Client | Can it authenticate? | How, or why not |
| - | - | - |
| **Claude Code (CLI)** | **Yes** | `claude mcp add --transport http … --header "Authorization: Bearer YOUR_API_KEY"`. This is the recommended path — see [Option B: Any MCP client](#option-b-any-mcp-client) below. |
| **Claude Code plugin** | **Yes** | The bundled MCP configuration sends your key as the `Authorization` header. See [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin). |
| **Claude Desktop — personal connector** | **No** | A personal custom connector cannot send a static `Authorization` header, and Ayrshare has not verified any third-party bridge that adds one. Use [Claude Code](#option-b-any-mcp-client) instead, or see the organization option below. |
| **claude.ai web — personal connector** | **No** | A personal connector cannot send a static `Authorization: Bearer` header, and Ayrshare publishes no OAuth metadata for it to use instead. See [Why personal connectors cannot authenticate](#why-personal-connectors-cannot-authenticate) below. |
| **Claude mobile — personal connector** | **No** | Same as claude.ai web: a personal connector has no way to present an Ayrshare API key. |
| **Claude Team or Enterprise — organization administrator** | **Beta, administrators only** | Anthropic's `static_headers` request-header option, available in beta to organization administrators in a limited set of organizations. See [Team and Enterprise organizations](#team-and-enterprise-organizations) below. |
| **Cursor** | **Yes** | Supported via a `headers` block on the remote server entry — `"Authorization": "Bearer YOUR_API_KEY"` — per [Cursor's MCP documentation](https://cursor.com/docs/context/mcp). |
| **VS Code** | **Yes** | Supported via the `headers` property on an `http` MCP server entry, per [VS Code's MCP configuration reference](https://code.visualstudio.com/docs/agents/reference/mcp-configuration). |
| **n8n** | **Yes** | Supported via a Bearer credential (or a generic header) on the MCP Client Tool node, per [n8n's MCP Client Tool documentation](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp/). See the [n8n guide](/docs/additional/mcp-n8n). |
| **Any client supporting custom HTTP headers** | **Yes** | Send `Authorization: Bearer YOUR_API_KEY` on every request. That is the server's only authentication requirement, so a client not listed above is still answerable by this rule. |
### Why personal connectors cannot authenticate
Ayrshare's Action MCP requires a static `Authorization: Bearer` header. Adding it as a personal custom connector on claude.ai, Claude Desktop, or Claude mobile does not work, because Ayrshare publishes no OAuth metadata and the request-header alternative is a beta limited to approved organizations.
On a personal connector, no setting on your side fixes this: there is no field for an `Authorization` header, and no API key, profile, or account setting that enables one. **Ayrshare does not publish OAuth metadata, so the connector's OAuth path has nothing to connect to.** Use a client from the table above instead: [Claude Code](#option-b-any-mcp-client), the [Claude Code plugin](/docs/additional/mcp-claude-code-plugin), or any client that lets you set a custom `Authorization` header. If you are an administrator of an organization in Anthropic's request-header beta, see [Team and Enterprise organizations](#team-and-enterprise-organizations) below.
> [!WARNING]
>
> **If your connector looks healthy but every tool fails.** The connector adds cleanly, reports **healthy**, and lists the **full tool catalog** — and then **every** tool call returns Ayrshare **403 / code 102, "API Key not valid"**. That is this limitation, not a broken key — your client cannot send the static `Authorization: Bearer` header this server requires. `initialize` and `tools/list` are reachable before authentication (see [Authentication](#authentication) below), so nothing fails until the first tool call, when it becomes clear no credential was ever sent.
>
> **Client capability, or a genuinely bad key?** They return the identical error, so use the signature to tell them apart. A client-capability failure means the connector is healthy **and** every tool fails, while the same key works elsewhere. A bad key produces the same error but is reproducible outside the connector — it fails on a REST call too. For the error semantics themselves, see [Authentication errors](#authentication-errors) below.
### Team and Enterprise organizations
Anthropic's `static_headers` mechanism lets a custom connector send a fixed credential as a request header instead of using OAuth. It is **available in beta to organization administrators in a limited set of organizations**, and it is **not** available to individual users adding a personal connector.
If your organization has access, the administrator must enter the header value as `Bearer ` followed by your Ayrshare API key, including the space — Claude sends the value exactly as entered and does not add the scheme for you. For current availability and setup, see [Anthropic's connector authentication documentation](https://claude.com/docs/connectors/building/authentication).
> [!WARNING]
>
> **The key is shared by the whole organization.** Anthropic's request-header credential is entered once by an administrator and used by every member of the organization, so all of them act as the single Ayrshare account that key belongs to — with access to every [User Profile](/docs/multiple-users/manage-user-profiles) under it on a Business key, and no per-user attribution in your Ayrshare history or analytics. Treat it as a shared service credential, not as individual access.
## Authentication
Authentication is enforced by the same Ayrshare API chain that powers the REST API.
**Required:** `Authorization: Bearer YOUR_API_KEY` — your account API key (Business plan key for profiles and sub-profiles).
**Optional:** `Profile-Key: YOUR_PROFILE_KEY` — targets a sub-profile for every call on the connection.
**Optional per call:** a `profileKey` tool argument — targets a sub-profile for a single tool call.
### Precedence: argument wins over header
When a tool call includes a `profileKey` argument **and** the connection has a `Profile-Key` header, the **per-call `profileKey` argument wins**. The header is used only when no valid argument is provided.
One exception: on `get_platform_history` and `get_social_network_analytics`, an X/Twitter `userId`/`userName` lookup must use the account API key only; supplying a `profileKey` argument or `Profile-Key` header there returns Error 400.
> [!NOTE]
>
> The `initialize` and `tools/list` MCP methods are reachable **before** authentication — they return metadata only and execute nothing. **Every tool call is authenticated.**
### Authentication errors
An unauthenticated or invalid key on a tool call returns an Ayrshare **error 403 / code 102** with message **"API Key not valid"**. Per the MCP spec, tool-execution errors are returned in-band: the tool result carries `isError: true` and this message, while the MCP transport itself responds **HTTP 200**. (The `403`/`102` are Ayrshare's application error, not the transport status.)
## Connect
### Option A: Claude Code plugin
If you use Claude Code, install the Ayrshare plugin. It bundles the MCP Server configuration, a setup command, agents, skills, and a confirmation hook. See the [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin) page for the full install steps.
### Option B: Any MCP client
For any MCP client that supports Streamable HTTP **and lets you set a custom request header** — see [Supported clients](#supported-clients) above — register the server directly. In Claude Code:
```bash theme={"system"}
claude mcp add --transport http ayrshare https://api.ayrshare.com/mcp --header "Authorization: Bearer YOUR_API_KEY"
```
To target a sub-profile on every call, add the optional `Profile-Key` header:
```bash theme={"system"}
claude mcp add --transport http ayrshare https://api.ayrshare.com/mcp \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "Profile-Key: YOUR_PROFILE_KEY"
```
> [!WARNING]
>
> The MCP connection initializes at session start. **Restart your MCP client** after installing the server or changing your key so the new configuration takes effect.
## X/Twitter BYO credentials
Since March 31, 2026, X/Twitter operations through Ayrshare require your own OAuth 1.0a credentials. When a tool call targets X/Twitter, forward these two headers on the connection alongside your `Authorization` (and optional `Profile-Key`) headers:
| Header | Description |
| - | - |
| `X-Twitter-OAuth1-Api-Key` | Your OAuth 1.0a API Key (Consumer Key) |
| `X-Twitter-OAuth1-Api-Secret` | Your OAuth 1.0a API Key Secret (Consumer Secret) |
These are the same headers used by the REST API — one OAuth 1.0a key pair per Ayrshare account, sent on every X-targeting request (the same pair applies to all sub-profiles). Ayrshare does not use OAuth 2.0 here. See the [API Overview](/docs/apis/overview#xtwitter-byo-credentials) and the [X BYO Key Setup Guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) for setup, policy, and troubleshooting.
> [!WARNING]
>
> Without these headers, an X/Twitter tool call returns error `419` (`x_credentials_required`).
### Connect with the BYO headers
To set up X/Twitter BYO credentials from the start, add the server with both OAuth 1.0a headers alongside your `Authorization` header (include `Profile-Key` too if you target a sub-profile on every call):
```bash theme={"system"}
claude mcp add --transport http ayrshare https://api.ayrshare.com/mcp \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "X-Twitter-OAuth1-Api-Key: YOUR_TWITTER_CONSUMER_KEY" \
--header "X-Twitter-OAuth1-Api-Secret: YOUR_TWITTER_CONSUMER_SECRET"
```
### Add the BYO headers to an existing connection
Connection headers are fixed when the server is added, so if you already connected without the BYO headers, remove the server and re-add it with the full set (re-include any headers you were already using, such as `Profile-Key`):
```bash theme={"system"}
claude mcp remove ayrshare
claude mcp add --transport http ayrshare https://api.ayrshare.com/mcp \
--header "Authorization: Bearer YOUR_API_KEY" \
--header "X-Twitter-OAuth1-Api-Key: YOUR_TWITTER_CONSUMER_KEY" \
--header "X-Twitter-OAuth1-Api-Secret: YOUR_TWITTER_CONSUMER_SECRET"
```
Restart your MCP client after changing headers so the new configuration takes effect. Using the Claude Code plugin instead of a raw connection? See [Claude Code Plugin → X/Twitter BYO credentials](/docs/additional/mcp-claude-code-plugin#xtwitter-byo-credentials).
## Next steps
> [card] [Tool Catalog](/docs/additional/mcp-action-tools)
>
> The 33 tools grouped by domain, with scope and purpose.
> [card] [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin)
>
> Install the Ayrshare plugin for Claude Code.
# Ayrshare MCP Server: Drive the API with AI Agents
Source: https://www.ayrshare.com/docs/additional/mcp-action-server
Let AI agents drive the Ayrshare API through the Model Context Protocol (MCP) Server.
> [!NOTE]
>
> Looking for the read-only docs-search MCP? See [Documentation MCP](/docs/additional/mcp-server). **This page documents the Action MCP** that lets an AI agent drive the Ayrshare API.
The Ayrshare **MCP Server** lets AI agents drive the Ayrshare API through [Model Context Protocol (MCP)](https://modelcontextprotocol.io) tools. Instead of writing REST calls by hand, an agent connects to the MCP Server and calls Ayrshare tools to publish posts, fetch history and analytics, manage comments and direct messages, create profiles, and more.
## How it works
Each MCP tool call is dispatched in-process through the same Ayrshare API chain that powers the REST API:
Authentication of your API key.
Rate limiting for your plan tier.
Quota and usage enforcement.
OpenAPI request validation.
The same controllers that serve the REST endpoints.
Because tools run through the real API chain, the MCP Server returns the same data and enforces the same rules as the REST API. There is no separate behavior to learn or maintain.
## Who it's for
**Agent builders** connecting an LLM agent to the Ayrshare API to publish and analyze content.
**Claude Code users** who want to post, fetch history, and manage profiles from their editor (see the [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin)).
**Integrators** onboarding clients' social accounts under sub-profiles (see [Client Onboarding](/docs/additional/mcp-client-onboarding)).
> [!NOTE]
>
> Working with profiles and sub-profiles requires a **Business plan** (or Enterprise). See the [Business Plan Overview](/docs/multiple-users/business-plan-overview).
## Endpoint and transport
The production MCP Server is available at:
`https://api.ayrshare.com/mcp`
It uses **Streamable HTTP** transport and is **stateless** — there is no session to maintain between calls. See [Connect & Setup](/docs/additional/mcp-action-connect) for the full connection and authentication details.
## Relationship to the REST API and Documentation MCP
**REST API** — the [Ayrshare API](/docs/apis/overview) is the underlying interface. The MCP Server dispatches each tool call through that same API chain, so behavior, rules, and data match.
**Documentation MCP** — the separate [Documentation MCP](/docs/additional/mcp-server) is a read-only server that searches Ayrshare's API documentation. It does not perform actions. Use the MCP Server (this page) to actually drive the API.
## Next steps
> [card] [Connect & Setup](/docs/additional/mcp-action-connect)
>
> Transport, endpoint, and authentication for any MCP client.
> [card] [Tool Catalog](/docs/additional/mcp-action-tools)
>
> The 33 tools grouped by domain, with scope and purpose.
> [card] [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin)
>
> Install the Ayrshare plugin for Claude Code.
> [card] [Client Onboarding](/docs/additional/mcp-client-onboarding)
>
> Mint social-linking URLs for client sub-profiles.
# MCP Tool Catalog
Source: https://www.ayrshare.com/docs/additional/mcp-action-tools
Lists each Ayrshare MCP Server tool by domain, from posts and analytics to webhooks and ads, and notes if it takes a profileKey or runs at account scope.
The Ayrshare [MCP Server](/docs/additional/mcp-action-server) exposes **33 tools**, grouped below by domain. Each tool is dispatched in-process through the real Ayrshare API chain.
## Scope
Every tool is either **Profile** or **Account** scope:
**Profile** — the tool accepts a `profileKey` argument to target a sub-profile.
**Account** — the tool takes **no** `profileKey`; it operates at the account level.
The six tools that take **no** `profileKey` argument are: `create_profile`, `list_profiles`, `explain_error`, `validate_media`, `generate_post`, and `recommend_hashtags`. All other tools accept a `profileKey`; `generate_jwt_social_linking_url` **requires** one. Note: `recommend_hashtags` takes no `profileKey` argument but still reads the connection's `Profile-Key` header to pick whose linked TikTok account it draws on, so its results are profile-specific.
When both a per-call `profileKey` argument and a `Profile-Key` header are present, the argument wins. See [Connect & Setup](/docs/additional/mcp-action-connect#precedence-argument-wins-over-header) for the precedence rules.
## Posts
| Tool | Scope | Purpose |
| - | - | - |
| `create_post` | Profile | Publish to one or more linked platforms. |
| `validate_post` | Profile | Dry-run validation, same input as `create_post`. |
| `get_post` | Profile | Fetch a post by Ayrshare Post ID. |
| `update_post` | Profile | Update a scheduled or awaiting-approval post. |
| `retry_post` | Profile | Re-submit a failed post. |
## History
| Tool | Scope | Purpose |
| - | - | - |
| `get_post_history` | Profile | Posts sent via Ayrshare. |
| `get_platform_history` | Profile | Posts and analytics direct from the network, including non-Ayrshare posts. For an X/Twitter `userId`/`userName` lookup, use the account API key only; a `profileKey` argument or `Profile-Key` header there returns Error 400. |
## Analytics
| Tool | Scope | Purpose |
| - | - | - |
| `get_post_analytics` | Profile | Per-post metrics by Ayrshare Post ID. |
| `get_post_analytics_by_social_id` | Profile | Per-post metrics by native Social Post ID. |
| `get_social_network_analytics` | Profile | Account-wide analytics and demographics for a connected social account. For an X/Twitter `userId`/`userName` lookup, use the account API key only; a `profileKey` argument or `Profile-Key` header there returns Error 400. |
## Comments
| Tool | Scope | Purpose |
| - | - | - |
| `get_comments` | Profile | Read comments on a post. |
| `add_comment` | Profile | Top-level comment. |
| `reply_comment` | Profile | Reply to a comment. |
## Messages / DMs
| Tool | Scope | Purpose |
| - | - | - |
| `get_messages` | Profile | Fetch DMs for a platform. |
| `send_message` | Profile | Send a DM. |
| `get_auto_response` | Profile | Read DM auto-response settings. |
| `set_auto_response` | Profile | Configure DM auto-response. |
## Profiles
| Tool | Scope | Purpose |
| - | - | - |
| `list_profiles` | Account | List sub-profiles. |
| `create_profile` | Account | Create a sub-profile. |
| `generate_jwt_social_linking_url` | Profile | Mint a JWT social-linking URL (requires `profileKey`). |
## Media
| Tool | Scope | Purpose |
| - | - | - |
| `validate_media` | Account | HEAD-check a media URL is reachable; returns `contentType`. |
## Generate
| Tool | Scope | Purpose |
| - | - | - |
| `generate_post` | Account | AI-draft post copy. |
| `recommend_hashtags` | Account | Hashtag suggestions from TikTok view-count data. Uses the linked TikTok account on the profile set by the `Profile-Key` header; takes no per-call `profileKey`. |
## Webhooks
| Tool | Scope | Purpose |
| - | - | - |
| `register_webhook` | Profile | Register a webhook. |
| `unregister_webhook` | Profile | Unregister a webhook. |
| `list_webhooks` | Profile | List registered webhooks. |
## Ads
Ads tools need the Ads add-on. Without it they return `Error 403: Ads is not enabled. Please subscribe to Ads in the Account page. (code 399)`. Each tool takes a `platform` argument: `facebook` or `instagram`.
| Tool | Scope | Purpose |
| - | - | - |
| `list_ad_accounts` | Profile | List the ad accounts the linked account can spend from. |
| `search_ad_targeting` | Profile | Search targeting options: `interests`, `regions` or `cities`. |
| `list_ads` | Profile | List boosted ads by campaign, account, ad or post. |
| `update_ad` | Profile | Pause, resume or retire an ad. The status is also set on its ad set and campaign, so other ads in them change too, except a campaign or ad set the boost was attached to with `attachTo`; the ad runs only while both are active. `status` takes `ACTIVE`, `PAUSED`, `DELETED` or `ARCHIVED`, all upper case or all lower case (`paused` works, `Paused` does not). Changes real ad spend. |
| `get_ad_history` | Profile | Spend and performance history for boosted ads. |
| `boost_post` | Profile | Boost a published post as an ad. Takes `postId` or `socialPostId`, `accountId` from `list_ad_accounts`, a `goal` and a `budget` (leave out `budget` and `targeting` with `attachTo.adSetId`). Settings only one platform has go under `platformOptions`; on Facebook and Instagram, `platformOptions..adName` and `.bidAmount` are required, except `.bidAmount` with `attachTo.adSetId`. Spends real money. Unless set otherwise, the ad is active, starts now and has no end date. Send an `idempotencyKey` and reuse it, with the same arguments, on a retry. The retry then returns the first result instead of creating a second ad. |
## Errors
| Tool | Scope | Purpose |
| - | - | - |
| `explain_error` | Account | Decode an Ayrshare error code into cause, classification, and fix. |
> [!NOTE]
>
> This catalog is verified against the live server's `tools/list`. More tools are planned (e.g. Links tools). If in doubt, query `tools/list` for the authoritative set. The catalog is maintained by the MCP server team.
# Claude Code Plugin
Source: https://www.ayrshare.com/docs/additional/mcp-claude-code-plugin
Install the Ayrshare plugin in Claude Code, set your API key and X/Twitter BYO credentials, and review its agents, skills, and confirmation hook.
The Ayrshare plugin for Claude Code bundles the [MCP Server](/docs/additional/mcp-action-server) configuration, a setup command, agents, skills, and a safety hook. With it installed, you can publish posts, fetch history and analytics, manage profiles, and more — directly from Claude Code. Before anything goes live, the agent validates each post against the target network's rules and asks you to confirm, catching avoidable rejections up front. Post history is available for matching a brand's voice, and analytics can be read back to inform the next post.
The plugin is open source. See the repository for internals: [ayrshare/ayrshare-social-media-api-claude-plugin](https://github.com/ayrshare/ayrshare-social-media-api-claude-plugin).
## Install
**1. Add the marketplace (one time)**
```bash theme={"system"}
claude plugin marketplace add ayrshare/ayrshare-social-media-api-claude-plugin
```
**2. Install the plugin**
```bash theme={"system"}
claude plugin install ayrshare@ayrshare
```
### Install scopes
| Scope | Where it applies |
| - | - |
| Global (default) | Every project on this machine. |
| `--scope local` | Current project, not committed to git. |
| `--scope project` | Current project, committed and shared with your team. |
> [!NOTE]
>
> Claude Code's CLI calls the global scope `--scope user`.
## Credentials
Provide your Ayrshare API key in either of these ways:
Run `/ayrshare:setup` to configure or rotate the key.
Set the `AYRSHARE_API_KEY` environment variable.
The plugin's bundled `.mcp.json` already declares the `Authorization` header plus the optional `Profile-Key` and X BYOK headers, each with an empty default (`${VAR:-}`), so you enable a header simply by setting its environment variable (Claude Code substitutes the values at startup). To target a sub-profile on every call, set `AYRSHARE_PROFILE_KEY` (via `/ayrshare:setup` or your `settings.json` `env` block) and restart Claude Code; the bundled `Profile-Key` header carries it, and stays empty (treated as not provided) when unset. To target a sub-profile for a single call instead, pass a `profileKey` tool argument (no config change needed). You do not need to edit the bundled `.mcp.json`, and you should not, because `claude plugin update` overwrites it.
> [!WARNING]
>
> The MCP server initializes at session start. **Restart Claude Code** after installing the plugin or changing your key.
### X/Twitter BYO credentials
Posting to X/Twitter requires your own X Developer App credentials (the X BYO-key mandate, effective March 31, 2026; see the [API Overview](/docs/apis/overview#xtwitter-byo-credentials)). The bundled `.mcp.json` already declares both X BYOK headers with empty defaults, alongside `Authorization` and `Profile-Key`:
```json theme={"system"}
"headers": {
"Authorization": "Bearer ${AYRSHARE_API_KEY}",
"Profile-Key": "${AYRSHARE_PROFILE_KEY:-}",
"X-Twitter-OAuth1-Api-Key": "${X_TWITTER_OAUTH1_API_KEY:-}",
"X-Twitter-OAuth1-Api-Secret": "${X_TWITTER_OAUTH1_API_SECRET:-}"
}
```
So you only set the matching environment variables (`X_TWITTER_OAUTH1_API_KEY`, `X_TWITTER_OAUTH1_API_SECRET`) via `/ayrshare:setup` or your `settings.json` `env`, then **restart Claude Code**; you do not edit the bundled `.mcp.json` (it is overwritten on `claude plugin update`). Set **both** or neither: with neither set an X/Twitter call returns error `419` (`x_credentials_required`); with only one set it returns error `400`. This is one key pair per Ayrshare account, sent on every X-targeting request (the same pair for all sub-profiles).
> [!NOTE]
>
> Prefer a raw connection over the plugin? The same BYO headers can be passed via `claude mcp add --header` — see [Connect & Setup → X/Twitter BYO credentials](/docs/additional/mcp-action-connect#xtwitter-byo-credentials).
## What it ships
### Command
`/ayrshare:setup` — configure or rotate the Ayrshare API key.
### Agents
`social-manager`: publishes, schedules, and analyzes content across platforms.
`profile-manager`: creates and lists profiles and mints JWT social-linking URLs for client profiles under a Business account.
`insights-analyst`: read-only reporting. Pulls metrics and post history and summarizes performance; never publishes, comments, messages, or modifies anything.
### Skills
| Skill | Purpose |
| - | - |
| `getting-started` | Orient and connect for first-time use. |
| `post` | Create, validate, update, and retry posts. |
| `history` | Retrieve post and platform history. |
| `analytics` | Pull per-post and account-level analytics. |
| `comments` | Read, add, and reply to comments. |
| `messages` | Read and send direct messages and configure auto-responses. |
| `profiles` | Create and list sub-profiles. |
| `media` | Validate that media URLs are reachable. |
| `generate` | AI-draft post copy and recommend hashtags. |
| `webhooks` | Register, unregister, and list webhooks. |
| `errors` | Decode Ayrshare error codes. |
| `draft-in-brand-voice` | Draft post copy in a consistent brand voice. |
| `plan-and-schedule-campaign` | Plan and schedule a multi-post campaign. |
### Confirmation hook
A `PreToolUse` hook asks for confirmation before any action with a live side effect. If you decline, the tool does not run and nothing is sent to Ayrshare. It covers exactly these 12 tools:
`create_post`
`update_post`
`retry_post`
`add_comment`
`reply_comment`
`send_message`
`set_auto_response`
`create_profile`: creates a client profile, which uses profile quota.
`update_ad`: pauses, resumes, deletes, or archives a boosted ad, which changes real ad spend. The same status is also set on the ad's ad set and campaign, so other ads there change too.
`boost_post`: boosts a published post as a paid ad, which spends real money from the ad account.
`register_webhook` and `unregister_webhook`: change where Ayrshare sends your account's events, including message and comment content.
> [!NOTE]
>
> The prompt needs plugin version 0.2.2 or later. Earlier versions of the plugin did not show it. To update, run `claude plugin marketplace update ayrshare`, then `claude plugin update ayrshare@ayrshare`, and restart Claude Code.
## FAQ
> [accordion] Does the plugin validate posts before publishing?
>
> Yes. The plugin runs `validate_post` to dry-run your content against each network's rules — length, format, and media requirements — before it publishes, so you catch avoidable rejections up front.
> [accordion] Does it ask before posting to my live accounts?
>
> Yes. A `PreToolUse` confirmation hook prompts you to approve before any action with a live side effect: `create_post`, `update_post`, `retry_post`, `add_comment`, `reply_comment`, `send_message`, `set_auto_response`, `create_profile`, `update_ad`, `boost_post`, `register_webhook`, and `unregister_webhook`. It needs plugin version 0.2.2 or later.
> [accordion] Can the plugin match my brand's voice?
>
> Yes. The `draft-in-brand-voice` skill reads your post history to draft new copy in a consistent brand voice. It produces drafts only — nothing publishes without your confirmation.
> [accordion] Can I read analytics to inform the next post?
>
> Yes. The analytics tools return per-post and account-level metrics, so you can read results back and let them guide your next post.
> [accordion] What do I need to use the plugin?
>
> An Ayrshare API key (a Business plan key for profiles and sub-profiles) and Claude Code. Configure the key with `/ayrshare:setup` or the `AYRSHARE_API_KEY` environment variable, then restart Claude Code.
> [accordion] Does posting to X/Twitter need extra setup?
>
> Yes. X/Twitter requires your own OAuth 1.0a credentials, added as two connection headers (`X-Twitter-OAuth1-Api-Key` and `X-Twitter-OAuth1-Api-Secret`). See [X/Twitter BYO credentials](#xtwitter-byo-credentials) above.
## Next steps
> [card] [Connect & Setup](/docs/additional/mcp-action-connect)
>
> Authentication, profile targeting, and BYO credentials.
> [card] [Tool Catalog](/docs/additional/mcp-action-tools)
>
> The 33 tools grouped by domain, with scope and purpose.
# Onboard Client Social Accounts
Source: https://www.ayrshare.com/docs/additional/mcp-client-onboarding
Use the MCP Server to onboard clients' social accounts under sub-profiles with a JWT social-linking URL.
Integrators on a Business or Enterprise plan can onboard their clients' social accounts under **sub-profiles**. Using the [MCP Server](/docs/additional/mcp-action-server), an agent creates a sub-profile, targets it, and mints a single sign-on linking URL the client opens to connect their accounts.
## Flow
**1. Create a sub-profile**
Call `create_profile` (account-level — no `profileKey`). It returns a **profile key** for the new sub-profile.
**2. Target the sub-profile**
Use the returned profile key on subsequent calls — either via the `Profile-Key` header on the connection or as a per-call `profileKey` argument. See [Connect & Setup](/docs/additional/mcp-action-connect#precedence-argument-wins-over-header) for precedence.
**3. Mint a social-linking URL**
Call `generate_jwt_social_linking_url` to mint a single sign-on linking URL. The JWT signing key and onboarding domain are derived **server-side** — the caller supplies **no** `X-Ayrshare-*` headers and no private key or domain.
**4. Client links their accounts**
The client opens the URL and authorizes (OAuths) their social accounts. The connected accounts are linked to the sub-profile.
## Requirements
> [!WARNING]
>
> `generate_jwt_social_linking_url` **requires** a `profileKey`. If none resolves, the call returns **400** with the message **"Profile Key is required…"**.
>
> A **social-linking domain must be provisioned** for your account. This is available on the **Business** and **Enterprise** plans.
### Error states
| Condition | Response |
| - | - |
| No provisioned domain | **400** — "No social-linking domain is provisioned for this account (available on Business/Enterprise plans)." |
| No signing key | **400** — "No signing key found for this account's social-linking domain." |
## Next steps
> [card] [Connect & Setup](/docs/additional/mcp-action-connect)
>
> Authentication and profile targeting.
> [card] [Tool Catalog](/docs/additional/mcp-action-tools)
>
> The 33 tools grouped by domain, with scope and purpose.
# n8n
Source: https://www.ayrshare.com/docs/additional/mcp-n8n
Connect n8n's AI Agent to the Ayrshare MCP Server to publish, schedule, and analyze across 13 social networks, with no per-platform API code.
n8n is where you wire your tools together. Ayrshare is the one API that publishes to Facebook, Instagram, LinkedIn, YouTube, TikTok, Pinterest, Reddit, Threads, Bluesky, Telegram, Google Business Profile, Snapchat, and X in a single call. (WhatsApp is messaging-only on Ayrshare, so it is not a publishing target. Use the [Messaging API](/docs/apis/messages/overview) for WhatsApp conversations.) Connect them with the [Ayrshare MCP Server](/docs/additional/mcp-action-server) and your n8n AI Agent can run the whole loop on its own: draft a post, validate it against each network's rules, publish or schedule it, then read the analytics back.

Because the MCP Server is a hosted Streamable HTTP endpoint, you connect it with n8n's built-in **MCP Client Tool** node. There is nothing to host, no custom code, and no community node to install. You point one node at one URL and your agent gets all 33 social tools.
> [card] [Download the n8n starter workflow](/docs/files/ayrshare-n8n-mcp-workflow.json)
>
> The workflow shown above, ready to import: Chat Trigger, AI Agent, an Anthropic chat model, and the Ayrshare MCP node pre-wired with the validate-first system prompt.
To use it: in n8n, **Workflows → Import from file**, select the JSON, then create the two credentials it expects (a **Bearer Auth** credential holding your Ayrshare key, and an **Anthropic API** credential). The two warning badges clear once the credentials are attached. Self-hosted instances need outbound access to `api.ayrshare.com` and your model provider.
This guide covers the MCP-first path end to end. If you would rather build a fixed, non-agent workflow, there is a short [REST fallback](#rest-fallback-no-agent) at the end.
## Why MCP instead of writing API calls
You can call Ayrshare's REST API directly from an HTTP Request node, which is fine for fixed, predictable workflows. The MCP Server earns its place when an LLM is in the loop:
**The agent picks the tool.** Describe the goal ("publish this to our business channels and schedule the follow-up for Tuesday") and the agent chooses `validate_post`, `create_post`, and the right parameters itself.
**Validation before anything goes live.** `validate_post` dry-runs your content against each platform's length, format, and media rules, so the agent does not ship a post a network will reject.
**One call, many networks.** A single `create_post` fans out to every linked platform.
**Platform changes are Ayrshare's problem.** When a network changes its API, Ayrshare maintains the integration and your workflow keeps working.
**Same rules as the REST API.** Every MCP tool call runs in-process through the same Ayrshare API chain: same auth, rate limits, quota, and validation. There is no separate behavior to learn.
## How it connects
The **AI Agent** node is the brain. The **MCP Client Tool** sub-node attaches to it, connects to the Ayrshare MCP Server at `https://api.ayrshare.com/mcp`, discovers the available tools, and exposes them to the agent. When the agent acts, the node dispatches the call to Ayrshare, which publishes to the networks.
```
Trigger -> AI Agent (+ Chat Model) -> MCP Client Tool -> Ayrshare MCP Server -> 13 networks
```
For the endpoint, transport, and authentication details, see [Connect & Setup](/docs/additional/mcp-action-connect). For the full tool list, see the [Tool Catalog](/docs/additional/mcp-action-tools).
## Prerequisites
An **n8n instance** (Cloud or self-hosted) on a recent version with the AI Agent and MCP Client Tool nodes. The MCP Client Tool node is built in. You do not need a community node, because the Ayrshare server speaks Streamable HTTP.
An **Ayrshare account and API key** (Dashboard → Settings → API Key), or start a [free trial](https://billing.ayrshare.com/b/9B6bJ15Oidr9fz615u1Nu0h).
**At least one social account linked** in Ayrshare. The agent can only publish where you have connected.
A **chat model credential** for the AI Agent node (Anthropic, OpenAI, etc.).
*(Optional)* A **Business or Enterprise plan** if you will manage multiple clients via sub-profiles.
## Set up the MCP Client Tool node
**1. Add an AI Agent node**
Open or create a workflow and add an **AI Agent** node (under the *Advanced AI* nodes).
**2. Attach the MCP Client Tool node**
On the AI Agent node, click the **Tool** connector and add an **MCP Client Tool** node. Configure it:
| Field | Value |
| - | - |
| **Endpoint** | `https://api.ayrshare.com/mcp` |
| **Server Transport** | `HTTP Streamable` |
| **Authentication** | `Bearer Auth` |
| **Tools to Include** | `All` (or `Selected` to expose only specific tools) |
**3. Add your Ayrshare key as a Bearer credential**
For **Credential**, create a new **Bearer Auth** credential and paste your Ayrshare API key as the token. n8n sends it as `Authorization: Bearer YOUR_API_KEY`. When you save, n8n connects and lists the 33 tools.
**4. Wire up the chat model and system prompt**
Attach a **Chat Model** sub-node and select your model credential. Give the agent a system prompt that sets the rules, for example:
> You are a social media assistant with access to Ayrshare tools. Before publishing anything, always call `validate_post` first and report any issues. Only call `create_post` after validation passes. Default to the platforms the user names; if none are named, ask. Never invent media URLs; only use URLs the user provides, and confirm them with `validate_media` when in doubt.
**5. Add a trigger**
For testing, the **Manual Trigger** or **Chat Trigger** is easiest. For production, use whatever kicks off the workflow (schedule, webhook, form, new row in a sheet).
> [!NOTE]
>
> **Use HTTP Streamable, not SSE.** The Ayrshare MCP Server uses the modern Streamable HTTP transport and is stateless. n8n's SSE option is deprecated, and the server rejects the SSE stream. Always choose **HTTP Streamable**.
## The tool surface
Your agent sees all 33 tools through the one MCP node. You rarely call them by name; you describe intent and the agent selects. The domains are Posts, History, Analytics, Comments, Messages, Profiles, Media, Generate, Webhooks, Ads, and Errors. For the complete list with each tool's purpose and scope, see the [Tool Catalog](/docs/additional/mcp-action-tools).
Two stand out for safety: **`validate_post`** dry-runs a post with the same input as `create_post` but publishes nothing, and **`explain_error`** turns any Ayrshare error code into a plain-English cause and fix, so the agent can self-diagnose.
## Example 1: draft, validate, and publish from a chat message
The "hello world" of this integration. Trigger the workflow with a chat message or form, and let the agent do the rest.
**Trigger:** Chat Trigger (or a Form Trigger with a "what should we post?" field).
**Prompt to the agent:** *"Write a friendly launch announcement for our new analytics dashboard and publish it to LinkedIn and Facebook. Validate first."*
What the agent does on its own: it calls `validate_post` with your copy and both platforms; if anything breaks a network's rules it tells you instead of failing silently; once validation passes it calls `create_post` and returns the live post URLs. Because validation runs first, you find out about a problem before anything is public.
## Example 2: auto-publish new content across your channels
Turn a content source into multi-network posts without touching it.
**Trigger:** RSS Read node on your blog feed, a webhook from your CMS, or a new row in Google Sheets or Airtable.
**AI Agent step:** *"Summarize this article into a short social post with 2 to 3 relevant hashtags, then validate and publish to LinkedIn, Facebook, and Threads."*
Optionally add a [human-in-the-loop](#keep-a-human-in-the-loop) approval before the `create_post` step so a person signs off.
You can lean on `recommend_hashtags` for data-driven tags and `generate_post` if you want Ayrshare to draft the copy rather than your chat model.
## Example 3: weekly analytics digest
Run the loop in reverse: read performance and report it.
**Trigger:** Schedule node, for example every Monday at 8am.
**AI Agent step:** *"Pull last week's account analytics for LinkedIn, Instagram, and Facebook, and summarize the top 3 posts by engagement."*
The agent uses `get_social_network_analytics` for account-level numbers and `get_post_analytics` for individual posts, then you pipe its summary to a **Slack**, **Gmail**, or **Notion** node.
A note on cadence: most posts get the bulk of their engagement in the first 24 hours, and metrics do not update by the second. A daily or weekly schedule is plenty. Do not poll analytics every few minutes or you will hit rate limits for no new data.
## Acting on behalf of clients (multi-tenant)
If you manage social for multiple clients, Ayrshare's profiles let one account post to many separate sets of linked accounts. On a Business or Enterprise plan you have two ways to target a client from n8n:
**Per connection:** add a `Profile-Key` header to the MCP Client Tool node (use **Multiple Headers** auth so you can send both `Authorization` and `Profile-Key`). Every call on that node then acts as that client. Good when a workflow serves one client.
**Per call:** many tools accept a `profileKey` argument, which the agent can set per action. When both are present, the [per-call argument wins](/docs/additional/mcp-action-connect#precedence-argument-wins-over-header). Good when one workflow routes across clients.
To onboard a new client, the agent can call `create_profile` and then `generate_jwt_social_linking_url` to mint a hosted page where the client links their own accounts. No credentials pass through your workflow.
## Posting to X/Twitter
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
In n8n, switch the MCP Client Tool node's **Authentication** to **Multiple Headers** and add the two `X-Twitter-OAuth1-*` headers alongside `Authorization`. It is a one-time setup per Ayrshare account, and the same key pair applies to every profile. For everything else (Facebook, Instagram, LinkedIn, YouTube, TikTok, and the rest) no extra headers are needed. See [Connect & Setup → X/Twitter BYO credentials](/docs/additional/mcp-action-connect#xtwitter-byo-credentials).
## Keep a human in the loop
The MCP tools make it trivial for an agent to publish, which is exactly why you should gate it. Two cheap safeguards:
**Always validate first.** Bake "call `validate_post` before `create_post`" into the system prompt. Validation publishes nothing and catches platform-rule violations early.
**Add an approval step.** Insert n8n's **Send and Wait for Response** (Slack or email) node between the draft and the publish action so a person approves before anything goes live. For scheduled content, the agent can use `update_post` to revise an awaiting-approval post.
## REST fallback (no agent)
If you want a fixed, deterministic workflow with no LLM, skip MCP and call the REST API with an **HTTP Request** node:
```json theme={"system"}
{
"post": "Excited to announce our new feature!",
"platforms": ["facebook", "linkedin", "instagram"],
"mediaUrls": ["https://example.com/image.jpg"],
"scheduleDate": "2026-07-01T10:00:00Z"
}
```
Use the API host `api.ayrshare.com`, not the dashboard host `app.ayrshare.com`. (`app.ayrshare.com` will accept API calls, but it is not the documented endpoint and can return intermittent [502/504 gateway errors](/docs/help-center/technical-support/response-bad-gateway-502-or-504-error), so always use `api.ayrshare.com`.) The same pattern works for the other [REST endpoints](/docs/apis/overview). This is the right tool when the workflow is fully predictable and you do not need an agent deciding anything.
## Troubleshooting
| Symptom | Likely cause | Fix |
| - | - | - |
| Tool call returns `403` / code `102`, "API Key not valid" | Bad or missing API key | Check the Bearer Auth credential holds your exact Ayrshare key. Re-save the node after changing it. |
| n8n cannot connect to the MCP server | Wrong transport or endpoint | Endpoint must be `https://api.ayrshare.com/mcp`; transport must be **HTTP Streamable**, not SSE. |
| X/Twitter post fails with error `419` | Missing X BYO credentials | Add the two `X-Twitter-OAuth1-*` headers via Multiple Headers auth. |
| Post fails for one platform only | That network is not linked, or a required field is missing | Link it in the dashboard; add `title` for YouTube, `title` + `subreddit` for Reddit, `mediaUrls` for Instagram. |
| Instagram post rejected | No media | Instagram requires `mediaUrls` for most post types. Have the agent confirm with `validate_media`. |
| Media attached as a file or binary does not appear | MCP is JSON-only | Reference media by public `mediaUrls` URL; the MCP path does not accept file uploads. Confirm the URL with `validate_media`. |
| Calls work, but act on the wrong client | Profile targeting | Set a `Profile-Key` header (per connection) or `profileKey` argument (per call); the argument wins if both are set. |
| `429 Too Many Requests` | Polling too often | Reduce analytics and comment polling frequency; metrics do not update by the second. |
| Config change did not take effect | MCP connection initializes at session start | Re-save the node or restart the workflow after changing the key or headers. |
You can also expose the `explain_error` tool to the agent so it decodes any Ayrshare error code into a cause and fix on its own.
## Next steps
> [card] [Connect & Setup](/docs/additional/mcp-action-connect)
>
> Endpoint, transport, authentication, profile targeting, and BYO credentials.
> [card] [Tool Catalog](/docs/additional/mcp-action-tools)
>
> The 33 tools grouped by domain, with scope and purpose.
> [card] [MCP Server](/docs/additional/mcp-action-server)
>
> What the MCP Server is and how it maps to the Ayrshare API.
> [card] [Claude Code Plugin](/docs/additional/mcp-claude-code-plugin)
>
> The same server, packaged for Claude Code.
# Documentation MCP
Source: https://www.ayrshare.com/docs/additional/mcp-server
Connect the read-only Ayrshare Documentation MCP server to your AI agent to search Ayrshare's API documentation
> [!NOTE]
>
> This is the read-only **Documentation MCP** (it searches Ayrshare's API docs). To let an AI agent **drive** the Ayrshare API, see the [MCP Server](/docs/additional/mcp-action-server) (Action MCP).
The Model Context Protocol (MCP) is an open standard that enables applications to share context and tools with Large Language Models (LLMs).
By connecting the Ayrshare API documentation MCP server to your AI development tools like Cursor or Claude Desktop, you can give your AI agent direct access to Ayrshare's documentation.
This integration allows your AI agent to:
Search through Ayrshare's API documentation
Understand available endpoints and parameters
Generate code that uses Ayrshare's APIs correctly
Provide contextually accurate suggestions when working with Ayrshare services
This means instead of manually looking up API details, your AI assistant can reference the documentation directly and help you implement Ayrshare functionality more efficiently.
## How to Get Started
Ayrshare's MCP server is available at `https://www.ayrshare.com/docs/mcp`.
### Claude Desktop
1. Navigate to the Connectors page in the Claude settings.
2. Select Add custom connector.
3. Add `Ayrshare MCP` or any name you prefer as your MCP server name and `https://www.ayrshare.com/docs/mcp` as your MCP server URL.
4. Select Add.
5. When using Claude, select the attachments button (the plus icon).
6. Select your MCP server.
> [!WARNING]
>
> These steps work because the Documentation MCP requires no authentication. They do not carry over to the Action MCP: it requires a static `Authorization: Bearer` header, which a personal custom connector cannot send, and Ayrshare publishes no OAuth metadata for the connector to use instead. Administrators of organizations in Anthropic's request-header beta have a separate option. See [Supported clients](/docs/additional/mcp-action-connect#supported-clients) for the clients that can authenticate to the Action MCP.
### Claude Code
```bash theme={"system"}
claude mcp add --transport http ayrshare https://www.ayrshare.com/docs/mcp
```
### VS Code
[](https://insiders.vscode.dev/redirect?url=vscode:mcp/install?%7B%22type%22%3A%22http%22%2C%22name%22%3A%22Ayrshare-MCP%22%2C%22url%22%3A%22https%3A%2F%2Fwww.ayrshare.com%2Fdocs%2Fmcp%22%7D)
1. Create a .vscode/mcp.json file.
2. In mcp.json, configure your server:
```json theme={"system"}
{
"servers": {
"Ayrshare MCP": {
"type": "http",
"url": "https://www.ayrshare.com/docs/mcp"
}
}
}
```
### Cursor
[](cursor://anysphere.cursor-deeplink/mcp/install?name=Ayrshare-MCP\&config=eyJ1cmwiOiJodHRwczovL3d3dy5heXJzaGFyZS5jb20vZG9jcy9tY3AifQ==)
1. Use Command + Shift + P (Ctrl + Shift + P on Windows) to open the command palette.
2. Search for “Open MCP settings”.
3. Select Add custom MCP. This opens the mcp.json file.
4. In mcp.json, configure your server:
```json theme={"system"}
{
"mcpServers": {
"Ayrshare MCP": {
"url": "https://www.ayrshare.com/docs/mcp"
}
}
}
```
## Using MCP Server Connection
Restart the app to apply the changes.
In Cursor, you can test the MCP connector by typing `How do you publish a post in Ayrshare. Use the MCP server.`

You will see the request to "Run tool" and Cursor will access the MCP server to get the response.
# Ad Campaigns
Source: https://www.ayrshare.com/docs/apis/ads/campaigns
Create, read, and update ad campaigns that have no post behind them, such as search campaigns.
A campaign is an ad that does not start from one of your posts. Use it for formats such as search ads, where you set a budget, a bidding strategy, and targeting, and the network builds the ads.
To promote a post you have already published, use [Boost Post](/docs/apis/ads/facebook/boost-post) instead.
> [!NOTE]
>
> Google accepts campaigns. See [Google Search Campaigns](/docs/apis/ads/google/search-campaigns) and [Google Performance Max Campaigns](/docs/apis/ads/google/performance-max-campaigns) for what is specific to Google. Other networks refuse a campaign request with `code: 101`, and `details` says why. On Facebook, for example, it says `campaignCreation is not supported on facebook.`
## Endpoints
| Method | Path | What it does |
| - | - | - |
| `POST` | `/ads/{platform}/campaigns` | Create a campaign |
| `GET` | `/ads/{platform}/campaigns/{id}` | Get one campaign |
| `PUT` | `/ads/{platform}/campaigns/{id}` | Change one campaign |
`{platform}` is the ads network. `{id}` is the campaign id returned when you created it. You can read and change only campaigns created through Ayrshare by the same user profile.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
**Idempotency-Key** `string` *required*
Required to create a campaign. Send a new unique value, such as a UUID, for each new campaign. If you repeat a request with the same key, Ayrshare returns the first response, marked `idempotentReplayed: true`, and creates nothing new. You can send the key in the body as `idempotencyKey` instead. If you send both, they must match.
## Create a Campaign
`POST /ads/{platform}/campaigns`
New campaigns start `paused` unless you send `status: "active"`, so nothing is spent until you choose to start.
**accountId** `string` *required*
The ad account to create the campaign in.
**name** `string` *required*
The campaign name, 1 to 255 characters.
**type** `string` *required*
The campaign type: `search` or `performanceMax`. Each network supports only some types. A type the network does not support answers `code: 101` and lists the supported ones.
**budget** `object` *required*
**properties**
**amount** `number` *required*
How much to spend per day, with at most two decimal places.
**type** `string` *required*
`daily`.
**currency** `string`
ISO 4217 code, such as `USD`. Defaults to the ad account's currency. Only some networks accept it.
**bidding** `object` *required*
**properties**
**strategy** `string` *required*
`maximizeConversions`, `maximizeConversionValue`, `targetCpa`, `targetRoas`, `maximizeClicks`, or `manualCpc`. Each network supports only some strategies.
**targetCpa** `number`
Target cost per action. Required with `targetCpa`, and not accepted with any other strategy.
**targetRoas** `number`
Target return on ad spend, for example `3.5` for 350%. Required with `targetRoas`, and not accepted with any other strategy.
**euPoliticalAdvertising** `boolean` *required*
Whether the campaign is political advertising in the EU. You must always send it. Ayrshare never fills it in for you.
**schedule** `object`
**properties**
**start** `string`
When to start: an ISO date (`2026-11-01`) or an ISO date-time with `Z` or an offset (`2026-11-01T09:00:00Z`). Defaults to now.
**end** `string`
When to stop, in the same format as `start`. Must be after `start`, or after now if you leave `start` out. Leave it out to run with no end date. A network that schedules whole days, such as Google, also accepts an `end` on the same day as `start`, for a one-day campaign.
**targeting** `object`
Each network supports only some targeting. A field the network does not support answers `code: 101`.
**properties**
**locations** `string[]`
The network's own location ids.
**languages** `string[]`
The network's own language ids.
**status** `string` (default: paused)
`active` or `paused`.
**platformOptions** `object`
Options for one network only, under that network's name, for example `{ "facebook": { ... } }`. Options for any other network answer `code: 101`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10" \
-X POST https://api.ayrshare.com/api/ads/google/campaigns \
-d '{
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/campaigns", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10"
},
body: JSON.stringify({
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
"Idempotency-Key": "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10",
}
payload = {
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": False,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}
r = requests.post("https://api.ayrshare.com/api/ads/google/campaigns", headers=headers, json=payload)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json",
"Idempotency-Key: 6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10"
],
CURLOPT_POSTFIELDS => $payload
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
client.DefaultRequestHeaders.Add("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10");
var payload = """
{
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}
""";
var response = await client.PostAsync(
"https://api.ayrshare.com/api/ads/google/campaigns",
new StringContent(payload, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := `{
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}`
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/ads/google/campaigns", strings.NewReader(payload))
req.Header.Add("Authorization", "Bearer API_KEY")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CreateCampaign {
public static void main(String[] args) throws Exception {
String payload = """
{
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/campaigns"))
.header("Authorization", "Bearer API_KEY")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/campaigns')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer API_KEY'
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = '6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10'
request.body = <<~JSON
{
"accountId": "123-456-7890",
"name": "Spring search",
"type": "search",
"budget": { "amount": 20, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/spring",
"headlines": ["Spring sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"keywords": [{ "text": "running shoes", "matchType": "phrase" }]
}
}
}
JSON
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
A successful create answers `status: "success"`, the `campaignId`, and the campaign's own state as `campaignStatus` (`active` or `paused`). Get and update answer the same way.
## Get a Campaign
`GET /ads/{platform}/campaigns/{id}`
Returns the campaign as the network reports it. A campaign that is not yours, or that does not exist, answers `code: 400`.
## Update a Campaign
`PUT /ads/{platform}/campaigns/{id}`
Send at least one of these fields. You cannot change `type`, `accountId`, or `euPoliticalAdvertising` after you create the campaign.
**name** `string`
The new campaign name.
**status** `string`
`active` or `paused`. A request that only sends `status: "paused"` is not blocked when Ayrshare has paused ads on that network. It still needs a linked network, and it can still fail if the network itself is down.
**budget** `object`
`{ "amount": number }`: the new amount to spend per day.
**schedule** `object`
`{ "end": string }`: the new end date, in the same format as `schedule.start`. It must be after the campaign's start. A network that schedules whole days, such as Google, also accepts the same day as the start.
## Errors
These errors carry an Ayrshare `code`:
| Code | When |
| - | - |
| `101` | The request is invalid, the network does not support campaigns or this campaign type, or the idempotency key is missing. `details` says which. Ayrshare checks for the key after it confirms the network is linked, so an unlinked profile gets the "not linked" error first. |
| `400` | The campaign is not yours or does not exist. |
| `424` | Ads on this network are temporarily unavailable. |
A create can also answer with one of these HTTP statuses. Its body has no `code`, only `{ "action": "createAdCampaign", "status": "error", "message": "..." }`:
| HTTP status | When |
| - | - |
| `409` | The idempotency key was used with a different request, or the request with this key is still in progress. Do not retry with a new key. Check the ad account first. |
| `503` | Ayrshare could not safely reserve the idempotency key, so nothing was created. Retry later with the same key. |
# Boost Post
Source: https://www.ayrshare.com/docs/apis/ads/facebook/boost-post
POST /ads/facebook/boost
Boost an existing Facebook post into a paid ad with custom targeting, budget, scheduling, and ad goals, plus the payment method and settings Meta requires.
Boost an existing Facebook post to create an ad.
This endpoint allows you to convert your organic posts into paid advertisements with custom targeting, budget, and scheduling parameters.
Budget and bid amounts are in the ad account's currency. Use up to two decimal places, or whole numbers for the currencies Meta takes in whole units: CLP, COP, CRC, HUF, IDR, ISK, JPY, KRW, PYG, TWD and VND.
The ad must run for at least 30 hours to address the Facebook requirement.
You can use the [interests endpoint](/docs/apis/ads/facebook/get-ad-interests) to find interest IDs
for targeting.
Facebook may take up to 24 hours to review and approve boosted posts.
If using `fbPostId` directly (instead of Ayrshare `postId`), ensure it's a valid Facebook post
ID.
If a boost fails after Facebook has created its campaign (at the creative, ad set or ad step),
we try to delete that campaign. If the delete also fails, the empty campaign stays in your ad
account. You can find and delete it in Ads Manager.
> [!NOTE]
>
> Meta refuses the ad until both of these are in place:
>
> * **A payment method on the ad account.** Add one in Meta's [Billing & payments](https://business.facebook.com/billing_hub/payment_settings/?placement=ads_manager) and attach it to the ad account you send as `accountId`. A payment method saved only on the business portfolio is not enough.
> * **Personalized ads turned on (EU, EEA and Switzerland).** If the owner of the Facebook or Instagram account linked to your Facebook Page chose to see less-personalized ads, or subscribed to use Meta without ads, Meta does not let that account or the Page run ads. The owner can switch back to personalized ads, or cancel the subscription, in Accounts Center → Ad preferences → Ad experience. [Learn more](https://www.facebook.com/business/help/1563729837497242).
### Ad Goals
Each ad must have a goal. The goal determines how the ad will be optimized for display.
`engagement`: This goal seeks to increase engagement while ensuring that the ad reaches the
maximum number of unique users. It balances visibility with engagement, showing the ad to as
many different people as possible who may interact with it.
`interactions`: Designed to boost interactions, such as likes, comments, and shares, on the ad.
Facebook prioritizes showing the ad to users most likely to engage with it.
`awareness_views`: Focuses on increasing brand awareness by maximizing the number of times the
ad is displayed. It prioritizes showing the ad as many times as possible within the budget,
regardless of unique reach.
`awareness_audience`: Aims to enhance brand awareness by maximizing the number of unique people
who see the ad. It ensures that the ad reaches as many different users as possible and maximizes
the unique audience size rather than showing it multiple times to the same audience.
`traffic`: Sends people off Facebook to a destination you choose, such as your website, a
landing page, or a job posting. Facebook optimizes for link clicks rather than on-platform
engagement. This is the only goal that supports a
[`callToAction`](/docs/apis/ads/facebook/boost-post#param-call-to-action) button that opens a link.
Learn more about integrating the Facebook Marketing API with Ayrshare in our [Facebook Marketing API Integration Guide](https://www.ayrshare.com/blog/facebook-ads-api-boosting-with-the-marketing-api/).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**idempotencyKey** `string`
A unique, non-empty key for this boost request. Retry the same payload with the same key to avoid creating a duplicate ad. Reusing the key with a different payload, or for an Instagram boost, returns HTTP 409. On any other platform, the same key starts a separate boost. A request still in progress also returns 409; an unavailable reservation returns 503 without creating an ad.
**accountId** `string` *required*
The ID of the Facebook ad account to boost the post on. The account ID can be retrieved from the
[ad accounts endpoint](/docs/apis/ads/facebook/get-ad-accounts).
**adName** `string` *required*
Name for your ad (appears in Facebook Ad Manager) with the format `{adName} - {postId or fbPostId} - {current date}`. If you send an `idempotencyKey`, the name also ends in ` - {request reference}`, a short reference that stays the same for that key.
**bidAmount** `number` *required*
Maximum bid amount in the ad account's currency, as a positive amount.
**budget** `number` *required*
Daily budget in the ad account's currency, as a positive amount. Meta may enforce minimums for your account and goal.
**fbPostId** `string` *required*
The [Facebook social post ID](/docs/apis/overview#social-post-id) of the post to boost, which allows
you to create an ad from a post created directly on Facebook. This is the ID of the [post on
Facebook](/docs/apis/history/history-platform), not Ayrshare. Required if `postId` is not set.
**goal** `string` *required* (default: engagement)
The goal of the ad. Values: `engagement`, `interactions`, `awareness_views`,
`awareness_audience`, and `traffic`. See [ad goals
details](/docs/apis/ads/facebook/boost-post#ad-goals) above for more information.
**locations** `object`
Target ad locations with an object of arrays: `countries`, `regions`, `cities`.
An object that names no country, region or city, such as `{}`, is refused with code `101`; leave `locations` out to target the US.
**child attributes**
**countries** `array`
List of country codes.
```json theme={"system"}
{
"countries": ["US", "CA"]
}
```
**regions** `array`
Regions require Facebook's region `key` value. See the [regions endpoint](/docs/apis/ads/facebook/get-ad-regions) for more information.
```json theme={"system"}
{
"regions": [{ "key": "3886" }]
}
```
**cities** `array`
Cities require: `key` (from Facebook), `radius`, and `distance_unit`.
`radius` is the distance around the city: 10–50 miles or 17–80 kilometers.
`distance_unit` is `mile` or `kilometer`.
See the [cities endpoint](/docs/apis/ads/facebook/get-ad-cities) for more information.
```json theme={"system"}
{
"cities": [
{ "key": "2420605", "radius": 25, "distance_unit": "mile" }
]
}
```
**postId** `string` *required*
The [Ayrshare post ID](/docs/apis/overview#ayrshare-post-id) of the post to boost. Required if
`fbPostId` is not set.
**status** `string` *required* (default: active)
The status of the ad. Values: `active` and `paused`.
You can later change the status of the ad using the [update ad endpoint](/docs/apis/ads/facebook/put-ad-update).
**adSetBudgetSharingEnabled** `boolean` (default: false)
Controls Meta's campaign-level ad set budget sharing.
Meta now requires this wire field on every campaign created without a campaign-level budget, so Ayrshare always sends it — defaulting to `false` for backward compatibility.
Set to `true` to let Meta share up to \~20% of an ad set's budget across other ad sets in the same campaign to optimize overall performance. See Meta's [ad campaign group reference](https://developers.facebook.com/docs/marketing-api/reference/ad-campaign-group/) for details.
**callToAction** `object`
Add a call-to-action button, such as **Apply Now** or **Shop Now**, to the boosted post. The button opens the URL you supply in `link`, or, with `destination`, a Messenger, WhatsApp or Instagram Direct chat instead (see [Open a Conversation Instead of a Link](/docs/apis/ads/facebook/boost-post#message-destination)).
```json theme={"system"}
{
"callToAction": {
"type": "APPLY_NOW",
"link": "https://www.mysite.com/careers/social-media-manager",
"linkTitle": "Social Media Manager"
}
}
```
**child attributes**
**type** `string` *required*
The button label. Values: `LEARN_MORE`, `APPLY_NOW`, `SIGN_UP`, `SHOP_NOW`, `BOOK_TRAVEL`,
`GET_QUOTE`, `CONTACT_US`, `DOWNLOAD`, and `SUBSCRIBE`.
Case-insensitive on input, so `apply_now`, `Apply_Now`, and `APPLY_NOW` are all accepted.
With `destination`, use the messaging button instead: `MESSAGE_PAGE`, `WHATSAPP_MESSAGE`, or
`INSTAGRAM_MESSAGE`.
**link** `string`
The destination the button opens. Must be an absolute `http` or `https` URL. Relative paths,
`mailto:` links, and malformed URLs are rejected. Required unless `destination` is set.
**linkTitle** `string`
Optional headline displayed with the button.
**destination** `string`
Open a chat instead of a link: `messenger`, `whatsapp`, or `instagramDm`. Nested form only. See
[Open a Conversation Instead of a Link](/docs/apis/ads/facebook/boost-post#message-destination).
> [!WARNING]
>
> A `callToAction` that opens a link is only supported with the `traffic` [ad goal](/docs/apis/ads/facebook/boost-post#ad-goals). A button with `destination` uses `engagement` or `interactions` instead, as described in [Open a Conversation Instead of a Link](/docs/apis/ads/facebook/boost-post#message-destination).
> Sending a link button with `engagement`, `interactions`, `awareness_views`, or `awareness_audience` returns a `400` with `code: 101` naming the compatible goals.
> Previously the value was accepted on any goal and then silently ignored, producing an ad with no button.
`callToAction.link` supplies the destination URL. When Meta renders the call to action as a button on the existing post, `urlTags` append to that button link. `goal: "traffic"` without a `callToAction` is allowed and creates a traffic ad with no button.
The `callToAction` you send is echoed back in the boost response with `type` upper-cased — see the *Post Boosted with a Call to Action* example — and is also returned on the ad afterwards by [Get Boosted Ads](/docs/apis/ads/facebook/get-boosted-ads).
To open a Messenger, WhatsApp or Instagram Direct chat instead of a link, see [Open a Conversation Instead of a Link](/docs/apis/ads/facebook/boost-post#message-destination).
**tracking** `object`
Track the ad using a Facebook Pixel.
**child attributes**
**pixelId** `number` *required*
The ID of the Facebook Pixel to track the ad.
```json theme={"system"}
{
"pixelId": 1234567890
}
```
**urlTags** `array`
Add UTM tags to the ad URL.
```json theme={"system"}
{
"urlTags": ["utm_source=ayrshare", "utm_medium=social", "utm_campaign=ayrshare-social"]
}
```
For example if the linking URL is `https://www.mysite.com/my-post` and the URL tags added are:
`utm_source=ayrshare`, `utm_medium=social`, and `utm_campaign=ayrshare-social`.
The ad URL will be:
`https://www.mysite.com/my-post?utm_source=ayrshare&utm_medium=social&utm_campaign=ayrshare-social`.
**specialAdCategories** `array`
Meta requires advertisers in [special ad categories](https://www.facebook.com/business/help/298000447747885?helpref=faq_content) to must self-identify their campaign category.
If your business is in one of these categories, you must select the appropriate category when boosting your post.
The following values are supported:
`housing`: Ads that promote or directly link to a housing opportunity or related service,
including but not limited to listings for the sale or rental of a home or apartment, homeowners
insurance, mortgage insurance, mortgage loans, housing repairs and home equity or appraisal
services.
`financial_product_services`: Ads that promote or directly link to a financial products and
services offer, including credit. `financial_products_services` is accepted as an equivalent
spelling, and `credit` is accepted as Meta's former name for this category — all three reach
Meta as the same category, so send whichever you already have.
`employment`: Ads that promote or directly link to an employment opportunity, including but not
limited to part- or full-time jobs, internships or professional certification programs. Related
ads that fall within this category include promotions for job boards or fairs, aggregation
services or ads detailing perks a company may provide, regardless of a specific job offer.
`none`: The default. Declares that the campaign is not in any special ad category. Sending it
explicitly is equivalent to omitting `specialAdCategories` altogether, and is accepted so a
caller that always sends the field can say "no category" without special-casing it.
`issues_elections_politics`: Ads made by, on behalf of, or about a candidate for public office,
a political figure, a political party or advocating for the outcome of an election to public
office. This also includes ads about any election, referendum or ballot initiative, including
"Go out and vote" election campaigns. Ads regulated as political advertising or about social
issues in any place where the ad is being placed. If selecting issues, elections, or politics,
you must select the country in which you want to run these ads. You are required to be
authorized to run ads about social issues, elections, or politics in the specified country.
Values are case-insensitive. An unrecognized value is rejected with a `400` and `code: 101` listing the accepted values. Previously an unrecognized value was silently dropped and the campaign was created as uncategorized, so a typo left a regulated ad running with no category declared.
The accepted-values list in that error message is written in Meta's upper-case form and also names the alternative spellings above, so it reads `NONE, HOUSING, FINANCIAL_PRODUCTS_SERVICES, EMPLOYMENT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCT_SERVICES, CREDIT`. Those are the same five categories listed here — `FINANCIAL_PRODUCT_SERVICES` and `CREDIT` are the equivalents of `financial_product_services`, not extra categories. The identical message is returned by the [interests endpoint](/docs/apis/ads/facebook/get-ad-interests#param-special-ad-categories).
Repeating a category, or sending two spellings of the same one such as `["credit", "financial_product_services"]`, is accepted and collapses to a single category before it reaches Meta. An empty value is rejected with a `400` and `code: 101`.
To find interest IDs that Meta allows for one of these categories, pass the same value to the [interests endpoint](/docs/apis/ads/facebook/get-ad-interests#param-special-ad-categories).
Meta also needs the countries a special category ad runs in. Ayrshare takes them from `locations`: the countries you list, plus the country of each region and city. So a `housing` boost that targets a city in the UK is set up for the UK, with nothing extra in your request. Without `locations` the ad runs in the US, and the campaign is set up for the US.
Meta also sets a minimum radius around each city for these categories. A UK city with a 17 kilometer radius was rejected with `#2909039`, which asks for at least 25 kilometers (15 miles), so use 25 kilometers or more.
> [!WARNING]
>
> Meta requires advertisers who have self-identified their campaign category.
> Meta uses human reviewers and machine-learning to identify these kinds of ads.
> If you send us an incorrect `specialAdCategory`, there is a risk your ads will be paused until the campaign is adjusted.
**endDate** `string`
End date and time in ISO 8601 format (must be at least 30 hours after start), for example `2025-03-01T00:00:00Z`.
If not set, the ad will run indefinitely and have end date of `ongoing`.
**excludedLocations** `object`
Exclude locations using an object with arrays of `countries`, `regions`, and `cities`.
**child attributes**
**countries** `array`
List of country codes to exclude.
```json theme={"system"}
{
"countries": ["US", "CA"]
}
```
**regions** `array`
Regions require Facebook's region `key` value. See the [regions endpoint](/docs/apis/ads/facebook/get-ad-regions) for more information.
```json theme={"system"}
{
"regions": [{ "key": "3886" }]
}
```
**cities** `array`
Cities require: `key` (from Facebook), `radius`, and `distance_unit`.
`radius` is the distance around the city: 10–50 miles or 17–80 kilometers.
`distance_unit` is `mile` or `kilometer`.
See the [cities endpoint](/docs/apis/ads/facebook/get-ad-cities) for more information.
```json theme={"system"}
{
"cities": [
{ "key": "2420605", "radius": 25, "distance_unit": "mile" }
]
}
```
**gender** `string` (default: all)
The gender of the audience. Values: `all`, `male`, `female`.
**interests** `array`
The target interests of the ad as an array of Facebook [interest
ids](/docs/apis/ads/facebook/get-ad-interests).
**maxAge** `number` (default: 65)
Maximum age for targeting the ad (default: 65).
**minAge** `number` (default: 18)
Minimum age for targeting the ad (default: 18).
**startDate** `string`
Start date and time in ISO 8601 format, for example `2025-03-01T00:00:00Z`.
If not set, the ad will start immediately.
**dsaBeneficiary** `string`
The beneficiary of the ad for Digital Services Act (DSA) compliance for EU countries. Must be set
together with `dsaPayor` if either is provided. Please refer to our [DSA
guide](/docs/apis/ads/facebook/get-dsa-recommendations) for more information.
**dsaPayor** `string`
The payor of the ad for Digital Services Act (DSA) compliance for EU countries. Must be set
together with `dsaBeneficiary` if either is provided.
## Lifetime Budgets
The body above sets a daily budget on the ad set. For a lifetime budget, send the nested form of the boost body instead.
In the nested form, `accountId`, `postId` (or `socialPostId` in place of `fbPostId`), `goal`, `status` and `callToAction` stay at the top level. Three objects replace the flat budget, date and targeting fields: `budget`, `schedule` (`start`, `end`) and `targeting` (`locations`, `excludedLocations`, `ageMin`, `ageMax`, `genders`, `interests`). The remaining fields, `adName`, `bidAmount`, `specialAdCategories`, `adSetBudgetSharingEnabled`, `link`, `tracking`, `urlTags`, `dsaBeneficiary` and `dsaPayor`, move under `platformOptions.facebook`.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "traffic",
"budget": { "amount": 50, "type": "lifetime" },
"schedule": { "start": "2026-11-01T00:00:00Z", "end": "2026-11-08T00:00:00Z" },
"targeting": { "ageMin": 25, "ageMax": 45, "locations": { "countries": ["US"] } },
"platformOptions": { "facebook": { "adName": "November boost", "bidAmount": 2 } }
}
```
* `budget.type`: `daily` (the default) or `lifetime`. A lifetime budget is spent between the start and end dates, so it needs `schedule.end`. Without one the request is rejected with a `400` and `code: 101`. Meta's ad set budget sharing works with daily budgets only, so a lifetime budget with `adSetBudgetSharingEnabled: true` is also rejected with `code: 101`.
The response adds `budgetType: "lifetime"` for a lifetime budget. A daily budget does not return `budgetType`, so treat a missing field as `daily`.
## Campaign Budgets
To hold the budget on the campaign instead of the ad set, send `budget.level: "campaign"` in the nested form shown above. The ad set then has no budget of its own. It works with daily and lifetime budgets.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "traffic",
"budget": { "amount": 50, "type": "lifetime", "level": "campaign" },
"schedule": { "start": "2026-11-01T00:00:00Z", "end": "2026-11-08T00:00:00Z" },
"platformOptions": { "facebook": { "adName": "November boost", "bidAmount": 2 } }
}
```
* `budget.level`: `adSet` (the default) or `campaign`. A campaign budget cannot be combined with `adSetBudgetSharingEnabled`. Sending both is rejected with a `400` and `code: 101`.
* The response adds `budgetLevel: "campaign"` for a campaign budget. An ad set budget does not return `budgetLevel`, so treat a missing field as `adSet`.
* In [History](/docs/apis/ads/facebook/get-ad-history), `budgetDaily` shows the campaign's daily budget for an ad with a daily campaign budget. If several ads share one campaign budget, each shows the whole campaign budget, not its share. History does not show lifetime budgets, on the campaign or the ad set. Once a lifetime campaign budget is spent, every ad in that campaign shows `deliveryStatus: "COMPLETED"` after the next nightly update, including an ad that is scheduled or in review. A rejected ad stays `REJECTED`.
## Boost into an Existing Campaign or Ad Set
By default every boost creates its own campaign, ad set and ad. To add the boost to a campaign or ad set you already run in Meta, send the nested form with `attachTo`.
* `attachTo.campaignId`: the boost creates a new ad set and ad inside that campaign. Send `budget`, `targeting` and `bidAmount` as usual: they apply to the new ad set.
* `attachTo.adSetId`: the boost creates only the ad, inside that ad set. Also send `attachTo.campaignId`, the campaign the ad set belongs to. Leave out `budget`, `targeting` and `platformOptions.facebook.bidAmount`: the ad set keeps its own. Sending any of them returns a `400` with `code: 101`.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"attachTo": { "campaignId": "120210000000000001", "adSetId": "120210000000000002" },
"platformOptions": { "facebook": { "adName": "November boost" } }
}
```
* The campaign or ad set must be in the ad account named by `accountId`, and must not belong to another user's boosts. Otherwise the request returns a `400`. A Facebook boost also cannot attach to a campaign an Instagram boost created, and the reverse.
* `attachTo` cannot be combined with `budget.level: "campaign"`, `specialAdCategories` or `adSetBudgetSharingEnabled`, which are set on a new campaign. Sending them returns `code: 101`.
* Pausing or activating the boost changes only what the boost created: the ad, and the ad set when the boost created it. The campaign or ad set you named in `attachTo` keeps its own status. The ad runs only while the campaign and ad set above it are active. This also applies when Ayrshare pauses your ads, for example when a profile is deleted.
* If the boost fails, the campaign or ad set named in `attachTo` is never deleted.
* If the ad set you attach to sits in a campaign that holds the budget (a campaign budget), [History](/docs/apis/ads/facebook/get-ad-history) reports the boost like any ad with a campaign budget: `budgetDaily` shows the whole campaign budget, not the boost's share, and once a lifetime campaign budget is spent, every ad Ayrshare tracks in that campaign shows `deliveryStatus: "COMPLETED"` after the next nightly update, including an ad that is scheduled or in review. A rejected ad stays `REJECTED`.
## Open a Conversation Instead of a Link
A boost's button can open a chat with your business instead of a web page. Send the nested form with `callToAction.destination`, and leave out `callToAction.link`.
| `destination` | Opens | `callToAction.type` |
| - | - | - |
| `messenger` | Messenger | `MESSAGE_PAGE` |
| `whatsapp` | WhatsApp | `WHATSAPP_MESSAGE` |
| `instagramDm` | Instagram Direct | `INSTAGRAM_MESSAGE` |
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"budget": { "amount": 20 },
"callToAction": { "type": "WHATSAPP_MESSAGE", "destination": "whatsapp" },
"platformOptions": { "facebook": { "adName": "Chat with us", "bidAmount": 2 } }
}
```
* Use the `engagement` or `interactions` [ad goal](/docs/apis/ads/facebook/boost-post#ad-goals). The ad is then optimized for conversations: Meta shows it to people likely to start a chat. Other goals return a `400` with `code: 101`.
* `callToAction.type` must match the destination, as in the table. Any casing is accepted.
* `callToAction.link` and `callToAction.linkTitle` cannot be sent with `destination`, because the button opens a chat, not a page. Without `destination`, `link` is still required.
* People message the account linked to your profile. For `whatsapp`, its Facebook Page must have a WhatsApp Business number connected in Meta.
* `destination` cannot be combined with `attachTo.adSetId`: the existing ad set keeps the destination it already has. With `attachTo.campaignId`, the campaign must have Meta's `OUTCOME_ENGAGEMENT` objective; otherwise the request returns a `400` with `code: 101`.
* The response, and [Get Boosted Ads](/docs/apis/ads/facebook/get-boosted-ads), return `callToAction` with its `destination`.
```bash cURL theme={"system"}
curl -X POST https://api.ayrshare.com/api/ads/facebook/boost \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": { "countries": ["US"] },
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": [1234567890, 1234567891]
}'
```
```bash cURL: traffic goal with a call to action theme={"system"}
curl -X POST https://api.ayrshare.com/api/ads/facebook/boost \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{
"postId": "1234567890",
"accountId": "1234567890",
"adName": "Nurse Practitioner Hiring",
"status": "active",
"goal": "traffic",
"callToAction": {
"type": "APPLY_NOW",
"link": "https://example.com/careers/nurse-practitioner",
"linkTitle": "Apply today"
},
"specialAdCategories": ["employment"],
"minAge": 18,
"maxAge": 65,
"locations": { "countries": ["US"] },
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z"
}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/boost", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
postId: "1234567890",
accountId: "1234567890",
adName: "My Ad",
status: "active",
goal: "engagement",
minAge: 18,
maxAge: 65,
locations: { countries: ["US"] },
budget: 100,
bidAmount: 1,
startDate: "2025-03-01T00:00:00Z",
endDate: "2025-03-07T23:59:59Z",
interests: [1234567890, 1234567891]
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
API_KEY = "API_KEY"
url = "https://api.ayrshare.com/api/ads/facebook/boost"
payload = {
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": {"countries": ["US"]},
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": [1234567890, 1234567891]
}
response = requests.post(url, headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}, json=payload)
print(response.json())
```
```php PHP theme={"system"}
"1234567890",
"accountId" => "1234567890",
"adName" => "My Ad",
"status" => "active",
"goal" => "engagement",
"minAge" => 18,
"maxAge" => 65,
"locations" => ["countries" => ["US"]],
"budget" => 100,
"bidAmount" => 1,
"startDate" => "2025-03-01T00:00:00Z",
"endDate" => "2025-03-07T23:59:59Z",
"interests" => [1234567890, 1234567891]
];
$ch = curl_init("https://api.ayrshare.com/api/ads/facebook/boost");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer " . $API_KEY
]);
$response = curl_exec($ch);
$result = json_decode($response, true);
print_r($result);
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string API_KEY = "API_KEY";
var payload = new
{
postId = "1234567890",
accountId = "1234567890",
adName = "My Ad",
status = "active",
goal = "engagement",
minAge = 18,
maxAge = 65,
locations = new { countries = new[] { "US" } },
budget = 100,
bidAmount = 1,
startDate = "2025-03-01T00:00:00Z",
endDate = "2025-03-07T23:59:59Z",
interests = new[] { 1234567890, 1234567891 }
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", API_KEY);
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync("https://api.ayrshare.com/api/ads/facebook/boost", content);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
payload := map[string]interface{}{
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": map[string]interface{}{"countries": []string{"US"}},
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": []int{1234567890, 1234567891},
}
jsonData, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error marshaling JSON:", err)
return
}
req, err := http.NewRequest("POST", "https://api.ayrshare.com/api/ads/facebook/boost", bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public class BoostPost {
public static void main(String[] args) {
String API_KEY = "API_KEY";
String url = "https://api.ayrshare.com/api/ads/facebook/boost";
Map payload = new HashMap<>();
payload.put("postId", "1234567890");
payload.put("accountId", "1234567890");
payload.put("adName", "My Ad");
payload.put("status", "active");
payload.put("goal", "engagement");
payload.put("minAge", 18);
payload.put("maxAge", 65);
Map locations = new HashMap<>();
locations.put("countries", Arrays.asList("US"));
payload.put("locations", locations);
payload.put("budget", 100);
payload.put("bidAmount", 1);
payload.put("startDate", "2025-03-01T00:00:00Z");
payload.put("endDate", "2025-03-07T23:59:59Z");
payload.put("interests", Arrays.asList(1234567890, 1234567891));
try {
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(payload);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
require 'json'
API_KEY = "API_KEY"
uri = URI.parse("https://api.ayrshare.com/api/ads/facebook/boost")
payload = {
postId: "1234567890",
accountId: "1234567890",
adName: "My Ad",
status: "active",
goal: "engagement",
minAge: 18,
maxAge: 65,
locations: { countries: ["US"] },
budget: 100,
bidAmount: 1,
startDate: "2025-03-01T00:00:00Z",
endDate: "2025-03-07T23:59:59Z",
interests: [1234567890, 1234567891]
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer #{API_KEY}"
request.body = payload.to_json
response = http.request(request)
puts response.body
```
```json 200: Post Boosted theme={"system"}
{
"status": "success",
"adId": "120217670757750410",
"adName": "API Post - DE6gpw8kxlonHy2b7Lo - 2025-03-26T23:42:43",
"adStatus": "active",
"bidAmount": 10,
"budget": 100,
"endDate": "2026-03-28T22:30:00Z",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement...",
"type": "engagement"
},
"interests": [
"6003195554098"
],
"locations": { "countries": ["US"] },
"maxAge": 65,
"minAge": 18,
"postId": "DE6gpw8kxlonHy2b7Lo",
"startDate": "2026-03-26T22:30:00Z"
}
```
```json 200: Post Boosted with a Call to Action theme={"system"}
{
"status": "success",
"adId": "120217670757750411",
"adName": "Nurse Practitioner Hiring - DE6gpw8kxlonHy2b7Lo - 2025-03-26T23:42:43",
"adStatus": "active",
"bidAmount": 1,
"budget": 100,
"callToAction": {
"type": "APPLY_NOW",
"link": "https://example.com/careers/nurse-practitioner",
"linkTitle": "Apply today"
},
"endDate": "2025-03-07T23:59:59Z",
"goal": {
"title": "Drive Traffic",
"description": "Sends people from the boosted post to a destination off Facebook...",
"type": "traffic"
},
"locations": { "countries": ["US"] },
"maxAge": 65,
"minAge": 18,
"postId": "DE6gpw8kxlonHy2b7Lo",
"startDate": "2025-03-01T00:00:00Z"
}
```
```json 400: Invalid Account ID theme={"system"}
{
"action": "boost post",
"status": "error",
"code": 369,
"message": "Unable to boost post. Please try again or contact us if the issue persists.",
"details": "The accountId likely does not exist. Please check the accountId and try again."
}
```
```json 400: Missing Required Parameters theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "Missing required fields: budget"
}
```
# Accounts
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-accounts
GET /ads/facebook/accounts
List the Facebook ad accounts linked to the authenticated profile, with account status, aggregate performance metrics, and results cached for 10 minutes.
Retrieve available Facebook ad accounts associated with the authenticated profile.
This endpoint allows you to access basic account information as well as key metrics for the ad accounts.
The `metrics` object contains aggregate performance data across all ads in
the account
Results are cached for 10 minutes to optimize performance and reduce API
calls to Facebook
Account status in the JSON response may be one of: `active`, `disabled`,
`unsettled`, `pending review`, or `closed`
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**limit** `number` (default: 100)
Limit the number of ad accounts returned.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/accounts
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/accounts", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/accounts', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/accounts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
]
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string apiKey = "API_KEY";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
HttpResponseMessage response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/accounts");
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/accounts", nil)
req.Header.Add("Authorization", "Bearer " + apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetAdAccounts {
public static void main(String[] args) {
try {
String apiKey = "API_KEY";
URL url = new URL("https://api.ayrshare.com/api/ads/facebook/accounts");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/facebook/accounts')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"adAccounts": [
{
"accountId": "274948345",
"ageInDays": 2870.86,
"amountSpent": 191.33,
"balance": 7.84,
"budgetRemaining": 1,
"business": {
"name": "Fun Fun Fun",
"city": "New York",
"country": "US",
"state": "NY",
"street": "178 Columbus Ave",
"zip": "10023"
},
"created": "2012-01-05T03:11:31-0800",
"currency": "USD",
"disableReason": null,
"fundingSource": {
"id": "13157487252022",
"type": "Credit Card"
},
"hasNotificationsEnabled": true,
"isPersonal": false,
"isPrepayAccount": false,
"metrics": {
"spend": 191.33,
"impressions": 24994,
"reach": 20410,
"clicks": 622,
"ctr": 2.488597,
"cpm": 7.655037,
"cpp": 9.374326,
"frequency": 1.224596,
"uniqueClicks": 432,
"uniqueCtr": 2.11661,
"costPerUniqueClick": 0.442894,
"inlineLinkClicks": 538,
"costPerInlineLinkClick": 0.355632,
"outboundClicks": 468,
"costPerOutboundClick": 0.408825,
"websiteCtr": [
{
"action_type": "link_click",
"value": "2.152517"
}
],
"accountCurrency": "USD",
"accountName": "John Smith",
"accountId": "274948321"
},
"minDailyBudget": 1,
"name": "John Smith",
"owner": "3205611418413",
"spendCap": 0,
"status": "Active",
"tax": {
"id": "824087134",
"type": "Business Tax ID (EIN/SSN)",
"status": "Pending",
"isRequired": false
},
"timezoneName": "America/Los_Angeles",
"tosAccepted": {
"webCustomAudienceTos": true,
"customAudienceTos": true
}
}
],
"count": 1,
"lastUpdated": "2025-03-26T19:33:51.571Z",
"nextUpdate": "2025-03-26T19:44:51.571Z"
}
```
```json 400: Ad accounts error theme={"system"}
{
"action": "get ad accounts",
"status": "error",
"code": 366,
"message": "Error getting ad accounts. Please verify you have an active ad account at Facebook."
}
```
# Campaigns
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-campaigns
GET /ads/facebook/campaigns
Fetch campaigns for one Facebook ad account by account ID, including remaining budget, campaign status, performance metrics, and scheduling details.
Retrieve ad campaigns for a specific Facebook ad account. This endpoint provides detailed information about campaigns, including budget, status, performance metrics, and scheduling details.
Campaign status may be one of: "ACTIVE", "PAUSED", "DELETED", "ARCHIVED", "IN\_PROCESS", or "WITH\_ISSUES"
The `metrics` object contains performance data for the campaign
The `budgetRemaining` field represents the remaining budget in the account's currency
Results are cached for 10 minutes to optimize performance
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
The ID of the ad account to retrieve campaigns for.
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
**limit** `number` (default: 100)
Limit the number of campaigns returned.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
[
'header' => "Authorization: Bearer " . $apiKey,
'method' => 'GET'
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$result = json_decode($response, true);
print_r($result);
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string apiKey = "API_KEY";
string url = "https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
HttpResponseMessage response = await client.GetAsync(url);
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
url := "https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer " + apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
public static void main(String[] args) {
try {
String apiKey = "API_KEY";
URL url = new URL("https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
require 'json'
api_key = "API_KEY"
uri = URI.parse("https://api.ayrshare.com/api/ads/facebook/campaigns?accountId=1234567890")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)
```
```json 200: Success theme={"system"}
{
"status": "success",
"campaigns": [
{
"accountId": "8501481455633",
"boostedObjectId": "67263320208733",
"budgetRemaining": 0,
"buyingType": "AUCTION",
"campaignId": "1202176707545833",
"configuredStatus": "PAUSED",
"created": "2025-03-26T19:42:43-0400",
"dailyBudget": 0,
"effectiveStatus": "ACTIVE",
"endDate": "2026-03-28T18:30:00-0400",
"lifetimeBudget": 0,
"metrics": {},
"name": "API Post - DE6gpw8kxlonHy6eb33 - 2025-03-26T23:42:43",
"spendCap": 0,
"startDate": "2026-03-26T18:30:00-0400",
"status": "ACTIVE"
}
],
"count": 1,
"lastUpdated": "2025-03-27T00:57:15.339Z",
"nextUpdate": "2025-03-27T01:08:15.339Z"
}
```
```json 400: Ad campaigns error theme={"system"}
{
"action": "get ad campaigns",
"status": "error",
"code": 367,
"message": "Error getting ad campaigns. Please verify you have an active ad campaign account."
}
```
# Cities
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-cities
GET /ads/facebook/cities
Search Facebook cities by name for targeting when boosting an ad. Each result includes its parent region, radius targeting support, and targeting flags.
Search for cities to be used for targeting when boosting an ad.
Cities use Facebook's `key` identifier and support radius targeting.
Each city result includes its parent region as `region` (the region name) and `regionId` (the region's numeric identifier), so you can target the city's region when it is available.
If a returned city has `supportsRegion` `true`, the region for this city is available for targeting. If `supportsCity` is `true`, this city is available for targeting.
Some locations may be returned as `subcity` with additional hierarchy fields.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string`
The search string to filter cities. Provide a partial or full city name.
**limit** `number` (default: 25)
Maximum number of cities to return.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdCities {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/facebook/cities?search=Manhattan')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"cities": [
{
"key": "2447439",
"name": "Manhattan",
"type": "city",
"countryCode": "US",
"countryName": "United States",
"region": "Kansas",
"regionId": 3859,
"supportsRegion": true,
"supportsCity": true
},
{
"key": "2703980",
"name": "Manhattan",
"type": "subcity",
"countryCode": "US",
"countryName": "United States",
"region": "New York",
"regionId": 3875,
"supportsRegion": true,
"supportsCity": true,
"geoHierarchyLevel": "SUBCITY",
"geoHierarchyName": "BOROUGH"
}
],
"count": 2
}
```
```json 414: Location metadata error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 414,
"message": "Error getting ad location metadata. Please try again."
}
```
# History
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-history
GET /ads/facebook/history
Retrieve daily Facebook ad spend and performance metrics for ads created with Boost Post, with goal-specific results, aggregated totals, and date filters.
Retrieve detailed daily ad spend and performance metrics for your Facebook ads.
This endpoint provides comprehensive analytics on your ads with daily breakdowns, goal-specific performance, and aggregated metrics.
> [!TIP]
>
> The `history` array contains historical daily spend and performance metrics
> for each ad. The data is as of 8 AM EST. If real-time spend data is needed or spend has not yet occurred,
> please use the [ads endpoint](/docs/apis/ads/facebook/get-boosted-ads).
>
> The ad will **NOT** be returned if no spend data is available or spend has not yet occurred.
>
> When no ad has spend in the date range, the response is still a success: `history` is
> empty and `warnings` holds one entry with code `401`.
Metrics are calculated from Facebook's reporting API and may have slight delays (up to 24
hours).
The `byGoalType` and `byGoalTitle` sections group performance metrics by goal objectives.
Use either `adId` or `postId` filters to narrow results to specific campaigns.
Each day's `spend` is in the ad account's currency, shown in `currency`. `spendUsd` is the same spend in US dollars.
Non-USD spend is converted to USD at that day's exchange rate from Open Exchange Rates. `totalSpend`, `cpm`, `cpp`, `cpc`, `costPerResult` and the goal `spend` totals are in US dollars.
`totalSpendByCurrency` gives the total spend in each currency before conversion. `unconvertedRows` counts the days not converted yet: they count at face value in `totalSpend`, and their `spendUsd` is `null`.
Spend from before October 2026 may show `currency: "USD"` even when the ad account uses another currency, and is counted in `totalSpend` as recorded.
The `goalPerformance` section highlights your best-performing ad objectives.
> [!NOTE]
>
> This endpoint only returns Facebook ads created via the [Boost Post](/docs/apis/ads/facebook/boost-post)
> endpoint. Ads created in Facebook Ads Manager or other third-party tools are not returned.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string`
Filter results to a specific account ID.
**adId** `string`
Filter results to a specific ad ID.
**postId** `string`
Filter results to ads associated with a specific [Ayrshare post
ID](/docs/apis/overview#ayrshare-post-id).
**fbPostId** `string`
Filter results to ads associated with a specific [Facebook social post
ID](/docs/apis/overview#social-post-id).
**limit** `number` (default: 25)
Retrieve a maximum of `limit` ads. Maximum is 300; higher values are capped at 300.
**startDate** `string` (default: 30 days ago)
Start date for the reporting period in ISO 8601 format (default: 30 days ago)
**endDate** `string` (default: today)
End date for the reporting period in ISO 8601 format (default: today)
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/history?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/history?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/history', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/history");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/history", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdSpend {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/history"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/facebook/history')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts response.body
```
```json 200: Success No Param theme={"system"}
{
"status": "success",
"history": [
{
"adId": "6683876017501",
"adName": "API Post 1 - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 3,
"impressions": 1035,
"reach": 1026,
"frequency": 1.01,
"ctr": 0,
"cpm": 1.53,
"cpp": 1.54,
"cpc": 0
},
"network": "facebook",
"postId": "DE6gpw8kxlonHy6eb7L1",
"spend": 1.58,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 1.58,
"status": "ACTIVE"
},
{
"adId": "120218167147110411",
"adName": "API Post 2 - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T16:41:15",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 4,
"impressions": 1036,
"reach": 1011,
"frequency": 1.02,
"ctr": 0,
"cpm": 1.61,
"cpp": 1.65,
"cpc": 0
},
"network": "facebook",
"postId": "DE6gpw8kxlonHy6eb7L1",
"spend": 1.67,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 1.67,
"status": "ACTIVE"
}
],
"dateRange": {
"start": "2025-02-24T20:02:34.613Z",
"end": "2025-03-26T20:02:34.613Z",
"postId": "eSNkaAiFZVin6wm6f"
},
"metrics": {
"totalSpend": 3.25,
"totalSpendByCurrency": { "USD": 3.25 },
"unconvertedRows": 0,
"totalClicks": 7,
"totalImpressions": 2071,
"totalReach": 2052,
"averageCTR": 0.1,
"averageFrequency": 1.02,
"cpm": 0.79,
"cpp": 0.85,
"cpc": 0.2,
"costPerResult": 1.2,
"byGoalType": {
"engagement": {
"spend": 7.62,
"impressions": 9332,
"clicks": 0,
"reach": 8502,
"count": 8,
"ctr": 0,
"cpm": 0.8165452207458208,
"cpp": 0.8962597035991532,
"cpc": 0
}
},
"byGoalTitle": {
"Get More Engagement": {
"spend": 3.25,
"impressions": 2071,
"clicks": 7,
"reach": 2052,
"count": 1,
"type": "engagement",
"ctr": 0.1,
"cpm": 0.79,
"cpp": 0.85,
"cpc": 0.2
}
},
"goalPerformance": {
"bestPerformingGoalType": {
"type": "engagement",
"clicks": 7,
"spend": 3.25,
"ctr": 0.1
},
"mostEfficientGoalType": {
"type": "engagement",
"cpc": 0.2,
"spend": 3.25
}
},
"count": 2,
"firstDay": "2025-03-17T00:00:00.000Z",
"lastDay": "2025-03-23T00:00:00.000Z"
}
}
```
```json 200: Success by Ad ID theme={"system"}
{
"status": "success",
"history": [
{
"accountId": "274948332",
"adId": "6683876017505",
"adName": "API Post 13 - DE6gpw8kxlonHy6eb7Lo - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"fbPostId": "106638148652329_672633202087926",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 2,
"cpc": 0,
"cpp": 1.6,
"cpm": 1.55,
"ctr": 0,
"frequency": 1.03,
"impressions": 452,
"reach": 438
},
"network": "facebook",
"postId": "DE6gpw8kxlonHy6eb7Lo",
"spend": 0.7,
"spendDate": "2025-04-03T00:00:00.000Z",
"spendUsd": 0.7,
"status": "ACTIVE"
},
{
"accountId": "274948332",
"adId": "6683876017505",
"adName": "API Post 13 - DE6gpw8kxlonHy6eb7Lo - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"fbPostId": "106638148652329_672633202087926",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 1,
"cpc": 0,
"cpp": 1.5,
"cpm": 1.5,
"ctr": 0,
"frequency": 1,
"impressions": 2793,
"reach": 2793
},
"network": "facebook",
"postId": "DE6gpw8kxlonHy6eb7Lo",
"spend": 4.19,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 4.19,
"status": "ACTIVE"
}
],
"dateRange": {
"start": "2025-03-04T18:49:18.159Z",
"end": "2025-04-03T18:49:18.159Z",
"adId": "6683876017505"
},
"metrics": {
"totalSpend": 4.89,
"totalSpendByCurrency": { "USD": 4.89 },
"unconvertedRows": 0,
"totalClicks": 3,
"totalImpressions": 10553,
"totalReach": 10339,
"averageCTR": 0.03,
"averageFrequency": 1.02,
"cpm": 1.460248270633943,
"cpp": 1.49047296643776,
"cpc": 5.136666666666667,
"costPerResult": 5.14,
"byGoalType": {
"engagement": {
"spend": 4.89,
"impressions": 10553,
"clicks": 3,
"reach": 10339,
"count": 2,
"ctr": 0.03,
"cpm": 1.46,
"cpp": 1.49,
"cpc": 5.14
}
},
"byGoalTitle": {
"Get More Engagement": {
"spend": 4.89,
"impressions": 10553,
"clicks": 3,
"reach": 10339,
"count": 2,
"type": "engagement",
"ctr": 0.03,
"cpm": 1.5,
"cpp": 1.49,
"cpc": 5.14
}
},
"goalPerformance": {
"bestPerformingGoalType": {
"type": "engagement",
"clicks": 3,
"spend": 4.89,
"ctr": 0.03
},
"mostEfficientGoalType": {
"type": "engagement",
"cpc": 5.14,
"spend": 4.89
}
},
"count": 2,
"firstDay": "2025-03-30T00:00:00.000Z",
"lastDay": "2025-04-03T00:00:00.000Z"
}
}
```
```json 200: No spend data theme={"system"}
{
"status": "success",
"history": [],
"dateRange": {
"start": "2025-03-04T18:49:18.159Z",
"end": "2025-04-03T18:49:18.159Z",
"adId": "6683876017505"
},
"metrics": {
"totalSpend": 0,
"totalSpendByCurrency": {},
"unconvertedRows": 0,
"totalClicks": 0,
"totalImpressions": 0,
"totalReach": 0,
"averageCTR": 0,
"averageFrequency": 0,
"cpm": 0,
"cpp": 0,
"cpc": 0,
"costPerResult": 0,
"byGoalType": {},
"byGoalTitle": {},
"goalPerformance": {
"bestPerformingGoalType": null,
"mostEfficientGoalType": null
},
"daysCount": 0,
"firstDay": null,
"lastDay": null
},
"warnings": [
{
"action": "ads",
"status": "error",
"code": 401,
"message": "No spend data found for the ad."
}
]
}
```
```json 400: Ad spend error theme={"system"}
{
"action": "get ad spend",
"status": "error",
"code": 367,
"message": "Error getting ad spend. Please verify you have an active ad account."
}
```
# Insights
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-insights
GET /ads/facebook/insights
Pull live daily Facebook ad metrics for campaigns, ad sets or ads, split by age, gender, country or placement, with paging for long date ranges.
Get live Facebook ad performance, one row per day, for your campaigns, ad sets or ads.
Pick the metrics you want, split them by age, gender, country or placement, and page through long date ranges.
Numbers are read from Meta when you call, not from a nightly copy.
Use [History](/docs/apis/ads/facebook/get-ad-history) for the spend we bill. Use Insights for reporting
and analysis.
All money values are in the ad account's currency, returned in `currency` on each row.
Campaigns must have been created through Ayrshare for this profile, and ad sets must be in such
a campaign. An ad created through
Ayrshare for another profile is refused. Other ad ids are read with this profile's linked Meta
account, so Meta only returns ads that account can see.
One request covers one ad account. Ids from several ad accounts return code `101`, and so do
several ad ids that Ayrshare has no record of. Ask for those one at a time.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**level** `string` *required*
What each row reports on: `campaign`, `adSet` or `ad`.
**ids** `string`
Comma-separated campaign, ad set or ad ids, up to 50. Required for `level=campaign` and
`level=adSet`.
With `level=ad`, you can leave `ids` out and send `accountId` instead. This reports on the ads
you created through Ayrshare for this profile in that ad account, not on every ad in it. It works
for up to 50 such ads. For more, send their ids, or report by campaign. If none are found, the
report is empty.
**accountId** `string`
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
Required with `level=ad` when `ids` is left out.
**metrics** `string`
Comma-separated metrics to return. Leave it out to get them all. See [Metrics](#metrics).
**breakdowns** `string`
Comma-separated breakdowns: `age`, `gender`, `country`, `placement`. Each day then has one row per
value, or per combination with `age,gender`. Send one breakdown at a time, or `age,gender`; Meta
refuses other mixes (such as `age,country`), so they return code `101`.
**startDate** `string` (default: 30 days ago)
First day, as `YYYY-MM-DD`. It can be up to 37 months ago.
**endDate** `string` (default: today)
Last day, as `YYYY-MM-DD`.
**cursor** `string`
The `nextCursor` from the previous response, to get the next page.
## Metrics
| Metric | Type | Meaning |
| - | - | - |
| `spend` | number | Amount spent |
| `impressions` | number | Times the ad was shown |
| `reach` | number | People who saw the ad at least once |
| `clicks` | number | Clicks |
| `cpm` | number or `null` | Cost per 1,000 impressions |
| `cpc` | number or `null` | Cost per click. `null` on a day with no clicks |
| `ctr` | number or `null` | Click-through rate, in percent |
| `actions` | array | Each action type Meta counted, with its count |
| `conversionValue` | number | Total value of purchases |
Facebook reports every metric in this table. One exception: Meta does not report `reach` for a report with
`breakdowns` that starts more than 13 months ago, so `reach` is left out of those rows. On networks that don't report a metric, it is left out
of the rows rather than returned as `0`, and asking for it returns code `101`.
> [!NOTE]
>
> Meta counts some actions under more than one type, so don't add `actions` values together. For
> example, `omni_purchase` already includes `purchase`. `conversionValue` counts each purchase once.
## Paging
Each response returns up to 500 rows. If there are more, `nextCursor` is set. Send the same request
again with `cursor` set to that value to get the next page. On the last page, `nextCursor` is `null`.
## Errors
| Code | When |
| - | - |
| `101` | A parameter is missing or wrong, a breakdown or metric isn't supported, `startDate` is more than 37 months ago, more than 50 ids are sent, the ids are in more than one ad account, or Meta refuses the breakdown combination |
| `400` | A campaign, ad set or ad isn't yours, or `accountId` isn't the ad account Ayrshare has on record for the ids you sent. For an ad set, `accountId` must also be the ad account Meta reports for it. For an ad with no Ayrshare record, `accountId` is not checked and the ad is read on its own |
| `370` | Meta could not return the insights. Try again |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/facebook/insights?level=campaign&ids=120200000000000001&startDate=2026-09-01&endDate=2026-09-02"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const params = new URLSearchParams({
level: "campaign",
ids: "120200000000000001",
startDate: "2026-09-01",
endDate: "2026-09-02",
});
fetch(`https://api.ayrshare.com/api/ads/facebook/insights?${params}`, {
method: "GET",
headers: { Authorization: `Bearer ${API_KEY}` },
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
params = {
'level': 'campaign',
'ids': '120200000000000001',
'startDate': '2026-09-01',
'endDate': '2026-09-02',
}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/insights', headers=headers, params=params)
print(r.json())
```
```json 200: Success theme={"system"}
{
"status": "success",
"platform": "facebook",
"level": "campaign",
"startDate": "2026-09-01",
"endDate": "2026-09-02",
"insights": [
{
"date": "2026-09-01",
"level": "campaign",
"id": "120200000000000001",
"currency": "USD",
"spend": 12.34,
"impressions": 4000,
"reach": 3100,
"clicks": 80,
"cpm": 3.085,
"cpc": 0.15425,
"ctr": 2,
"actions": [
{ "type": "link_click", "value": 75 },
{ "type": "omni_purchase", "value": 3 }
],
"conversionValue": 89.97
}
],
"count": 1,
"nextCursor": null
}
```
```json 200: Breakdowns theme={"system"}
{
"status": "success",
"platform": "facebook",
"level": "ad",
"startDate": "2026-09-01",
"endDate": "2026-09-01",
"insights": [
{
"date": "2026-09-01",
"level": "ad",
"id": "120210000000000001",
"age": "25-34",
"gender": "female",
"currency": "EUR",
"spend": 1.5,
"impressions": 300
},
{
"date": "2026-09-01",
"level": "ad",
"id": "120210000000000001",
"age": "25-34",
"gender": "male",
"currency": "EUR",
"spend": 1,
"impressions": 200
}
],
"count": 2,
"nextCursor": null
}
```
```json 400: Not Yours theme={"system"}
{
"action": "ads",
"status": "error",
"code": 400,
"message": "The ad is not found or not authorized."
}
```
# Interests
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-interests
GET /ads/facebook/interests
Search Facebook ad targeting interests by keyword for interest IDs and audience size estimates to use in Boost Post, or narrow to a special ad category.
Search for available Facebook ad targeting interests by keyword.
Use this endpoint to look up interest IDs for the [Boost Post](/docs/apis/ads/facebook/boost-post) endpoint.
This endpoint helps you discover interest-based targeting options for your Facebook ads, along with audience size estimates.
Interest IDs can be used in the `interests` array when boosting posts.
Results are cached for 10 minutes.
Use specific, relevant search terms for best results.
Facebook may return different results based on your ad account's industry and region.
The `audienceSizeLowerBound` and `audienceSizeUpperBound` fields in the JSON response provide an
estimated range of audience size for the interest and it is useful for targeting.
Use `specialAdCategories` to return only the interests Meta allows for a [special ad
category](/docs/apis/ads/facebook/boost-post#param-special-ad-categories) campaign. Filtered
requests also require `accountId`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string` *required*
The search query to retrieve Facebook ad interests.
Don't forget to escape the search query, for example `Rhythm%20and%20blues%20music`.
**specialAdCategories** `array`
Restrict the results to the interests Meta allows a [special ad category](/docs/apis/ads/facebook/boost-post#param-special-ad-categories) campaign to target. Use this before boosting a housing, employment, financial, or political post so you pick interest IDs the ad will actually be approved for.
The following values are supported, matching the [Boost Post](/docs/apis/ads/facebook/boost-post#param-special-ad-categories) parameter of the same name:
`housing`
`employment`
`financial_product_services` — `financial_products_services` and `credit` are accepted as
equivalents and resolve to the same category
`issues_elections_politics`
`none` — accepted for symmetry with [Boost Post](/docs/apis/ads/facebook/boost-post#param-special-ad-categories), where it means "not in a special ad category". It is sent to Meta as `regulated_categories=["NONE"]`: `accountId` is required and a successful response echoes `["NONE"]`. This does not guarantee the same results as an unfiltered search; a Meta rejection returns `code: 368`. Omit `specialAdCategories` entirely rather than sending `none` if you want an ordinary unfiltered search.
Values are case-insensitive. An unrecognized value is rejected with a `400` and `code: 101` listing the accepted values. That message is written in Meta's upper-case form and also names the alternative spellings above, so it reads `NONE, HOUSING, FINANCIAL_PRODUCTS_SERVICES, EMPLOYMENT, ISSUES_ELECTIONS_POLITICS, FINANCIAL_PRODUCT_SERVICES, CREDIT` — the same categories listed here, not extra ones. Boost Post returns the identical message. An empty value, as in `?specialAdCategories=`, is rejected the same way.
Pass more than one category by repeating the parameter, for example `?search=nursing&specialAdCategories=employment&specialAdCategories=housing`. The bracketed form `?specialAdCategories[]=employment&specialAdCategories[]=housing` works as well. A comma-separated string such as `?specialAdCategories=employment,housing` is **not** supported and is rejected with a `400` and `code: 101`. All requested categories are sent to Meta in one search. Ayrshare returns Meta's results and does not compute or guarantee an intersection of independently eligible interests. Repeating a category, or sending two spellings of the same one, collapses to a single category.
When you supply more than one category, the `specialAdCategories` field echoed back in the response is sorted alphabetically rather than returned in the order you sent it.
Omit `specialAdCategories` entirely and the endpoint behaves exactly as it did before: an unfiltered search, no `accountId` needed, and the same response shape.
**accountId** `string`
Required when `specialAdCategories` is supplied, and ignored otherwise. The ID of the Facebook ad
account the interests are being searched for, without the `act_` prefix — the same value [Boost
Post](/docs/apis/ads/facebook/boost-post) takes. Retrieve it from the [ad accounts
endpoint](/docs/apis/ads/facebook/get-ad-accounts). It must be numeric; a leading `act_` is accepted
and stripped, and anything else is rejected with a `400` and `code: 101`.
> [!WARNING]
>
> Meta only offers category-aware interest search on an ad account, so a request that supplies
> `specialAdCategories` without `accountId` is rejected with a `400` and `code: 101` before any
> search runs. A request with no `specialAdCategories` is unchanged and still needs no `accountId`.
**limit** `number` (default: 100)
Limit the number of ad interests returned.
> [!NOTE]
>
> A filtered response echoes the applied categories back in a `specialAdCategories` field, so you
> can confirm what the results were narrowed against. An unfiltered response omits that field
> entirely rather than returning it empty. Filtered results also omit the `topic` field on each
> interest, because Meta's category-aware search does not return it — read `name` instead. A search
> with no eligible interests returns `200` with an empty `interests` array and `count: 0`, not an
> error, and a filtered search never falls back to unfiltered results.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdInterests {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/facebook/interests?search=Rhythm%20and%20blues%20music')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music",
"topic": "News and entertainment",
"audienceSizeLowerBound": 669180255,
"audienceSizeUpperBound": 786955980
},
{
"id": "6003470511564",
"name": "Do it yourself (DIY)",
"topic": "Hobbies and activities",
"audienceSizeLowerBound": 418387661, // Lower bound of audience size
"audienceSizeUpperBound": 492023890 // Upper bound of audience size
},
{
"id": "6002926036121",
"name": "Italy",
"topic": "Travel, places and events",
"audienceSizeLowerBound": 279313350, // Lower bound of audience size
"audienceSizeUpperBound": 328472500 // Upper bound of audience size
}
],
"count": 3,
"lastUpdated": "2025-03-27T01:01:38.547Z",
"nextUpdate": "2025-03-27T01:12:38.547Z"
}
```
```json 200: Filtered by special ad category theme={"system"}
{
"status": "success",
"interests": [
{
"id": "6003269553527",
"name": "Nursing",
"audienceSizeLowerBound": 91340120,
"audienceSizeUpperBound": 107420310
},
{
"id": "6003204427551",
"name": "Health care",
"audienceSizeLowerBound": 412880440,
"audienceSizeUpperBound": 485610770
}
],
"count": 2,
"specialAdCategories": ["EMPLOYMENT"],
"lastUpdated": "2026-09-11T01:01:38.547Z",
"nextUpdate": "2026-09-11T01:12:38.547Z"
}
```
```json 400: Ad interests error theme={"system"}
{
"action": "get ad interests",
"status": "error",
"code": 368,
"message": "Error getting ad interests. Please try again or contact us if the issue persists."
}
```
# Regions
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-ad-regions
GET /ads/facebook/regions
Search Facebook regions such as states and provinces for ad targeting, or call without parameters to list every country that supports region targeting.
Search for regions (states, provinces, etc.) to be used for targeting when boosting an ad.
Regions use Facebook's `key` identifier. Use this `key` when boosting ads
with region targeting.
If a returned region has supportsRegion true, you can target this region. If supportsCity is
true, the region has city codes.
To get all countries that support region targeting, call this endpoint without query parameters.
Results may vary based on Facebook data availability.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string`
The search string to filter regions. Provide a partial or full region name.
**limit** `number` (default: 25)
Maximum number of regions to return. Only used with `search`. Without `search`, up to 1000 regions are returned.
```bash cURL theme={"system"}
# All countries that support region targeting
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/facebook/regions"
```
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/facebook/regions?search=al"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/regions?search=al", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/regions?search=al', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/regions?search=al",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/regions?search=al");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/regions?search=al", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdRegions {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/regions?search=al"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/facebook/regions?search=al')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"regions": [
{
"key": "3843",
"name": "Alabama",
"type": "region",
"countryCode": "US",
"countryName": "United States",
"supportsRegion": true,
"supportsCity": true
},
{
"key": "3844",
"name": "Alaska",
"type": "region",
"countryCode": "US",
"countryName": "United States",
"supportsRegion": true,
"supportsCity": true
}
],
"count": 2
}
```
```json 414: Location metadata error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 414,
"message": "Error getting ad location metadata. Please try again."
}
```
# Boosted Ads
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-boosted-ads
GET /ads/facebook/ads
Retrieve boosted Facebook ads by account, ad, or post ID, with status, Meta review results, current spend, real-time metrics, and shareable preview links.
Retrieve ads that have been boosted for a specific Facebook post.
This boosted ads endpoint provides detailed information about the boosted ads, including status, current spend, performance metrics (analytics), and preview links.
The metrics are real-time totals for the ads. If you need historical data, use the [get ad
history endpoint](/docs/apis/ads/facebook/get-ad-history).
`review` is Meta's review of the ad: `status` is `pending` while Meta reviews it, `rejected`
when Meta disapproved it or left policy feedback on it, and `approved` when the ad is
active or pre-approved. `reasons` holds Meta's explanations, or the policy's name when Meta gives no text, and is empty when there is no feedback. `review` is `null` when neither
the ad's status nor any feedback shows the review, for example an ad paused before review.
The `previewLink` URL allows you share the preview ad with your colleagues, so that they will
see the ad for 24-hours across the various Facebook formats. Your colleagues' views of the ad
will not be counted towards your ad spend.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
Retrieve all ads under the account ID.
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
Required if `adId`, `fbPostId`, or `postId` is not set.
**adId** `string` *required*
Retrieve an ad with the ad ID.
Required if `accountId`, `fbPostId`, or `postId` is not set.
**fbPostId** `string` *required*
Retrieve ads with the [Facebook social post ID](/docs/apis/overview#social-post-id).
Required if `accountId`, `adId`, or `postId` is not set.
**limit** `number` (default: 25)
Retrieve a maximum of `limit` ads. Maximum is 300; higher values are capped at 300.
**postId** `string` *required*
Retrieve ads with the Ayrshare [post ID](/docs/apis/overview#ayrshare-post-id).
Required if `accountId`, `adId`, or `fbPostId` is not set.
**status** `string` (default: all)
Filter ads by status of the ad. Valid values are:
`active`
`paused`
`deleted`
`archived`
> [!NOTE]
>
> An ad boosted with a [`callToAction`](/docs/apis/ads/facebook/boost-post#param-call-to-action) carries
> that object back on `ad.callToAction`, with `type` in Meta's upper-case form. The key is omitted
> entirely for an ad that has no call-to-action button. A button that opens a chat also carries its
> `destination`. The examples below use the `engagement` goal with no button. A link button needs
> the `traffic` goal, and a [chat button](/docs/apis/ads/facebook/boost-post#message-destination) needs
> `engagement` or `interactions`. A chat button boosted with `interactions` is listed with the
> `engagement` goal when you filter by campaign.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/ads?adId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/ads?adId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/ads?adId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistoryGETRequest_csharp
{
class History
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/ads/facebook/ads?adId=1234567890";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success with Ad ID theme={"system"}
{
"status": "success",
"ads": [
{
"postId": "DE6gpw8kxlonHy6eb721",
"ad": {
"accountId": "274948331",
"adId": "6683876017501",
"budgetRemaining": 0,
"created": "2025-03-30T10:11:34-0700",
"creativeId": "1033490148629541",
"dailyBudget": 0,
"deliveryStatus": "ACTIVE",
"endDate": "2025-04-03T22:30:00.000Z",
"fbPostId": "106638148652329_672633202087922",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"type": "engagement"
},
"isComplete": false,
"lifetimeBudget": 0,
"metrics": {
"spend": 9.21,
"impressions": 6495,
"reach": 6272,
"clicks": 3,
"ctr": 0.046189,
"cpm": 1.418014,
"cpp": 1.468431,
"frequency": 1.035555,
"uniqueClicks": 3,
"uniqueCtr": 0.047832,
"costPerUniqueClick": 3.07,
"inlineLinkClicks": 0,
"costPerInlineLinkClick": 0,
"outboundClicks": 0,
"costPerOutboundClick": 0,
"websiteCtr": [],
"accountCurrency": "USD",
"accountName": "John Doe",
"accountId": "274948331"
},
"name": "API Post - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T17:11:26",
"previewLink": "https://fb.me/22Cn31wXlzhxOC1",
"review": {
"reasons": [],
"status": "approved"
},
"spend": 9.21,
"startDate": "2025-03-30T17:11:32.000Z",
"status": "ACTIVE",
"targeting": {
"ageMax": 65,
"ageMin": 18,
"geoLocations": {
"countries": [
"US"
],
"locationTypes": [
"home",
"recent"
]
},
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music"
}
]
}
}
}
],
"count": 1
}
```
```json 200: Success with Account ID theme={"system"}
{
"status": "success",
"ads": [
{
"postId": "DE6gpw8kxlonHy6eb7Lo",
"ad": {
"accountId": "274948331",
"adId": "6683876017501",
"budgetRemaining": 0,
"created": "2025-03-30T10:11:34-0700",
"creativeId": "1033490148629541",
"dailyBudget": 0,
"deliveryStatus": "ACTIVE",
"endDate": "2025-04-03T22:30:00.000Z",
"fbPostId": "106638148652329_672633202087921",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"type": "engagement"
},
"isComplete": false,
"lifetimeBudget": 0,
"metrics": {
"spend": 9.21,
"impressions": 6495,
"reach": 6272,
"clicks": 3,
"ctr": 0.046189,
"cpm": 1.418014,
"cpp": 1.468431,
"frequency": 1.035555,
"uniqueClicks": 3,
"uniqueCtr": 0.047832,
"costPerUniqueClick": 3.07,
"inlineLinkClicks": 0,
"costPerInlineLinkClick": 0,
"outboundClicks": 0,
"costPerOutboundClick": 0,
"websiteCtr": [],
"accountCurrency": "USD",
"accountName": "John Doe",
"accountId": "274948331"
},
"name": "API Post - DE6gpw8kxlonHy6eb7Lo - 2025-03-30T17:11:26",
"previewLink": "https://fb.me/22Cn31wXlzhxOC1",
"review": {
"reasons": [],
"status": "approved"
},
"spend": 9.21,
"startDate": "2025-03-30T17:11:32.000Z",
"status": "ACTIVE",
"targeting": {
"ageMax": 65,
"ageMin": 18,
"geoLocations": {
"countries": ["US"],
"locationTypes": ["home", "recent"]
},
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music"
}
]
}
}
},
{
"ad": {
"accountId": "274948331",
"adId": "6685225314301",
"budgetRemaining": 0,
"created": "2025-03-30T18:36:39-0700",
"creativeId": "628434386637341",
"dailyBudget": 0,
"deliveryStatus": "PAUSED",
"endDate": "2025-04-03T22:30:00.000Z",
"fbPostId": "106638148652329_672633202087921",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"type": "engagement"
},
"isComplete": false,
"lifetimeBudget": 0,
"metrics": {},
"name": "API Post 14 - 106638148652329_672633202087926 - 2025-03-31T01:36:32",
"previewLink": "https://fb.me/2nPlTIZEpzrCnY1",
"review": null,
"spend": 0,
"startDate": "2025-03-31T01:36:37.000Z",
"status": "PAUSED",
"targeting": {
"ageMax": 65,
"ageMin": 18,
"geoLocations": {
"countries": ["US"],
"locationTypes": ["home", "recent"]
},
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music"
}
]
}
}
}
],
"count": 2
}
```
```json 400: Submitted ads retrieval error theme={"system"}
{
"action": "get ads",
"status": "error",
"code": 370,
"message": "Error getting ads. Please try again or contact us if the issue persists."
}
```
# DSA Recommendations
Source: https://www.ayrshare.com/docs/apis/ads/facebook/get-dsa-recommendations
GET /ads/facebook/dsaRecommendations
Get Digital Services Act (DSA) beneficiary and payor recommendations for your Facebook ad account, required when boosting posts for ads that target the EU.
Get Digital Services Act (DSA) recommendations for your Facebook ad account.
As part of the requirements set forth by the European Union (EU) Digital Services Act (DSA), [Facebook requires ads targeting](https://www.facebook.com/business/help/605021638170961) any part of the EU to provide string values defining the beneficiary and payor of the ad being created
This endpoint provides a list of recommended DSA beneficiaries and payors that can be used when boosting posts.
These fields may be used in [`dsaBeneficiary`](/docs/apis/ads/facebook/boost-post#param-dsa-beneficiary) and [`dsaPayor`](/docs/apis/ads/facebook/boost-post#param-dsa-payor).
Outputs a list of strings (maximum 25) that Facebook has identified to likely be the
beneficiary/payor, based on recent activity of the ad account.
These recommendations can be used as values for the `dsaBeneficiary` and `dsaPayor` parameters
in the [Boost Post](/docs/apis/ads/facebook/boost-post) endpoint.
Both `dsaBeneficiary` and `dsaPayor` must be set together if either is provided when boosting
posts.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
The ID of the Facebook ad account to get DSA recommendations for. The account ID can be retrieved
from the [ad accounts endpoint](/docs/apis/ads/facebook/get-ad-accounts).
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetDsaRecommendations {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/facebook/dsaRecommendations?accountId=1234567890')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"dsaRecommendations": [
"Beneficiary 1",
"Beneficiary 2",
"Beneficiary 3"
],
"count": 1
}
```
```json 413: DSA Recommendations Error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 413,
"message": "There was an error getting the DSA recommendations. Please try again."
}
```
# Update Ad
Source: https://www.ayrshare.com/docs/apis/ads/facebook/put-ad-update
PUT /ads/facebook/ads
Set a Facebook boosted ad to active, paused, deleted, or archived by adId, and see how status changes apply to boosts attached to an existing campaign.
Update the status of a boosted ad to make it active, paused, deleted, or archived.
* For a boost added to your own campaign or ad set with `attachTo`, the new status applies only to what the boost created: the ad, and the ad set when the boost created it. The campaign or ad set you named in `attachTo` keeps its own status. The ad runs only while the campaign and ad set above it are active. See [Boost into an Existing Campaign or Ad Set](/docs/apis/ads/facebook/boost-post#attach-to).
* An ad that Ayrshare has no record of can be paused, deleted or archived, but not set to `active`. That request returns a `400`. Contact support with the ad id.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**adId** `string` *required*
The boosted ad ID to update.
**status** `string` *required*
The status of the ad. Values: `active`, `paused`, `deleted`, or `archived`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.ayrshare.com/api/ads/facebook/ads \
-d '{"adId": "1234567890", "status": "active"}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/facebook/ads", {
method: "PUT",
headers: {
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({ adId: "1234567890", status: "active" })
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.put('https://api.ayrshare.com/api/ads/facebook/ads', headers=headers, json={"adId": "1234567890", "status": "active"})
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/facebook/ads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode(["adId" => "1234567890", "status" => "active"])
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.PutAsync("https://api.ayrshare.com/api/ads/facebook/ads", new StringContent(JsonConvert.SerializeObject(new { adId = "1234567890", status = "active" }), Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("PUT", "https://api.ayrshare.com/api/ads/facebook/ads", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdSpend {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/facebook/ads"))
.header("Authorization", "Bearer API_KEY")
.PUT(HttpRequest.BodyPublishers.ofString(Json.stringify(new { adId = "1234567890", status = "active" })))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/facebook/ads')
request = Net::HTTP::Put.new(uri)
request['Authorization'] = 'Bearer API_KEY'
request.body = JSON.generate({ adId: "1234567890", status: "active" })
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"adId": "6691084804905",
"requestedStatus": "ACTIVE"
}
```
```json 400: Ad update error theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "Invalid status: actives. Status must be either 'active', 'paused', 'deleted', or 'archived'."
}
```
# Accounts
Source: https://www.ayrshare.com/docs/apis/ads/google/get-ad-accounts
GET /ads/google/accounts
List the Google Ads accounts you can create campaigns in, and see which linked accounts Google will not open and why.
Get the Google Ads accounts you can create campaigns in. Use an account's `id` as `accountId` when you create a campaign.
Link Google Ads from the Social Accounts page first.
The Google user you link must have direct access to the ad account. Accounts you reach only through a manager account are not listed and cannot be used yet: add that user to the ad account itself. Manager accounts are not listed either: they cannot hold campaigns.
Every account comes back in one response, so `nextCursor` is always `null`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Response
`adAccounts` lists the accounts you can use, with each account's `id`, `name` and `currency`.
If Google lists an account but will not open it, the response also has `unavailableAccounts`. Each entry has the account `id` and a `reason`. The field is missing when every account can be used.
| `reason` | What it means | What to do |
| - | - | - |
| `notEnabled` | The account is cancelled, suspended, or not set up yet. | Finish setup or reactivate the account in Google Ads, then list the accounts again. Changes can take up to about 10 minutes to show. |
| `noAccess` | The Google user you linked can see the account but has no access to its data. | Give that user access to the ad account in Google Ads, then list the accounts again. Changes can take up to about 10 minutes to show. |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/google/accounts"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/accounts", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
}
r = requests.get("https://api.ayrshare.com/api/ads/google/accounts", headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/accounts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
]
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/google/accounts");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/google/accounts", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdAccounts {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/accounts"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/accounts')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"adAccounts": [{ "id": "1234567890", "name": "Acme", "currency": "USD" }],
"nextCursor": null,
"unavailableAccounts": [{ "id": "2345678901", "reason": "notEnabled" }]
}
```
# Insights
Source: https://www.ayrshare.com/docs/apis/ads/google/get-ad-insights
GET /ads/google/insights
Pull live daily Google Ads metrics for your campaigns, ad groups or ads, with clicks, cost, conversions and conversion value.
Get live Google Ads performance, one row per day, for the campaigns, ad groups or ads you created through Ayrshare.
Numbers are read from Google when you call, not from a nightly copy.
All money values are in the ad account's currency, returned in `currency` on each row.
Google leaves out days with no activity, so a range can return fewer rows than it has days.
Google adjusts a day's cost and conversions for a few days after it ends, so recent days can change between reads.
For a Performance Max campaign, use `level=campaign` with the campaign id. A Performance Max campaign has no ads of its own, so `level=ad` returns no rows.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**level** `string` *required*
What each row reports on: `campaign` (the `campaignId` from Create), `adSet` (the `adGroupId` from Create) or `ad` (the `adId` from Create).
**ids** `string` *required*
Comma-separated campaign, ad group or ad ids, up to 50. They must all be in one ad account.
**accountId** `string`
The 10-digit Google Ads customer id, with or without hyphens (`123-456-7890`).
Ad and ad group reports read the ad account you name here. Without it, an ad report searches every ad account your Google Ads link reaches, up to 25, and an ad group report works only when your link reaches one ad account. Name the account when your link reaches more than 25 ad accounts, or more than one for an ad group report. Otherwise the request answers `code: 101`.
**metrics** `string`
Comma-separated metrics to return. Leave it out to get them all. See [Metrics](#metrics).
**startDate** `string` (default: 30 days ago)
First day, as `YYYY-MM-DD`. Google keeps daily numbers for 37 months; an older `startDate` answers `code: 101`.
**endDate** `string` (default: today)
Last day, as `YYYY-MM-DD`.
## Metrics
| Metric | Type | Meaning |
| - | - | - |
| `spend` | number | Amount spent |
| `impressions` | number | Times the ad was shown |
| `clicks` | number | Clicks |
| `cpm` | number or `null` | Cost per 1,000 impressions. `null` on a day with no impressions |
| `cpc` | number or `null` | Cost per click. `null` on a day with no clicks |
| `ctr` | number or `null` | Click-through rate, in percent. `null` on a day with no impressions |
| `actions` | number | Google's conversions, which can be fractional |
| `conversionValue` | number | The value of those conversions |
`actions` and `conversionValue` stay `0` until conversion tracking is set up in your Google Ads account. `reach` is not available, because Google reports no unique reach for Search, and `breakdowns` are not available on Google. Asking for either answers `code: 101`.
## Paging
Every row comes back in one response, so `nextCursor` is always `null`.
## Errors
| Code | When |
| - | - |
| `101` | A parameter is missing or wrong, a metric or breakdown isn't supported, `startDate` is more than 37 months ago, more than 50 ids are sent, the ids are in more than one ad account, or `accountId` is needed and missing |
| `400` | A campaign, ad group or ad isn't yours, or `accountId` isn't the ad account Ayrshare has on record for the ids you sent |
| `370` | Google could not return the insights. Try again |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
}
r = requests.get("https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07", headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
]
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdInsights {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/insights?level=campaign&ids=21534567890&startDate=2026-10-01&endDate=2026-10-07')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"platform": "google",
"level": "campaign",
"startDate": "2026-10-01",
"endDate": "2026-10-07",
"insights": [
{
"date": "2026-10-01",
"level": "campaign",
"id": "21534567890",
"currency": "USD",
"spend": 12.48,
"impressions": 1830,
"clicks": 41,
"cpm": 6.82,
"cpc": 0.3,
"ctr": 2.2404,
"actions": 3.5,
"conversionValue": 140
}
],
"count": 1,
"nextCursor": null
}
```
# Get Campaign
Source: https://www.ayrshare.com/docs/apis/ads/google/get-campaign
GET /ads/google/campaigns/{id}
Read a Google Ads campaign's status and daily budget, and Google's review of each of its ads or assets.
Get a Google Ads campaign you created through Ayrshare: its name, status, daily budget, and Google's review of each of its ads.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The `campaignId` returned when you created the campaign.
## Response
| Field | What it holds |
| - | - |
| `campaignId` | The campaign's id |
| `name` | The campaign name |
| `campaignStatus` | `active` or `paused` |
| `platformStatus` | Google's own status: `ENABLED`, `PAUSED`, or `REMOVED` for a campaign deleted in Google Ads. A removed campaign keeps the last `campaignStatus` you set through Ayrshare |
| `budget` | The daily budget: `amount`, `currency` (the ad account's currency) and `type` (`daily`) |
| `ads` | Google's review of each ad (Search) or asset (Performance Max). See [Ad Review](#ad-review) |
## Ad Review
Google reviews every new ad against its advertising policies, usually within one business day. Each row in `ads` holds:
| Field | What it holds |
| - | - |
| `adId` | The ad's id, as returned by Create |
| `approvalStatus` | Google's decision: `APPROVED`, `APPROVED_LIMITED` (shown with limits, for example not in some countries), `AREA_OF_INTEREST_ONLY` or `DISAPPROVED`. Until Google decides, it holds another value, such as `UNKNOWN` or `UNSPECIFIED` |
| `reviewStatus` | Where the review stands, for example `REVIEW_IN_PROGRESS` or `REVIEWED` |
| `policyTopics` | The policies behind a limit or disapproval: `[{ "topic": "ALCOHOL", "type": "PROHIBITED" }]` |
A disapproved ad does not serve. Change it in Google Ads, or create a new campaign with compliant text. `ads` is left out when Google's review could not be read at that moment, or took more than a few seconds; read the campaign again later.
Each night, Ayrshare also checks every review and sends an `ads` webhook with `type: "reviewChanged"` when Google's decision changes. See [Ads Action](/docs/apis/webhooks/actions#ads-action).
## Asset Review
Google reviews each asset of a Performance Max campaign on its own. `ads` holds one row per asset, with the same fields as an [ad review](#ad-review) plus:
| Field | What it holds |
| - | - |
| `adId` | The asset's key in this campaign: `asset:~~`, or `asset:~~` for a brand asset |
| `assetId` | Google's id for the asset |
| `fieldType` | Where it is used, for example `HEADLINE`, `MARKETING_IMAGE`, `SQUARE_MARKETING_IMAGE`, `BUSINESS_NAME` or `LOGO` |
| `primaryStatus` | Whether it can serve, for example `ELIGIBLE` or `NOT_ELIGIBLE` |
The business name, logos and landscape logos are brand assets, linked to the campaign rather than the asset group. They have a row here too, after the asset group rows, keyed by the campaign id: `asset:~~`, where `fieldType` is `BUSINESS_NAME`, `LOGO` or `LANDSCAPE_LOGO`.
## Errors
| Code | When |
| - | - |
| `400` | The campaign is not yours, does not exist, or is no longer in the Google Ads account. Also when the Google user you linked cannot access the ad account, or Google Ads access was revoked. |
| `370` | Google could not complete the request. Try again. |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/google/campaigns/21534567890"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/campaigns/21534567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
}
r = requests.get("https://api.ayrshare.com/api/ads/google/campaigns/21534567890", headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/campaigns/21534567890",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
]
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/google/campaigns/21534567890");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/google/campaigns/21534567890", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetCampaign {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/campaigns/21534567890"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/campaigns/21534567890')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Search Campaign theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567890",
"name": "Fall sale",
"campaignStatus": "active",
"platformStatus": "ENABLED",
"budget": { "amount": 25, "currency": "USD", "type": "daily" },
"ads": [
{
"adId": "71234567890",
"approvalStatus": "APPROVED_LIMITED",
"reviewStatus": "REVIEWED",
"policyTopics": [{ "topic": "ALCOHOL", "type": "LIMITED" }]
}
]
}
```
```json 200: Performance Max Campaign theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567891",
"name": "Fall sale PMax",
"campaignStatus": "paused",
"platformStatus": "PAUSED",
"budget": { "amount": 40, "currency": "USD", "type": "daily" },
"ads": [
{
"adId": "asset:6512345678~31234567890~HEADLINE",
"assetId": "31234567890",
"fieldType": "HEADLINE",
"primaryStatus": "ELIGIBLE",
"approvalStatus": "APPROVED",
"reviewStatus": "REVIEWED",
"policyTopics": []
}
]
}
```
```json 400: Not Found theme={"system"}
{
"action": "ads",
"status": "error",
"code": 400,
"message": "Campaign not found or you are not authorized to access it."
}
```
# Google Performance Max Campaigns
Source: https://www.ayrshare.com/docs/apis/ads/google/performance-max-campaigns
POST /ads/google/campaigns
Create a Google Ads Performance Max campaign in one request from text and image URLs, with a daily budget, one asset group and its assets built together.
> [!WARNING]
>
> **Set up conversion tracking first.** Performance Max uses your conversions to decide where to show ads and how much to bid. Ayrshare does not block a campaign without it, but Google may limit its spend until conversion tracking is set up in your Google Ads account. Ayrshare does not set it up for you. When the account has none, the create response includes a `warnings` entry saying so.
Create a Google Ads Performance Max campaign in one request. You send text and image URLs; Google mixes them into ads across Search, YouTube, Display, Discover, Gmail and Maps. Ayrshare builds the budget, the campaign, one asset group and all its assets together. If Google refuses any part, nothing is created.
The request uses the shared [Ad Campaigns](/docs/apis/ads/campaigns) shape. Reading and changing a campaign, insights and errors work as on [Google Search Campaigns](/docs/apis/ads/google/search-campaigns). This page covers what is different.
Google ads are turned on per account. Contact us to enable them.
New campaigns start `paused` unless you send `status: "active"`.
The budget is per day, in the ad account's currency, and is never shared with other campaigns.
Spend in another currency is converted to USD for billing at that day's exchange rate from Open Exchange Rates. An account in a currency that cannot be converted answers `code: 101` before anything is created.
Video is not supported. Google may make videos from your images.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
**Idempotency-Key** `string` *required*
A new unique value, such as a UUID, for each new campaign. Repeating a request with the same key returns the first response. When a create fails before Ayrshare sends the campaign to Google (for example, an image that is the wrong size), the key is freed, so you can fix the request and retry with the same key.
## Body Parameters
**type** `string` *required*
`performanceMax`.
**bidding** `object` *required*
Performance Max takes only these strategies. Any other answers `code: 101`.
`maximizeConversions`: get the most conversions for the budget.
`targetCpa` with `targetCpa`: the same, aiming at that cost per conversion.
`maximizeConversionValue`: get the most conversion value for the budget.
`targetRoas` with `targetRoas`: the same, aiming at that return on ad spend, for example `3.5` for 350%.
**euPoliticalAdvertising** `boolean` *required*
Google requires this declaration on every campaign.
**targeting.locations** `string[]`
Google location ids, for example `2840` for the United States. Leave it out to target every location.
**platformOptions.google** `object` *required*
**properties**
**finalUrl** `string` *required*
The `http` or `https` page the ads open.
**assets** `object` *required*
Your text and images. Each list's items must be different from each other.
**properties**
**headlines** `string[]` *required*
3 to 15, each up to 30 characters.
**longHeadlines** `string[]` *required*
1 to 5, each up to 90 characters.
**descriptions** `string[]` *required*
2 to 5, each up to 90 characters. At least one must be 60 characters or fewer.
**businessName** `string` *required*
One business name, up to 25 characters.
**marketingImages** `string[]` *required*
1 to 20 landscape image URLs: 1.91:1, at least 600×314.
**squareMarketingImages** `string[]` *required*
1 to 20 square image URLs: 1:1, at least 300×300.
**logos** `string[]` *required*
1 to 5 square logo URLs: 1:1, at least 128×128.
**landscapeLogos** `string[]`
Up to 4 wide logo URLs: 4:1, at least 512×128. Google takes 5 logos and wide logos together at most.
### Images
Ayrshare downloads each image from its URL and checks it before sending anything to Google:
GIF, JPG or PNG, up to 5120 KB each.
At least the minimum size above, with the aspect ratio of its field (within 1%).
All images together up to 45 MB.
The URL must be public. Private addresses are refused.
An image that fails answers `code: 101` naming it, for example `platformOptions.google.assets.logos[0]: the image is 100x100; use at least 128x128.`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11" \
-X POST https://api.ayrshare.com/api/ads/google/campaigns \
-d '{
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/campaigns", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11"
},
body: JSON.stringify({
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
"Idempotency-Key": "0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11",
}
payload = {
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": False,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}
r = requests.post("https://api.ayrshare.com/api/ads/google/campaigns", headers=headers, json=payload)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json",
"Idempotency-Key: 0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11"
],
CURLOPT_POSTFIELDS => $payload
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
client.DefaultRequestHeaders.Add("Idempotency-Key", "0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11");
var payload = """
{
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}
""";
var response = await client.PostAsync(
"https://api.ayrshare.com/api/ads/google/campaigns",
new StringContent(payload, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := `{
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}`
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/ads/google/campaigns", strings.NewReader(payload))
req.Header.Add("Authorization", "Bearer API_KEY")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Idempotency-Key", "0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CreateCampaign {
public static void main(String[] args) throws Exception {
String payload = """
{
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/campaigns"))
.header("Authorization", "Bearer API_KEY")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/campaigns')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer API_KEY'
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = '0b7a6f1c-2d4e-4c55-9a3e-2f8d1b7c9e11'
request.body = <<~JSON
{
"accountId": "123-456-7890",
"name": "Fall sale PMax",
"type": "performanceMax",
"budget": { "amount": 40, "type": "daily" },
"bidding": { "strategy": "maximizeConversions" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"assets": {
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"longHeadlines": ["Every running shoe 30% off this week only"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"businessName": "Acme Shoes",
"marketingImages": ["https://example.com/images/fall-1200x628.png"],
"squareMarketingImages": ["https://example.com/images/fall-1200x1200.png"],
"logos": ["https://example.com/images/logo-512.png"]
}
}
}
}
JSON
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Campaign Created theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567891",
"assetGroupId": "6512345678",
"campaignStatus": "paused"
}
```
```json 200: Created, No Conversion Tracking theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567891",
"assetGroupId": "6512345678",
"campaignStatus": "paused",
"warnings": [
"This Google Ads account has no conversion tracking. Performance Max optimizes for conversions, so Google may limit the campaign's spend until you set up conversion tracking in Google Ads."
]
}
```
```json 400: Image Too Small theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "platformOptions.google.assets.logos[0]: the image is 100x100; use at least 128x128."
}
```
# Google Search Campaigns
Source: https://www.ayrshare.com/docs/apis/ads/google/search-campaigns
POST /ads/google/campaigns
Create a Google Ads Search campaign with its budget, keywords, ad text and location targeting in one request
Create a Google Ads Search campaign in one request. Ayrshare builds the budget, the campaign, one ad group, your keywords, one responsive search ad, and the location targeting together. If Google refuses any part, nothing is created.
The request uses the shared [Ad Campaigns](/docs/apis/ads/campaigns) shape. This page covers what is specific to Google.
Google ads are turned on per account. Contact us to enable them.
Link Google Ads from the Social Accounts page first. `accountId` is the 10-digit Google Ads customer id, with or without hyphens (`123-456-7890`).
The Google user you link must have direct access to the ad account. Accounts you reach only through a manager account are not listed and cannot be used yet: add that user to the ad account itself. Manager accounts are not listed either: they cannot hold campaigns.
New campaigns start `paused` unless you send `status: "active"`.
The budget is per day, in the ad account's currency. Do not send `budget.currency`: it answers `code: 101`.
Spend in another currency is converted to USD for billing at that day's exchange rate from Open Exchange Rates. An account in a currency that cannot be converted answers `code: 101` before anything is created.
Ads show on Google Search only, not on search partners or the Display Network.
> [!NOTE]
>
> **Conversion tracking.** `maximizeConversions` only works when conversion tracking is set up in your Google Ads account. Ayrshare does not set it up for you. Without it, use `maximizeClicks` or `manualCpc`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
**Idempotency-Key** `string` *required*
A new unique value, such as a UUID, for each new campaign. Repeating a request with the same key returns the first response and creates nothing new. When a create fails before Ayrshare sends the campaign to Google, the key is freed, so you can fix the request and retry with the same key. That covers a value Ayrshare refuses (such as an unsupported bidding strategy), an account in a currency that cannot be converted to USD, and any failure of the account check Ayrshare makes first, including Google refusing it. A create that fails once the campaign was sent to Google keeps its answer: retry it with a new key.
## Body Parameters
The shared fields work as described on [Ad Campaigns](/docs/apis/ads/campaigns). On Google:
**type** `string` *required*
`search`.
**bidding.strategy** `string` *required*
`maximizeClicks`, `manualCpc`, or `maximizeConversions`. Any other answers `code: 101`.
**euPoliticalAdvertising** `boolean` *required*
Google requires this declaration on every campaign. While any campaign in an account is undeclared, Google rejects every change to that account's campaigns.
**schedule** `object`
Google schedules whole days in the ad account's time zone. `start` begins at 00:00:00 on its day and `end` stops at 23:59:59 on its day; a time you send only picks the day. `start` can be today but not an earlier day. For a one-day campaign, send the same date for `start` and `end`. An `end` on an earlier day than `start` answers `code: 101`.
**targeting** `object`
Google matches Search ads to people by the language of your ad text, so there is no language setting. Sending `targeting.languages` answers `code: 101`.
**properties**
**locations** `string[]`
Google location ids, for example `2840` for the United States. Find ids in Google's [geo targets list](https://developers.google.com/google-ads/api/data/geotargets). Leave it out to target every location.
**platformOptions.google** `object` *required*
**properties**
**finalUrl** `string` *required*
The `http` or `https` page the ad opens.
**headlines** `string[]` *required*
3 to 15 headlines, each up to 30 characters.
**descriptions** `string[]` *required*
2 to 4 descriptions, each up to 90 characters.
**path1** `string`
First part of the display path shown after the domain, up to 15 characters.
**path2** `string`
Second part of the display path, up to 15 characters. Needs `path1`.
**keywords** `object[]` *required*
At least one keyword: `{ "text": string, "matchType": "exact" | "phrase" | "broad" }`. `text` is up to 80 characters and 10 words.
**maxCpc** `number`
The most to pay per click, in the account currency. Required with `manualCpc`. With `maximizeClicks` it caps each bid. Not accepted with `maximizeConversions`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10" \
-X POST https://api.ayrshare.com/api/ads/google/campaigns \
-d '{
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/campaigns", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10"
},
body: JSON.stringify({
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
"Idempotency-Key": "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10",
}
payload = {
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": False,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}
r = requests.post("https://api.ayrshare.com/api/ads/google/campaigns", headers=headers, json=payload)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/campaigns",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json",
"Idempotency-Key: 6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10"
],
CURLOPT_POSTFIELDS => $payload
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
client.DefaultRequestHeaders.Add("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10");
var payload = """
{
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}
""";
var response = await client.PostAsync(
"https://api.ayrshare.com/api/ads/google/campaigns",
new StringContent(payload, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := `{
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}`
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/ads/google/campaigns", strings.NewReader(payload))
req.Header.Add("Authorization", "Bearer API_KEY")
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class CreateCampaign {
public static void main(String[] args) throws Exception {
String payload = """
{
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/campaigns"))
.header("Authorization", "Bearer API_KEY")
.header("Content-Type", "application/json")
.header("Idempotency-Key", "6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/campaigns')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer API_KEY'
request['Content-Type'] = 'application/json'
request['Idempotency-Key'] = '6f1c2d4e-0b7a-4c55-9a3e-2f8d1b7c9e10'
request.body = <<~JSON
{
"accountId": "123-456-7890",
"name": "Fall sale",
"type": "search",
"budget": { "amount": 25, "type": "daily" },
"bidding": { "strategy": "maximizeClicks" },
"targeting": { "locations": ["2840"] },
"euPoliticalAdvertising": false,
"platformOptions": {
"google": {
"finalUrl": "https://example.com/fall-sale",
"headlines": ["Fall sale", "Shoes 30% off", "Free returns"],
"descriptions": ["Every shoe 30% off this week.", "Free returns on every order."],
"path1": "shoes",
"keywords": [
{ "text": "running shoes", "matchType": "phrase" },
{ "text": "trail running shoes", "matchType": "exact" }
]
}
}
}
JSON
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Campaign Created theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567890",
"adGroupId": "16812345678",
"adId": "71234567890",
"campaignStatus": "paused"
}
```
```json 400: Too Few Headlines theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "platformOptions.google.headlines: Supply at least 3 headlines."
}
```
## Errors
| Code | When |
| - | - |
| `101` | A field is missing or invalid. `details` names it. |
| `205` | Google refused the request: for example, the account already has a campaign with this name, or the ad text breaks Google's advertising policies. `details` says what to change. |
| `400` | The Google user you linked cannot access this ad account, Google Ads access was revoked, the campaign is not yours, or a change targets a campaign removed in Google Ads. |
| `370` | Google could not complete the request, or Google has not yet approved Ayrshare's access. `details` includes Google's request id when there is one. A create that answers `370` because Google's answer was lost may still have created the campaign. Google refuses a second campaign with the same name in one account, so retrying with the same `name` answers `205` instead of creating a duplicate. |
# Update Campaign
Source: https://www.ayrshare.com/docs/apis/ads/google/update-campaign
PUT /ads/google/campaigns/{id}
Start or pause a Google Ads campaign, rename it, or change its daily budget or end date.
Start or pause a Google Ads campaign you created through Ayrshare, rename it, or change its daily budget or end date.
Send at least one field. All changes in one request apply together or not at all.
You cannot change `type`, `accountId` or `euPoliticalAdvertising` after you create the campaign.
No `Idempotency-Key` is needed: sending the same change again sets the same values.
The response returns the campaign's `campaignStatus`. Use [Get Campaign](/docs/apis/ads/google/get-campaign) to read the other fields.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The `campaignId` returned when you created the campaign.
## Body Parameters
**status** `string`
`active` or `paused`. A request that only sends `status: "paused"` is not blocked when Ayrshare has paused ads on Google.
**name** `string`
The new campaign name, 1 to 255 characters, with no line breaks.
**budget** `object`
`{ "amount": number }`: the new amount to spend per day, in the ad account's currency, with at most two decimal places. A campaign whose budget you shared with other campaigns in Google Ads answers `code: 101`: change the shared budget in Google Ads.
**schedule** `object`
`{ "end": string }`: the new end date, as an ISO date (`2026-11-30`) or date-time. Google schedules whole days in the ad account's time zone, so the campaign stops at 23:59:59 on that day. It can be the same day as the start, but not an earlier one.
## Errors
| Code | When |
| - | - |
| `101` | No field was sent, a field is invalid, or the budget is shared with other campaigns. `details` says which. |
| `205` | Google refused the change. `details` says what to change. |
| `400` | The campaign is not yours, does not exist, or was removed in Google Ads. Also when the Google user you linked cannot access the ad account, or Google Ads access was revoked. |
| `370` | Google could not complete the request. Try again. |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.ayrshare.com/api/ads/google/campaigns/21534567890 \
-d '{"status": "active", "budget": { "amount": 30 }}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/google/campaigns/21534567890", {
method: "PUT",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({"status": "active", "budget": { "amount": 30 }})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
}
payload = {"status": "active", "budget": { "amount": 30 }}
r = requests.put("https://api.ayrshare.com/api/ads/google/campaigns/21534567890", headers=headers, json=payload)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/google/campaigns/21534567890",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => $payload
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var payload = """
{"status": "active", "budget": { "amount": 30 }}
""";
var response = await client.PutAsync(
"https://api.ayrshare.com/api/ads/google/campaigns/21534567890",
new StringContent(payload, Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
func main() {
payload := `{"status": "active", "budget": { "amount": 30 }}`
req, _ := http.NewRequest("PUT", "https://api.ayrshare.com/api/ads/google/campaigns/21534567890", strings.NewReader(payload))
req.Header.Add("Authorization", "Bearer API_KEY")
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class UpdateCampaign {
public static void main(String[] args) throws Exception {
String payload = """
{"status": "active", "budget": { "amount": 30 }}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/google/campaigns/21534567890"))
.header("Authorization", "Bearer API_KEY")
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/google/campaigns/21534567890')
request = Net::HTTP::Put.new(uri)
request['Authorization'] = 'Bearer API_KEY'
request['Content-Type'] = 'application/json'
request.body = <<~JSON
{"status": "active", "budget": { "amount": 30 }}
JSON
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"platform": "google",
"campaignId": "21534567890",
"campaignStatus": "active"
}
```
```json 400: Shared Budget theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "This campaign uses a budget shared with other campaigns in Google Ads. Change the shared budget in Google Ads."
}
```
# Boost Post
Source: https://www.ayrshare.com/docs/apis/ads/instagram/boost-post
POST /ads/instagram/boost
Boost an existing Instagram post into a paid ad with custom targeting, budget, scheduling, and ad goals, plus the payment method and settings Meta requires.
Boost an existing Instagram post to create an ad.
This endpoint allows you to convert your organic posts into paid advertisements with custom targeting, budget, and scheduling parameters.
Budget and bid amounts are in the ad account's currency. Use up to two decimal places, or whole numbers for the currencies Meta takes in whole units: CLP, COP, CRC, HUF, IDR, ISK, JPY, KRW, PYG, TWD and VND.
The ad must run for at least 30 hours to address the Instagram requirement.
You can use the [interests endpoint](/docs/apis/ads/instagram/get-ad-interests) to find interest IDs
for targeting.
Instagram may take up to 24 hours to review and approve boosted posts.
If using `socialPostId` directly (instead of Ayrshare `postId`), ensure it's a valid Instagram
post ID.
If a boost fails after Facebook has created its campaign (at the creative, ad set or ad step),
we try to delete that campaign. If the delete also fails, the empty campaign stays in your ad
account. You can find and delete it in Ads Manager.
> [!NOTE]
>
> Meta refuses the ad until both of these are in place:
>
> * **A payment method on the ad account.** Add one in Meta's [Billing & payments](https://business.facebook.com/billing_hub/payment_settings/?placement=ads_manager) and attach it to the ad account you send as `accountId`. A payment method saved only on the business portfolio is not enough.
> * **Personalized ads turned on (EU, EEA and Switzerland).** If the owner of the Facebook or Instagram account linked to your Facebook Page chose to see less-personalized ads, or subscribed to use Meta without ads, Meta does not let that account or the Page run ads. The owner can switch back to personalized ads, or cancel the subscription, in Accounts Center → Ad preferences → Ad experience. [Learn more](https://www.facebook.com/business/help/1563729837497242).
### Ad Goals
Each ad must have a goal. The goal determines how the ad will be optimized for display.
`engagement`: This goal seeks to increase engagement while ensuring that the ad reaches the
maximum number of unique users. It balances visibility with engagement, showing the ad to as
many different people as possible who may interact with it.
`interactions`: Designed to boost interactions, such as likes, comments, and shares, on the ad.
Instagram prioritizes showing the ad to users most likely to engage with it.
`awareness_views`: Focuses on increasing brand awareness by maximizing the number of times the
ad is displayed. It prioritizes showing the ad as many times as possible within the budget,
regardless of unique reach.
`awareness_audience`: Aims to enhance brand awareness by maximizing the number of unique people
who see the ad. It ensures that the ad reaches as many different users as possible and maximizes
the unique audience size rather than showing it multiple times to the same audience.
> [!NOTE]
>
> Instagram ads require an Instagram Business account connected through Facebook, an associated Facebook Page, and the `ads_management` permission. Relink through Facebook to grant this permission. Accounts connected with Instagram Login cannot use these ads endpoints. The media ID and Instagram account ID are resolved from your linked account; do not send `instagram_user_id`.
The `socialPostId` selector also accepts the Instagram-specific alias `igPostId`. `fbPostId` applies only to Facebook. Successful responses include `platform: "instagram"`, `socialPostId`, and `igPostId`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**idempotencyKey** `string`
A unique, non-empty key for this boost request. Retry the same payload with the same key to avoid creating a duplicate ad. Reusing the key with a different payload, or for a Facebook boost, returns HTTP 409. On any other platform, the same key starts a separate boost. A request still in progress also returns 409; an unavailable reservation returns 503 without creating an ad.
**adSetBudgetSharingEnabled** `boolean` (default: false)
Allow Meta to share budget between ad sets in the campaign.
**accountId** `string` *required*
The ID of the Instagram ad account to boost the post on. The account ID can be retrieved from the
[ad accounts endpoint](/docs/apis/ads/instagram/get-ad-accounts).
**adName** `string` *required*
Name for your ad (appears in Instagram Ad Manager) with the format `{adName} - {postId or socialPostId} - {current date}`. If you send an `idempotencyKey`, the name also ends in ` - {request reference}`, a short reference that stays the same for that key.
**bidAmount** `number` *required*
Maximum bid amount in the ad account's currency, as a positive amount.
**budget** `number` *required*
Daily budget in the ad account's currency, as a positive amount. Meta may enforce minimums for your account and goal.
**socialPostId** `string`
The [Instagram social post ID](/docs/apis/overview#social-post-id) of the post to boost, which allows
you to create an ad from a post created directly on Instagram. This is the ID of the [post on
Instagram](/docs/apis/history/history-platform), not Ayrshare. Required if `postId` is not set.
**goal** `string` (default: engagement)
The goal of the ad. Values: `engagement`, `interactions`, `awareness_views`, and
`awareness_audience`. See [ad goals details](/docs/apis/ads/instagram/boost-post#ad-goals) above for
more information.
**locations** `object`
Target countries with `{"countries": ["US", "CA"]}` using [country codes](/docs/iso-codes/country). You can also supply `regions` and `cities` arrays; see [regions](/docs/apis/ads/instagram/get-ad-regions) and [cities](/docs/apis/ads/instagram/get-ad-cities). An object that names no country, region or city, such as `{}`, is refused with code `101`; leave `locations` out to target the US.
**postId** `string`
The [Ayrshare post ID](/docs/apis/overview#ayrshare-post-id) of the post to boost. Required if
`socialPostId` is not set.
**status** `string` (default: active)
The status of the ad. Values: `active` and `paused`.
You can later change the status of the ad using the [update ad endpoint](/docs/apis/ads/instagram/put-ad-update).
**tracking** `object`
Track the ad using a Meta Pixel.
**child attributes**
**pixelId** `number` *required*
The ID of the Meta Pixel to track the ad.
```json theme={"system"}
{
"pixelId": 1234567890
}
```
**urlTags** `array`
Add UTM tags to the ad URL.
```json theme={"system"}
{
"urlTags": ["utm_source=ayrshare", "utm_medium=social", "utm_campaign=ayrshare-social"]
}
```
For example if the linking URL is `https://www.mysite.com/my-post` and the URL tags added are:
`utm_source=ayrshare`, `utm_medium=social`, and `utm_campaign=ayrshare-social`.
The ad URL will be:
`https://www.mysite.com/my-post?utm_source=ayrshare&utm_medium=social&utm_campaign=ayrshare-social`.
**specialAdCategories** `array`
Meta requires advertisers in [special ad categories](https://www.facebook.com/business/help/298000447747885?helpref=faq_content) to self-identify their campaign category.
If your business is in one of these categories, you must select the appropriate category when boosting your post.
The following values are supported:
`housing`: Ads that promote or directly link to a housing opportunity or related service,
including but not limited to listings for the sale or rental of a home or apartment, homeowners
insurance, mortgage insurance, mortgage loans, housing repairs and home equity or appraisal
services.
`financial_product_services`: Ads that promote or directly link to a financial products and
services offer, including credit.
`employment`: Ads that promote or directly link to an employment opportunity, including but not
limited to part- or full-time jobs, internships or professional certification programs. Related
ads that fall within this category include promotions for job boards or fairs, aggregation
services or ads detailing perks a company may provide, regardless of a specific job offer.
`issues_elections_politics`: Ads made by, on behalf of, or about a candidate for public office,
a political figure, a political party or advocating for the outcome of an election to public
office. This also includes ads about any election, referendum or ballot initiative, including
"Go out and vote" election campaigns. Ads regulated as political advertising or about social
issues in any place where the ad is being placed. If selecting issues, elections, or politics,
you must select the country in which you want to run these ads. You are required to be
authorized to run ads about social issues, elections, or politics in the specified country.
Meta also needs the countries a special category ad runs in. Ayrshare takes them from `locations`: the countries you list, plus the country of each region and city. So a `housing` boost that targets a city in the UK is set up for the UK, with nothing extra in your request. Without `locations` the ad runs in the US, and the campaign is set up for the US.
Meta also sets a minimum radius around each city for these categories. A UK city with a 17 kilometer radius was rejected with `#2909039`, which asks for at least 25 kilometers (15 miles), so use 25 kilometers or more.
> [!WARNING]
>
> Meta requires advertisers to identify their campaign category correctly.
> Meta uses human reviewers and machine-learning to identify these kinds of ads.
> If you send us an incorrect `specialAdCategories`, there is a risk your ads will be paused until the campaign is adjusted.
**dsaBeneficiary** `string`
The person or organization benefiting from the ad. Set together with `dsaPayor`. Use the [DSA recommendations endpoint](/docs/apis/ads/instagram/get-dsa-recommendations) for suggestions.
**dsaPayor** `string`
The person or organization paying for the ad. Set together with `dsaBeneficiary`.
**endDate** `string`
End date and time in ISO 8601 format (must be at least 30 hours after start), for example `2025-03-01T00:00:00Z`.
If not set, the ad will run indefinitely and have end date of `ongoing`.
**excludedLocations** `object`
Exclude locations using the same `countries`, `regions`, and `cities` structure as `locations`.
**gender** `string` (default: all)
The gender of the audience. Values: `all`, `male`, `female`.
**interests** `array`
The target interests of the ad as an array of Instagram [interest
ids](/docs/apis/ads/instagram/get-ad-interests).
**maxAge** `number` (default: 65)
Maximum age for targeting the ad (default: 65).
**minAge** `number` (default: 18)
Minimum age for targeting the ad (default: 18).
**startDate** `string`
Start date and time in ISO 8601 format, for example `2025-03-01T00:00:00Z`.
If not set, the ad will start immediately.
Replace the example IDs with your own. Set `startDate` and `endDate` to your intended schedule; the example dates are illustrative.
## Lifetime Budgets
The body above sets a daily budget on the ad set. For a lifetime budget, send the nested form of the boost body instead.
In the nested form, `accountId`, `postId` (or `socialPostId` in place of `igPostId`), `goal`, `status` and `callToAction` stay at the top level. Three objects replace the flat budget, date and targeting fields: `budget`, `schedule` (`start`, `end`) and `targeting` (`locations`, `excludedLocations`, `ageMin`, `ageMax`, `genders`, `interests`). The remaining fields, `adName`, `bidAmount`, `specialAdCategories`, `adSetBudgetSharingEnabled`, `link`, `tracking`, `urlTags`, `dsaBeneficiary` and `dsaPayor`, move under `platformOptions.instagram`.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"budget": { "amount": 50, "type": "lifetime" },
"schedule": { "start": "2026-11-01T00:00:00Z", "end": "2026-11-08T00:00:00Z" },
"targeting": { "ageMin": 25, "ageMax": 45, "locations": { "countries": ["US"] } },
"platformOptions": { "instagram": { "adName": "November boost", "bidAmount": 2 } }
}
```
* `budget.type`: `daily` (the default) or `lifetime`. A lifetime budget is spent between the start and end dates, so it needs `schedule.end`. Without one the request is rejected with a `400` and `code: 101`. Meta's ad set budget sharing works with daily budgets only, so a lifetime budget with `adSetBudgetSharingEnabled: true` is also rejected with `code: 101`.
The response adds `budgetType: "lifetime"` for a lifetime budget. A daily budget does not return `budgetType`, so treat a missing field as `daily`.
## Campaign Budgets
To hold the budget on the campaign instead of the ad set, send `budget.level: "campaign"` in the nested form shown above. The ad set then has no budget of its own. It works with daily and lifetime budgets.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"budget": { "amount": 50, "type": "lifetime", "level": "campaign" },
"schedule": { "start": "2026-11-01T00:00:00Z", "end": "2026-11-08T00:00:00Z" },
"platformOptions": { "instagram": { "adName": "November boost", "bidAmount": 2 } }
}
```
* `budget.level`: `adSet` (the default) or `campaign`. A campaign budget cannot be combined with `adSetBudgetSharingEnabled`. Sending both is rejected with a `400` and `code: 101`.
* The response adds `budgetLevel: "campaign"` for a campaign budget. An ad set budget does not return `budgetLevel`, so treat a missing field as `adSet`.
* In [History](/docs/apis/ads/instagram/get-ad-history), `budgetDaily` shows the campaign's daily budget for an ad with a daily campaign budget. If several ads share one campaign budget, each shows the whole campaign budget, not its share. History does not show lifetime budgets, on the campaign or the ad set. Once a lifetime campaign budget is spent, every ad in that campaign shows `deliveryStatus: "COMPLETED"` after the next nightly update, including an ad that is scheduled or in review. A rejected ad stays `REJECTED`.
## Boost into an Existing Campaign or Ad Set
By default every boost creates its own campaign, ad set and ad. To add the boost to a campaign or ad set you already run in Meta, send the nested form with `attachTo`.
* `attachTo.campaignId`: the boost creates a new ad set and ad inside that campaign. Send `budget`, `targeting` and `bidAmount` as usual: they apply to the new ad set.
* `attachTo.adSetId`: the boost creates only the ad, inside that ad set. Also send `attachTo.campaignId`, the campaign the ad set belongs to. Leave out `budget`, `targeting` and `platformOptions.instagram.bidAmount`: the ad set keeps its own. Sending any of them returns a `400` with `code: 101`.
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"attachTo": { "campaignId": "120210000000000001", "adSetId": "120210000000000002" },
"platformOptions": { "instagram": { "adName": "November boost" } }
}
```
* The campaign or ad set must be in the ad account named by `accountId`, and must not belong to another user's boosts. Otherwise the request returns a `400`. An Instagram boost also cannot attach to a campaign a Facebook boost created, and the reverse.
* `attachTo` cannot be combined with `budget.level: "campaign"`, `specialAdCategories` or `adSetBudgetSharingEnabled`, which are set on a new campaign. Sending them returns `code: 101`.
* Pausing or activating the boost changes only what the boost created: the ad, and the ad set when the boost created it. The campaign or ad set you named in `attachTo` keeps its own status. The ad runs only while the campaign and ad set above it are active. This also applies when Ayrshare pauses your ads, for example when a profile is deleted.
* If the boost fails, the campaign or ad set named in `attachTo` is never deleted.
* If the ad set you attach to sits in a campaign that holds the budget (a campaign budget), [History](/docs/apis/ads/instagram/get-ad-history) reports the boost like any ad with a campaign budget: `budgetDaily` shows the whole campaign budget, not the boost's share, and once a lifetime campaign budget is spent, every ad Ayrshare tracks in that campaign shows `deliveryStatus: "COMPLETED"` after the next nightly update, including an ad that is scheduled or in review. A rejected ad stays `REJECTED`.
## Open a Conversation Instead of a Link
A boost's button can open a chat with your business instead of a web page. Send the nested form with `callToAction.destination`, and leave out `callToAction.link`.
| `destination` | Opens | `callToAction.type` |
| - | - | - |
| `messenger` | Messenger | `MESSAGE_PAGE` |
| `whatsapp` | WhatsApp | `WHATSAPP_MESSAGE` |
| `instagramDm` | Instagram Direct | `INSTAGRAM_MESSAGE` |
```json theme={"system"}
{
"accountId": "123456789",
"postId": "Ut1fWU6XkqkMayHGnJZ",
"goal": "engagement",
"budget": { "amount": 20 },
"callToAction": { "type": "WHATSAPP_MESSAGE", "destination": "whatsapp" },
"platformOptions": { "instagram": { "adName": "Chat with us", "bidAmount": 2 } }
}
```
* Use the `engagement` or `interactions` [ad goal](/docs/apis/ads/instagram/boost-post#ad-goals). The ad is then optimized for conversations: Meta shows it to people likely to start a chat. Other goals return a `400` with `code: 101`.
* `callToAction.type` must match the destination, as in the table. Any casing is accepted.
* `callToAction.link` and `callToAction.linkTitle` cannot be sent with `destination`, because the button opens a chat, not a page. Without `destination`, `link` is still required.
* People message the account linked to your profile. For `whatsapp`, its Facebook Page must have a WhatsApp Business number connected in Meta.
* `destination` cannot be combined with `attachTo.adSetId`: the existing ad set keeps the destination it already has. With `attachTo.campaignId`, the campaign must have Meta's `OUTCOME_ENGAGEMENT` objective; otherwise the request returns a `400` with `code: 101`.
* The response, and [Get Boosted Ads](/docs/apis/ads/instagram/get-boosted-ads), return `callToAction` with its `destination`. Meta stores the same settings for both goals, so a chat button boosted with `interactions` can be listed with the `engagement` goal when the ad is read back from Meta.
```bash cURL theme={"system"}
curl -X POST https://api.ayrshare.com/api/ads/instagram/boost \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-d '{
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": {"countries": ["US"]},
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": [1234567890, 1234567891]
}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/boost", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
postId: "1234567890",
accountId: "1234567890",
adName: "My Ad",
status: "active",
goal: "engagement",
minAge: 18,
maxAge: 65,
locations: { countries: ["US"] },
budget: 100,
bidAmount: 1,
startDate: "2025-03-01T00:00:00Z",
endDate: "2025-03-07T23:59:59Z",
interests: [1234567890, 1234567891]
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
API_KEY = "API_KEY"
url = "https://api.ayrshare.com/api/ads/instagram/boost"
payload = {
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": {"countries": ["US"]},
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": [1234567890, 1234567891]
}
response = requests.post(url, headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}, json=payload)
print(response.json())
```
```php PHP theme={"system"}
"1234567890",
"accountId" => "1234567890",
"adName" => "My Ad",
"status" => "active",
"goal" => "engagement",
"minAge" => 18,
"maxAge" => 65,
"locations" => ["countries" => ["US"]],
"budget" => 100,
"bidAmount" => 1,
"startDate" => "2025-03-01T00:00:00Z",
"endDate" => "2025-03-07T23:59:59Z",
"interests" => [1234567890, 1234567891]
];
$ch = curl_init("https://api.ayrshare.com/api/ads/instagram/boost");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Content-Type: application/json",
"Authorization: Bearer " . $API_KEY
]);
$response = curl_exec($ch);
$result = json_decode($response, true);
print_r($result);
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string API_KEY = "API_KEY";
var payload = new
{
postId = "1234567890",
accountId = "1234567890",
adName = "My Ad",
status = "active",
goal = "engagement",
minAge = 18,
maxAge = 65,
locations = new { countries = new[] { "US" } },
budget = 100,
bidAmount = 1,
startDate = "2025-03-01T00:00:00Z",
endDate = "2025-03-07T23:59:59Z",
interests = new[] { 1234567890, 1234567891 }
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", API_KEY);
var content = new StringContent(
JsonSerializer.Serialize(payload),
Encoding.UTF8,
"application/json"
);
var response = await client.PostAsync("https://api.ayrshare.com/api/ads/instagram/boost", content);
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
payload := map[string]interface{}{
"postId": "1234567890",
"accountId": "1234567890",
"adName": "My Ad",
"status": "active",
"goal": "engagement",
"minAge": 18,
"maxAge": 65,
"locations": map[string]interface{}{"countries": []string{"US"}},
"budget": 100,
"bidAmount": 1,
"startDate": "2025-03-01T00:00:00Z",
"endDate": "2025-03-07T23:59:59Z",
"interests": []int{1234567890, 1234567891},
}
jsonData, err := json.Marshal(payload)
if err != nil {
fmt.Println("Error marshaling JSON:", err)
return
}
req, err := http.NewRequest("POST", "https://api.ayrshare.com/api/ads/instagram/boost", bytes.NewBuffer(jsonData))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.databind.ObjectMapper;
public class BoostPost {
public static void main(String[] args) {
String API_KEY = "API_KEY";
String url = "https://api.ayrshare.com/api/ads/instagram/boost";
Map payload = new HashMap<>();
payload.put("postId", "1234567890");
payload.put("accountId", "1234567890");
payload.put("adName", "My Ad");
payload.put("status", "active");
payload.put("goal", "engagement");
payload.put("minAge", 18);
payload.put("maxAge", 65);
payload.put("locations", Map.of("countries", Arrays.asList("US")));
payload.put("budget", 100);
payload.put("bidAmount", 1);
payload.put("startDate", "2025-03-01T00:00:00Z");
payload.put("endDate", "2025-03-07T23:59:59Z");
payload.put("interests", Arrays.asList(1234567890, 1234567891));
try {
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(payload);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + API_KEY)
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
require 'json'
API_KEY = "API_KEY"
uri = URI.parse("https://api.ayrshare.com/api/ads/instagram/boost")
payload = {
postId: "1234567890",
accountId: "1234567890",
adName: "My Ad",
status: "active",
goal: "engagement",
minAge: 18,
maxAge: 65,
locations: { countries: ["US"] },
budget: 100,
bidAmount: 1,
startDate: "2025-03-01T00:00:00Z",
endDate: "2025-03-07T23:59:59Z",
interests: [1234567890, 1234567891]
}
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.request_uri)
request["Content-Type"] = "application/json"
request["Authorization"] = "Bearer #{API_KEY}"
request.body = payload.to_json
response = http.request(request)
puts response.body
```
```json 200: Post Boosted theme={"system"}
{
"status": "success",
"adId": "120217670757750410",
"adName": "API Post - DE6gpw8kxlonHy2b7Lo - 2025-03-26T23:42:43",
"adStatus": "active",
"bidAmount": 10,
"budget": 100,
"endDate": "2026-03-28T22:30:00Z",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement...",
"type": "engagement"
},
"interests": [
"6003195554098"
],
"locations": {"countries": ["US"]},
"maxAge": 65,
"minAge": 18,
"postId": "DE6gpw8kxlonHy2b7Lo",
"socialPostId": "1234567890",
"igPostId": "1234567890",
"platform": "instagram",
"startDate": "2026-03-26T22:30:00Z"
}
```
```json 400: Invalid Account ID theme={"system"}
{
"action": "boost post",
"status": "error",
"code": 369,
"message": "Unable to boost post. Please try again or contact us if the issue persists.",
"details": "The accountId likely does not exist. Please check the accountId and try again."
}
```
```json 400: Missing Required Parameters theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "Missing required fields: accountId"
}
```
# Accounts
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-accounts
GET /ads/instagram/accounts
List Meta ad accounts for Instagram ads on the authenticated profile with status and metrics. Needs an Instagram Business account linked through Facebook.
Retrieve available Instagram ad accounts associated with the authenticated profile.
This endpoint allows you to access basic account information as well as key metrics for the ad accounts.
The `metrics` object contains aggregate performance data across all ads in the account
Results are cached for 10 minutes to optimize performance and reduce API calls to Instagram
Account status in the JSON response may be one of: `active`, `disabled`, `unsettled`, `pending_risk_review`, `pending_settlement`, `in_grace_period`, `pending_closure`, `closed`, `any_active`, or `any_closed`
> [!NOTE]
>
> These endpoints require an Instagram Business account connected through Facebook with ads management permission. Relink through Facebook if that permission is missing. Instagram Login is not supported. Meta ad accounts can contain ads for both Facebook and Instagram; the account list is not an Instagram-only list. Submitted ads and history are scoped to Instagram.
Use `facebook` or `instagram` in the ads URL. Other platform values return HTTP 400 with `code: 101`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**limit** `number` (default: 100)
Limit the number of ad accounts returned.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/accounts
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/accounts", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/accounts', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/accounts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
]
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string apiKey = "API_KEY";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
HttpResponseMessage response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/accounts");
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/accounts", nil)
req.Header.Add("Authorization", "Bearer " + apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class GetAdAccounts {
public static void main(String[] args) {
try {
String apiKey = "API_KEY";
URL url = new URL("https://api.ayrshare.com/api/ads/instagram/accounts");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/instagram/accounts')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"adAccounts": [
{
"accountId": "274948345",
"ageInDays": 2870.86,
"amountSpent": 191.33,
"balance": 7.84,
"budgetRemaining": 1,
"business": {
"name": "Fun Fun Fun",
"city": "New York",
"country": "US",
"state": "NY",
"street": "178 Columbus Ave",
"zip": "10023"
},
"created": "2012-01-05T03:11:31-0800",
"currency": "USD",
"disableReason": null,
"fundingSource": {
"id": "13157487252022",
"type": "Credit Card"
},
"hasNotificationsEnabled": true,
"isPersonal": false,
"isPrepayAccount": false,
"metrics": {
"spend": 191.33,
"impressions": 24994,
"reach": 20410,
"clicks": 622,
"ctr": 2.488597,
"cpm": 7.655037,
"cpp": 9.374326,
"frequency": 1.224596,
"uniqueClicks": 432,
"uniqueCtr": 2.11661,
"costPerUniqueClick": 0.442894,
"inlineLinkClicks": 538,
"costPerInlineLinkClick": 0.355632,
"outboundClicks": 468,
"costPerOutboundClick": 0.408825,
"websiteCtr": [
{
"action_type": "link_click",
"value": "2.152517"
}
],
"accountCurrency": "USD",
"accountName": "John Smith",
"accountId": "274948321"
},
"minDailyBudget": 1,
"name": "John Smith",
"ownerBusiness": {
"name": "My Business"
},
"spendCap": 0,
"status": "active",
"taxIdStatus": "NOT_REQUIRED",
"timezoneId": 74,
"timezoneName": "America/New_York",
"timezoneOffsetHoursUtc": -5
}
],
"count": 1,
"lastUpdated": "2025-03-27T00:55:00.000Z",
"nextUpdate": "2025-03-27T01:05:00.000Z"
}
```
```json 400: Ad accounts error theme={"system"}
{
"action": "get ad accounts",
"status": "error",
"code": 366,
"message": "Error getting ad accounts. Please verify you have an active ad account at Meta. You also might not have granted permission to access your ad account. Please try unlinking and relinking the platform and try again. Contact us if the issue persists."
}
```
# Campaigns
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-campaigns
GET /ads/instagram/campaigns
Retrieve campaigns for an Instagram ad account by accountId, with status, budgetRemaining, metrics, and scheduling details. Results cache for 10 minutes.
Retrieve ad campaigns for a specific Instagram ad account. This endpoint provides detailed information about campaigns, including budget, status, performance metrics, and scheduling details.
Campaign status may be one of: "ACTIVE", "PAUSED", "DELETED", "ARCHIVED", "IN\_PROCESS", or
"WITH\_ISSUES"
The `metrics` object contains performance data for the campaign
The `budgetRemaining` field represents the remaining budget in the account's currency
Results are cached for 10 minutes to optimize performance
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
The ID of the ad account to retrieve campaigns for.
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
**limit** `number` (default: 100)
Limit the number of campaigns returned.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
[
'header' => "Authorization: Bearer " . $apiKey,
'method' => 'GET'
]
];
$context = stream_context_create($options);
$response = file_get_contents($url, false, $context);
$result = json_decode($response, true);
print_r($result);
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string apiKey = "API_KEY";
string url = "https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890";
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
HttpResponseMessage response = await client.GetAsync(url);
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
apiKey := "API_KEY"
url := "https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer " + apiKey)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class Main {
public static void main(String[] args) {
try {
String apiKey = "API_KEY";
URL url = new URL("https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + apiKey);
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
System.out.println(response.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
require 'json'
api_key = "API_KEY"
uri = URI.parse("https://api.ayrshare.com/api/ads/instagram/campaigns?accountId=1234567890")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{api_key}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts JSON.parse(response.body)
```
```json 200: Success theme={"system"}
{
"status": "success",
"campaigns": [
{
"accountId": "8501481455633",
"boostedObjectId": "67263320208733",
"budgetRemaining": 0,
"buyingType": "AUCTION",
"campaignId": "1202176707545833",
"configuredStatus": "ACTIVE",
"created": "2025-03-26T19:42:43-0400",
"dailyBudget": 0,
"effectiveStatus": "ACTIVE",
"endDate": "2026-03-28T18:30:00-0400",
"lifetimeBudget": 0,
"metrics": {},
"name": "API Post - DE6gpw8kxlonHy6eb33 - 2025-03-26T23:42:43",
"spendCap": 0,
"startDate": "2026-03-26T18:30:00-0400",
"status": "ACTIVE"
}
],
"count": 1,
"lastUpdated": "2025-03-27T00:57:15.339Z",
"nextUpdate": "2025-03-27T01:08:15.339Z"
}
```
```json 400: Ad campaigns error theme={"system"}
{
"action": "get ad campaigns",
"status": "error",
"code": 367,
"message": "Error getting ad campaigns. Please verify you have an active ad campaign account."
}
```
# Cities
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-cities
GET /ads/instagram/cities
Find cities by name for Instagram ad targeting. Results include Facebook's key, region and regionId, supportsCity and supportsRegion flags, and subcities.
Search for cities to be used for targeting when boosting an ad.
Cities use Facebook's `key` identifier and support radius targeting.
Each city result includes its parent region as `region` (the region name) and `regionId` (the region's numeric identifier), so you can target the city's region when it is available.
If a returned city has `supportsRegion` `true`, the region for this city is available for targeting. If `supportsCity` is `true`, this city is available for targeting.
Some locations may be returned as `subcity` with additional hierarchy fields.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string`
The search string to filter cities. Provide a partial or full city name.
**limit** `number` (default: 25)
Maximum number of cities to return.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdCities {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/instagram/cities?search=Manhattan')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"cities": [
{
"key": "2447439",
"name": "Manhattan",
"type": "city",
"countryCode": "US",
"countryName": "United States",
"region": "Kansas",
"regionId": 3859,
"supportsRegion": true,
"supportsCity": true
},
{
"key": "2703980",
"name": "Manhattan",
"type": "subcity",
"countryCode": "US",
"countryName": "United States",
"region": "New York",
"regionId": 3875,
"supportsRegion": true,
"supportsCity": true,
"geoHierarchyLevel": "SUBCITY",
"geoHierarchyName": "BOROUGH"
}
],
"count": 2
}
```
```json 414: Location metadata error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 414,
"message": "Error getting ad location metadata. Please try again."
}
```
# History
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-history
GET /ads/instagram/history
Retrieve daily Instagram ad spend and performance metrics, with goal-specific results, aggregated totals, and filters by account, ad, post, and date range.
Retrieve detailed daily ad spend and performance metrics for your Instagram ads.
This endpoint provides comprehensive analytics on your ads with daily breakdowns, goal-specific performance, and aggregated metrics.
> [!TIP]
>
> The `history` array contains historical daily spend and performance metrics
> for each ad. The data is as of 8 AM EST. If real-time spend data is needed or spend has not yet occurred,
> please use the [ads endpoint](/docs/apis/ads/instagram/get-boosted-ads).
>
> The ad will **NOT** be returned if no spend data is available or spend has not yet occurred.
>
> When no ad has spend in the date range, the response is still a success: `history` is
> empty and `warnings` holds one entry with code `401`.
Metrics are calculated from Instagram's reporting API and may have slight delays (up to 24
hours).
The `byGoalType` and `byGoalTitle` sections group performance metrics by goal objectives.
Use either `adId` or `postId` filters to narrow results to specific campaigns.
Each day's `spend` is in the ad account's currency, shown in `currency`. `spendUsd` is the same spend in US dollars.
Non-USD spend is converted to USD at that day's exchange rate from Open Exchange Rates. `totalSpend`, `cpm`, `cpp`, `cpc`, `costPerResult` and the goal `spend` totals are in US dollars.
`totalSpendByCurrency` gives the total spend in each currency before conversion. `unconvertedRows` counts the days not converted yet: they count at face value in `totalSpend`, and their `spendUsd` is `null`.
Spend from before October 2026 may show `currency: "USD"` even when the ad account uses another currency, and is counted in `totalSpend` as recorded.
The `goalPerformance` section highlights your best-performing ad objectives.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string`
Filter results to a specific account ID.
**adId** `string`
Filter results to a specific ad ID.
**postId** `string`
Filter results to ads associated with a specific [Ayrshare post
ID](/docs/apis/overview#ayrshare-post-id).
**socialPostId** `string`
Filter results to ads associated with a specific [Instagram social post
ID](/docs/apis/overview#social-post-id).
**limit** `number` (default: 25)
Retrieve a maximum of `limit` ads. Maximum is 300; higher values are capped at 300.
**startDate** `string` (default: 30 days ago)
Start date for the reporting period in ISO 8601 format (default: 30 days ago)
**endDate** `string` (default: today)
End date for the reporting period in ISO 8601 format (default: today)
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/history?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/history?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/history', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/history",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/history");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/history", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdSpend {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/history"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/instagram/history')
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts response.body
```
```json 200: Success No Param theme={"system"}
{
"status": "success",
"history": [
{
"adId": "6683876017501",
"adName": "API Post 1 - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 3,
"impressions": 1035,
"reach": 1026,
"frequency": 1.01,
"ctr": 0.29,
"cpm": 1.53,
"cpp": 1.54,
"cpc": 0.53
},
"network": "instagram",
"postId": "DE6gpw8kxlonHy6eb7L1",
"spend": 1.58,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 1.58,
"status": "ACTIVE",
"accountId": "274948332"
},
{
"adId": "120218167147110411",
"adName": "API Post 2 - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T16:41:15",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 4,
"impressions": 1036,
"reach": 1011,
"frequency": 1.02,
"ctr": 0.39,
"cpm": 1.61,
"cpp": 1.65,
"cpc": 0.42
},
"network": "instagram",
"postId": "DE6gpw8kxlonHy6eb7L1",
"spend": 1.67,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 1.67,
"status": "ACTIVE",
"accountId": "274948332"
}
],
"dateRange": {
"start": "2025-03-04T18:49:18.159Z",
"end": "2025-04-03T18:49:18.159Z"
},
"metrics": {
"totalSpend": 3.25,
"totalSpendByCurrency": { "USD": 3.25 },
"unconvertedRows": 0,
"totalClicks": 7,
"totalImpressions": 2071,
"totalReach": 2037,
"averageCTR": 0.34,
"averageFrequency": 1.02,
"cpm": 1.57,
"cpp": 1.6,
"cpc": 0.46,
"costPerResult": 0.46,
"byGoalType": {
"engagement": {
"spend": 3.25,
"impressions": 2071,
"clicks": 7,
"reach": 2037,
"count": 2,
"ctr": 0.3380009657170449,
"cpm": 1.5692901979719942,
"cpp": 1.5954835542464407,
"cpc": 0.4642857142857143
}
},
"byGoalTitle": {
"Get More Engagement": {
"spend": 3.25,
"impressions": 2071,
"clicks": 7,
"reach": 2037,
"count": 2,
"ctr": 0.3380009657170449,
"cpm": 1.5692901979719942,
"cpp": 1.5954835542464407,
"cpc": 0.4642857142857143,
"type": "engagement"
}
},
"goalPerformance": {
"bestPerformingGoalType": {
"type": "engagement",
"clicks": 7,
"spend": 3.25,
"ctr": 0.34
},
"mostEfficientGoalType": {
"type": "engagement",
"cpc": 0.46,
"spend": 3.25
}
},
"count": 2,
"firstDay": "2025-04-02T00:00:00.000Z",
"lastDay": "2025-04-02T00:00:00.000Z"
}
}
```
```json 200: Success by Ad ID theme={"system"}
{
"status": "success",
"history": [
{
"accountId": "274948332",
"adId": "6683876017505",
"adName": "API Post 13 - DE6gpw8kxlonHy6eb7Lo - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"socialPostId": "17890000000000001",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 2,
"cpc": 0.35,
"cpp": 1.6,
"cpm": 1.55,
"ctr": 0.44,
"frequency": 1.03,
"impressions": 452,
"reach": 438
},
"network": "instagram",
"postId": "DE6gpw8kxlonHy6eb7Lo",
"spend": 0.7,
"spendDate": "2025-04-03T00:00:00.000Z",
"spendUsd": 0.7,
"status": "ACTIVE",
"igPostId": "17890000000000001"
},
{
"accountId": "274948332",
"adId": "6683876017505",
"adName": "API Post 13 - DE6gpw8kxlonHy6eb7Lo - 2025-03-30T17:11:26",
"budgetDaily": 4,
"budgetType": "daily",
"currency": "USD",
"deliveryStatus": "ACTIVE",
"socialPostId": "17890000000000001",
"goal": {
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"title": "Get More Engagement",
"type": "engagement"
},
"isActive": true,
"isComplete": false,
"metrics": {
"clicks": 1,
"cpc": 4.19,
"cpp": 1.5,
"cpm": 1.5,
"ctr": 0.04,
"frequency": 1,
"impressions": 2793,
"reach": 2793
},
"network": "instagram",
"postId": "DE6gpw8kxlonHy6eb7Lo",
"spend": 4.19,
"spendDate": "2025-04-02T00:00:00.000Z",
"spendUsd": 4.19,
"status": "ACTIVE",
"igPostId": "17890000000000001"
}
],
"dateRange": {
"start": "2025-03-04T18:49:18.159Z",
"end": "2025-04-03T18:49:18.159Z",
"adId": "6683876017505"
},
"metrics": {
"totalSpend": 4.89,
"totalSpendByCurrency": { "USD": 4.89 },
"unconvertedRows": 0,
"totalClicks": 3,
"totalImpressions": 3245,
"totalReach": 3231,
"averageCTR": 0.09,
"averageFrequency": 1.02,
"cpm": 1.51,
"cpp": 1.51,
"cpc": 1.63,
"costPerResult": 1.63,
"byGoalType": {
"engagement": {
"spend": 4.890000000000001,
"impressions": 3245,
"clicks": 3,
"reach": 3231,
"count": 2,
"ctr": 0.09244992295839753,
"cpm": 1.50693374422188,
"cpp": 1.5134633240482824,
"cpc": 1.6300000000000001
}
},
"byGoalTitle": {
"Get More Engagement": {
"spend": 4.890000000000001,
"impressions": 3245,
"clicks": 3,
"reach": 3231,
"count": 2,
"ctr": 0.09244992295839753,
"cpm": 1.50693374422188,
"cpp": 1.5134633240482824,
"cpc": 1.6300000000000001,
"type": "engagement"
}
},
"goalPerformance": {
"bestPerformingGoalType": {
"type": "engagement",
"clicks": 3,
"spend": 4.89,
"ctr": 0.09
},
"mostEfficientGoalType": {
"type": "engagement",
"cpc": 1.63,
"spend": 4.89
}
},
"count": 2,
"firstDay": "2025-04-02T00:00:00.000Z",
"lastDay": "2025-04-03T00:00:00.000Z"
}
}
```
```json 200: No spend data theme={"system"}
{
"status": "success",
"history": [],
"dateRange": {
"start": "2025-03-04T18:49:18.159Z",
"end": "2025-04-03T18:49:18.159Z",
"adId": "6683876017505"
},
"metrics": {
"totalSpend": 0,
"totalSpendByCurrency": {},
"unconvertedRows": 0,
"totalClicks": 0,
"totalImpressions": 0,
"totalReach": 0,
"averageCTR": 0,
"averageFrequency": 0,
"cpm": 0,
"cpp": 0,
"cpc": 0,
"costPerResult": 0,
"byGoalType": {},
"byGoalTitle": {},
"goalPerformance": {
"bestPerformingGoalType": null,
"mostEfficientGoalType": null
},
"daysCount": 0,
"firstDay": null,
"lastDay": null
},
"warnings": [
{
"action": "ads",
"status": "error",
"code": 401,
"message": "No spend data found for the ad."
}
]
}
```
```json 400: Ad spend error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 370,
"message": "Error getting ads. Please try again or contact us if the issue persists.",
"details": "Error retrieving ads spend data"
}
```
# Insights
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-insights
GET /ads/instagram/insights
Pull live daily Instagram ad metrics for campaigns, ad sets or ads, split by age, gender, country or placement, with paging for long date ranges.
Get live Instagram ad performance, one row per day, for your campaigns, ad sets or ads.
Pick the metrics you want, split them by age, gender, country or placement, and page through long date ranges.
Numbers are read from Meta when you call, not from a nightly copy.
Use [History](/docs/apis/ads/instagram/get-ad-history) for the spend we bill. Use Insights for reporting
and analysis.
All money values are in the ad account's currency, returned in `currency` on each row.
Campaigns must have been created through Ayrshare for this profile, and ad sets must be in such
a campaign. An ad created through
Ayrshare for another profile is refused. Other ad ids are read with this profile's linked Meta
account, so Meta only returns ads that account can see.
One request covers one ad account. Ids from several ad accounts return code `101`, and so do
several ad ids that Ayrshare has no record of. Ask for those one at a time.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**level** `string` *required*
What each row reports on: `campaign`, `adSet` or `ad`.
**ids** `string`
Comma-separated campaign, ad set or ad ids, up to 50. Required for `level=campaign` and
`level=adSet`.
With `level=ad`, you can leave `ids` out and send `accountId` instead. This reports on the ads
you created through Ayrshare for this profile in that ad account, not on every ad in it. It works
for up to 50 such ads. For more, send their ids, or report by campaign. If none are found, the
report is empty.
**accountId** `string`
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
Required with `level=ad` when `ids` is left out.
**metrics** `string`
Comma-separated metrics to return. Leave it out to get them all. See [Metrics](#metrics).
**breakdowns** `string`
Comma-separated breakdowns: `age`, `gender`, `country`, `placement`. Each day then has one row per
value, or per combination with `age,gender`. Send one breakdown at a time, or `age,gender`; Meta
refuses other mixes (such as `age,country`), so they return code `101`.
**startDate** `string` (default: 30 days ago)
First day, as `YYYY-MM-DD`. It can be up to 37 months ago.
**endDate** `string` (default: today)
Last day, as `YYYY-MM-DD`.
**cursor** `string`
The `nextCursor` from the previous response, to get the next page.
## Metrics
| Metric | Type | Meaning |
| - | - | - |
| `spend` | number | Amount spent |
| `impressions` | number | Times the ad was shown |
| `reach` | number | People who saw the ad at least once |
| `clicks` | number | Clicks |
| `cpm` | number or `null` | Cost per 1,000 impressions |
| `cpc` | number or `null` | Cost per click. `null` on a day with no clicks |
| `ctr` | number or `null` | Click-through rate, in percent |
| `actions` | array | Each action type Meta counted, with its count |
| `conversionValue` | number | Total value of purchases |
Instagram reports every metric in this table. One exception: Meta does not report `reach` for a report with
`breakdowns` that starts more than 13 months ago, so `reach` is left out of those rows. On networks that don't report a metric, it is left out
of the rows rather than returned as `0`, and asking for it returns code `101`.
> [!NOTE]
>
> Meta counts some actions under more than one type, so don't add `actions` values together. For
> example, `omni_purchase` already includes `purchase`. `conversionValue` counts each purchase once.
## Paging
Each response returns up to 500 rows. If there are more, `nextCursor` is set. Send the same request
again with `cursor` set to that value to get the next page. On the last page, `nextCursor` is `null`.
## Errors
| Code | When |
| - | - |
| `101` | A parameter is missing or wrong, a breakdown or metric isn't supported, `startDate` is more than 37 months ago, more than 50 ids are sent, the ids are in more than one ad account, or Meta refuses the breakdown combination |
| `400` | A campaign, ad set or ad isn't yours, or `accountId` isn't the ad account Ayrshare has on record for the ids you sent. For an ad set, `accountId` must also be the ad account Meta reports for it. For an ad with no Ayrshare record, `accountId` is not checked and the ad is read on its own |
| `370` | Meta could not return the insights. Try again |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/instagram/insights?level=campaign&ids=120200000000000001&startDate=2026-09-01&endDate=2026-09-02"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const params = new URLSearchParams({
level: "campaign",
ids: "120200000000000001",
startDate: "2026-09-01",
endDate: "2026-09-02",
});
fetch(`https://api.ayrshare.com/api/ads/instagram/insights?${params}`, {
method: "GET",
headers: { Authorization: `Bearer ${API_KEY}` },
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
params = {
'level': 'campaign',
'ids': '120200000000000001',
'startDate': '2026-09-01',
'endDate': '2026-09-02',
}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/insights', headers=headers, params=params)
print(r.json())
```
```json 200: Success theme={"system"}
{
"status": "success",
"platform": "instagram",
"level": "campaign",
"startDate": "2026-09-01",
"endDate": "2026-09-02",
"insights": [
{
"date": "2026-09-01",
"level": "campaign",
"id": "120200000000000001",
"currency": "USD",
"spend": 12.34,
"impressions": 4000,
"reach": 3100,
"clicks": 80,
"cpm": 3.085,
"cpc": 0.15425,
"ctr": 2,
"actions": [
{ "type": "link_click", "value": 75 },
{ "type": "omni_purchase", "value": 3 }
],
"conversionValue": 89.97
}
],
"count": 1,
"nextCursor": null
}
```
```json 200: Breakdowns theme={"system"}
{
"status": "success",
"platform": "instagram",
"level": "ad",
"startDate": "2026-09-01",
"endDate": "2026-09-01",
"insights": [
{
"date": "2026-09-01",
"level": "ad",
"id": "120210000000000001",
"age": "25-34",
"gender": "female",
"currency": "EUR",
"spend": 1.5,
"impressions": 300
},
{
"date": "2026-09-01",
"level": "ad",
"id": "120210000000000001",
"age": "25-34",
"gender": "male",
"currency": "EUR",
"spend": 1,
"impressions": 200
}
],
"count": 2,
"nextCursor": null
}
```
```json 400: Not Yours theme={"system"}
{
"action": "ads",
"status": "error",
"code": 400,
"message": "The ad is not found or not authorized."
}
```
# Interests
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-interests
GET /ads/instagram/interests
Search Instagram ad targeting interests by keyword and get audience size estimates. Use returned interest IDs in the interests array to boost posts.
Search for available Instagram ad targeting interests by keyword.
Required for the [Boost Post](/docs/apis/ads/instagram/boost-post) endpoint.
This endpoint helps you discover interest-based targeting options for your Instagram ads, along with audience size estimates.
Interest IDs can be used in the `interests` array when boosting posts.
Results are cached for 10 minutes.
Use specific, relevant search terms for best results.
Instagram may return different results based on your ad account's industry and region.
The `audienceSizeLowerBound` and `audienceSizeUpperBound` fields in the JSON response provide an
estimated range of audience size for the interest and it is useful for targeting.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string` *required*
The search query to retrieve Instagram ad interests.
Don't forget to escape the search query, for example `Rhythm%20and%20blues%20music`.
**limit** `number` (default: 100)
Limit the number of ad interests returned.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdInterests {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/instagram/interests?search=Rhythm%20and%20blues%20music')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music",
"topic": "News and entertainment",
"audienceSizeLowerBound": 669180255,
"audienceSizeUpperBound": 786955980
},
{
"id": "6003470511564",
"name": "Do it yourself (DIY)",
"topic": "Hobbies and activities",
"audienceSizeLowerBound": 418387661,
"audienceSizeUpperBound": 492023890
},
{
"id": "6002926036121",
"name": "Italy",
"topic": "Travel, places and events",
"audienceSizeLowerBound": 279313350,
"audienceSizeUpperBound": 328472500
}
],
"count": 3,
"lastUpdated": "2025-03-27T01:01:38.547Z",
"nextUpdate": "2025-03-27T01:12:38.547Z"
}
```
```json 400: Ad interests error theme={"system"}
{
"action": "get ad interests",
"status": "error",
"code": 368,
"message": "Error getting ad interests. Please try again or contact us if the issue persists."
}
```
# Regions
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-ad-regions
GET /ads/instagram/regions
Search regions such as states and provinces for Instagram ad targeting, or call without parameters to list every country that supports region targeting.
Search for regions (states, provinces, etc.) to be used for targeting when boosting an ad.
Regions use Facebook's `key` identifier. Use this `key` when boosting ads
with region targeting.
If a returned region has supportsRegion true, you can target this region. If supportsCity is
true, the region has city codes.
To get all countries that support region targeting, call this endpoint without query parameters.
Results may vary based on Facebook data availability.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string`
The search string to filter regions. Provide a partial or full region name.
**limit** `number` (default: 25)
Maximum number of regions to return. Only used with `search`. Without `search`, up to 1000 regions are returned.
```bash cURL theme={"system"}
# All countries that support region targeting
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/instagram/regions"
```
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/ads/instagram/regions?search=al"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/regions?search=al", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/regions?search=al', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/regions?search=al",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/regions?search=al");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/regions?search=al", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdRegions {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/regions?search=al"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/instagram/regions?search=al')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"regions": [
{
"key": "3843",
"name": "Alabama",
"type": "region",
"countryCode": "US",
"countryName": "United States",
"supportsRegion": true,
"supportsCity": true
},
{
"key": "3844",
"name": "Alaska",
"type": "region",
"countryCode": "US",
"countryName": "United States",
"supportsRegion": true,
"supportsCity": true
}
],
"count": 2
}
```
```json 414: Location metadata error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 414,
"message": "Error getting ad location metadata. Please try again."
}
```
# Boosted Ads
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-boosted-ads
GET /ads/instagram/ads
Retrieve boosted Instagram ads by account, ad, or post ID, with status, Meta review results, current spend, real-time metrics, and shareable preview links.
Retrieve ads that have been boosted for a specific Instagram post.
This boosted ads endpoint provides detailed information about the boosted ads, including status, current spend, performance metrics (analytics), and preview links.
The metrics are real-time totals for the ads. If you need historical data, use the [get ad
history endpoint](/docs/apis/ads/instagram/get-ad-history).
`review` is Meta's review of the ad: `status` is `pending` while Meta reviews it, `rejected`
when Meta disapproved it or left policy feedback on it, and `approved` when the ad is
active or pre-approved. `reasons` holds Meta's explanations, or the policy's name when Meta gives no text, and is empty when there is no feedback. `review` is `null` when neither
the ad's status nor any feedback shows the review, for example an ad paused before review.
The `previewLink` URL allows you share the preview ad with your colleagues, so that they will
see the ad for 24-hours across the various Instagram formats. Your colleagues' views of the ad
will not be counted towards your ad spend.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
Retrieve all ads under the account ID.
The ad account id, with or without the `act_` prefix (`1234567890` or `act_1234567890`).
Required if `adId`, `socialPostId`, or `postId` is not set.
**adId** `string` *required*
Retrieve an ad with the ad ID.
Required if `accountId`, `socialPostId`, or `postId` is not set.
**socialPostId** `string` *required*
Retrieve ads with the [Instagram social post ID](/docs/apis/overview#social-post-id).
Required if `accountId`, `adId`, or `postId` is not set.
**limit** `number` (default: 25)
Retrieve a maximum of `limit` ads. Maximum is 300; higher values are capped at 300.
**postId** `string` *required*
Retrieve ads with the [Ayrshare post ID](/docs/apis/overview#ayrshare-post-id).
Required if `accountId`, `adId`, or `socialPostId` is not set.
**status** `string` (default: all)
Filter ads by status of the ad. Omit this parameter to return ads of every
status. Valid values are:
`active`
`paused`
`deleted`
`archived`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/ads?adId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/ads?adId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/ads?adId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistoryGETRequest_csharp
{
class History
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/ads/instagram/ads?adId=1234567890";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success with Ad ID theme={"system"}
{
"status": "success",
"ads": [
{
"postId": "DE6gpw8kxlonHy6eb721",
"ad": {
"accountId": "274948331",
"adId": "6683876017501",
"budgetRemaining": 0,
"created": "2025-03-30T10:11:34-0700",
"creativeId": "1033490148629541",
"dailyBudget": 0,
"deliveryStatus": "ACTIVE",
"endDate": "2025-04-03T22:30:00.000Z",
"socialPostId": "106638148652329",
"goal": {
"title": "Get More Engagement",
"description": "This goal seeks to increase engagement while ensuring that the ad reaches the maximum number of unique users. It balances visibility with engagement, showing the ad to as many different people as possible who may interact with it.",
"type": "engagement"
},
"isComplete": false,
"lifetimeBudget": 0,
"metrics": {
"spend": 9.21,
"impressions": 6495,
"reach": 6272,
"clicks": 3,
"ctr": 0.046189,
"cpm": 1.418014,
"cpp": 1.468431,
"frequency": 1.035555,
"uniqueClicks": 3,
"uniqueCtr": 0.047832,
"costPerUniqueClick": 3.07,
"inlineLinkClicks": 0,
"costPerInlineLinkClick": 0,
"outboundClicks": 0,
"costPerOutboundClick": 0,
"websiteCtr": [],
"accountCurrency": "USD",
"accountName": "John Doe",
"accountId": "274948331"
},
"name": "API Post - DE6gpw8kxlonHy6eb7L1 - 2025-03-30T17:11:26",
"previewLink": "https://fb.me/22Cn31wXlzhxOC1",
"review": {
"reasons": [],
"status": "approved"
},
"spend": 9.21,
"startDate": "2025-03-30T17:11:32.000Z",
"status": "ACTIVE",
"targeting": {
"ageMax": 65,
"ageMin": 18,
"geoLocations": {
"countries": [
"US"
],
"locationTypes": [
"home",
"recent"
]
},
"interests": [
{
"id": "6003195554098",
"name": "Rhythm and blues music"
}
]
}
}
}
],
"count": 1
}
```
```json 400: Boosted ads error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 370,
"message": "Error getting ads. Please try again or contact us if the issue persists."
}
```
# DSA Recommendations
Source: https://www.ayrshare.com/docs/apis/ads/instagram/get-dsa-recommendations
GET /ads/instagram/dsaRecommendations
Get Digital Services Act (DSA) beneficiary and payor recommendations for the Meta ad account behind your Instagram ads, required for ads targeting the EU.
Get Digital Services Act (DSA) recommendations for the Meta ad account used for your Instagram ads.
As part of the requirements set forth by the European Union (EU) Digital Services Act (DSA), [Facebook requires ads targeting](https://www.facebook.com/business/help/605021638170961) any part of the EU to provide string values defining the beneficiary and payor of the ad being created.
This endpoint provides a list of recommended DSA beneficiaries and payors that can be used when boosting posts.
These fields may be used in [`dsaBeneficiary`](/docs/apis/ads/instagram/boost-post#param-dsa-beneficiary) and [`dsaPayor`](/docs/apis/ads/instagram/boost-post#param-dsa-payor).
Outputs a list of strings (maximum 25) that Facebook has identified to likely be the
beneficiary/payor, based on recent activity of the ad account.
These recommendations can be used as values for the `dsaBeneficiary` and `dsaPayor` parameters
in the [Boost Post](/docs/apis/ads/instagram/boost-post) endpoint.
Both `dsaBeneficiary` and `dsaPayor` must be set together if either is provided when boosting
posts.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**accountId** `string` *required*
The ID of the Meta ad account to get DSA recommendations for. The account ID can be retrieved
from the [ad accounts endpoint](/docs/apis/ads/instagram/get-ad-accounts).
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY"
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.GetAsync("https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("GET", "https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, _ := client.Do(req)
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetDsaRecommendations {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890"))
.header("Authorization", "Bearer API_KEY")
.GET()
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
uri = URI('https://api.ayrshare.com/api/ads/instagram/dsaRecommendations?accountId=1234567890')
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer API_KEY'
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http|
http.request(req)
}
puts res.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"dsaRecommendations": [
"Beneficiary 1",
"Beneficiary 2",
"Beneficiary 3"
],
"count": 3
}
```
```json 413: DSA Recommendations Error theme={"system"}
{
"action": "ads",
"status": "error",
"code": 413,
"message": "There was an error getting the DSA recommendations. Please try again."
}
```
# Update Ad
Source: https://www.ayrshare.com/docs/apis/ads/instagram/put-ad-update
PUT /ads/instagram/ads
Change an Instagram boosted ad's status with adId via PUT /ads/instagram/ads. Learn why attachTo campaigns keep their status and when untracked ads fail.
Update the status of a boosted ad to make it active, paused, deleted, or archived.
* For a boost added to your own campaign or ad set with `attachTo`, the new status applies only to what the boost created: the ad, and the ad set when the boost created it. The campaign or ad set you named in `attachTo` keeps its own status. The ad runs only while the campaign and ad set above it are active. See [Boost into an Existing Campaign or Ad Set](/docs/apis/ads/instagram/boost-post#attach-to).
* An ad that Ayrshare has no record of can be paused, deleted or archived, but not set to `active`. That request returns a `400`. Contact support with the ad id.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**adId** `string` *required*
The boosted ad ID to update.
**status** `string` *required*
The status of the ad. Values: `active`, `paused`, `deleted`, or `archived`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.ayrshare.com/api/ads/instagram/ads \
-d '{"adId": "1234567890", "status": "active"}'
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/ads/instagram/ads", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({ adId: "1234567890", status: "active" })
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.put('https://api.ayrshare.com/api/ads/instagram/ads', headers=headers, json={"adId": "1234567890", "status": "active"})
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/ads/instagram/ads",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json"
],
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode(["adId" => "1234567890", "status" => "active"])
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error: " . $err;
} else {
echo $response;
}
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using Newtonsoft.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
var response = await client.PutAsync("https://api.ayrshare.com/api/ads/instagram/ads", new StringContent(JsonConvert.SerializeObject(new { adId = "1234567890", status = "active" }), Encoding.UTF8, "application/json"));
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
}
```
```go Go theme={"system"}
package main
import (
"strings"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
client := &http.Client{}
req, _ := http.NewRequest("PUT", "https://api.ayrshare.com/api/ads/instagram/ads", strings.NewReader(`{"adId":"1234567890","status":"active"}`))
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
body, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetAdSpend {
public static void main(String[] args) {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.ayrshare.com/api/ads/instagram/ads"))
.header("Authorization", "Bearer API_KEY")
.header("Content-Type", "application/json")
.PUT(HttpRequest.BodyPublishers.ofString("{\"adId\":\"1234567890\",\"status\":\"active\"}"))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'json'
require 'uri'
uri = URI.parse('https://api.ayrshare.com/api/ads/instagram/ads')
request = Net::HTTP::Put.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer API_KEY'
request.body = JSON.generate({ adId: "1234567890", status: "active" })
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
puts response.body
```
```json 200: Success theme={"system"}
{
"status": "success",
"adId": "6691084804905",
"requestedStatus": "ACTIVE"
}
```
```json 400: Ad update error theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "Invalid status: actives. Status must be either 'active', 'paused', 'deleted', or 'archived'."
}
```
# Ads API: Boost Facebook and Instagram Posts and Manage Google Ads
Source: https://www.ayrshare.com/docs/apis/ads/overview
Boost your Meta posts and create Google Ads using Ayrshare's API.
## Introduction to the Ads API
The Ayrshare Ads API provides programmatic access to create, manage, and analyze social media ads.
The Ayrshare Ads API supports **Facebook / Instagram and Google**. For Google, create and manage [Search campaigns](/docs/apis/ads/google/search-campaigns) or [Performance Max campaigns](/docs/apis/ads/google/performance-max-campaigns).
For Facebook and Instagram, the Facebook Ads API, also known as the Facebook Marketing API, allows you to transform existing posts into paid advertisements.
This includes boosting posts (transforming a post into an ad), managing ads, tracking performance, and analyzing ad spend.
Ads created and updated via Ayrshare can always be found in the Facebook Ads Manager.
Ayrshare handles all of the settings at the ad, ad set, and campaign levels.
There is never a need to manually edit any of the settings directly in the Facebook Ads Manager.
> [!NOTE]
>
> For Instagram connection requirements and request examples, see [Instagram Ads](/docs/apis/ads/instagram/boost-post). The walkthrough below uses Facebook.
> [!NOTE]
>
> 1. The Ads capabilities require the paid Ads add-on which can be enabled on the
> Account page of the [web dashboard](https://app.ayrshare.com/account).
>
> 2. The ad account you boost from must have a [payment method](https://business.facebook.com/billing_hub/payment_settings/?placement=ads_manager) attached at Facebook (Meta) before you create an ad. One saved only on the business portfolio is not enough. For Meta's other account prerequisites, see [Boost Post](/docs/apis/ads/facebook/boost-post).
>
> 3. User Profiles linked with a Facebook Page prior to April 1, 2025 should be
> re-linked to enable ads.
For more information, see our [Facebook Ads API guide](https://www.ayrshare.com/blog/facebook-ads-api-boosting-with-the-marketing-api/).
## Supported Platforms
Each Ads endpoint takes the network as its path segment, for example `/ads/facebook/accounts` or `/ads/instagram/accounts`.
`code: 101` means the network is not an Ads network or does not offer that endpoint. `code: 424` means ads on that network are temporarily unavailable.
| Endpoint | Platforms |
| - | - |
| `GET /ads/{platform}/accounts` | `facebook`, `instagram`, `google` |
| `GET /ads/{platform}/interests` | `facebook`, `instagram` |
| `GET /ads/{platform}/campaigns` | `facebook`, `instagram` |
| `POST /ads/{platform}/campaigns` | `google` |
| `GET /ads/{platform}/campaigns/{id}` | `google` |
| `PUT /ads/{platform}/campaigns/{id}` | `google` |
| `POST /ads/{platform}/boost` | `facebook`, `instagram` |
| `GET /ads/{platform}/ads` | `facebook`, `instagram` |
| `PUT /ads/{platform}/ads` | `facebook`, `instagram` |
| `GET /ads/{platform}/history` | `facebook`, `instagram` |
| `GET /ads/{platform}/insights` | `facebook`, `instagram`, `google` |
| `GET /ads/{platform}/dsaRecommendations` | `facebook`, `instagram` |
| `GET /ads/{platform}/regions` | `facebook`, `instagram` |
| `GET /ads/{platform}/cities` | `facebook`, `instagram` |
## Create an Ad Flow

Boosting a post on Facebook is a quick and simple way to turn one of your
existing posts into an ad. Here is a high-level flow of how to implement it in
your platform:
**1. Choose the Facebook Ad Account**
Your user will choose the Facebook ad account which has all the billing details already configured.
> [card] [Get Facebook Ad Accounts](/docs/apis/ads/facebook/get-ad-accounts)
**2. Select the Post**
Select a post from your Facebook page. It can be a photo, video, status update, etc.
> **Tip:** Posts with good organic engagement (likes, shares, comments) tend to perform better when boosted.
**3. Click “Boost Post”**
Your user will click a button such as "Boost Post" in your platform to start
setting up the ad.
> [card] [Boost a post by submitting it to Facebook's ad platform](/docs/apis/ads/facebook/boost-post)
**4. Set the Goal, Audience, Budget, & Duration**
The user can set a number of parameters.
Choose the goal strategy for this
boosted post by selecting a goal.
Customize the audience based on age, gender,
location, interests, etc.
Choose how much to spend total.
Set the
time period to run the ad.
**5. Review & Boost**
Double-check all the settings. If everything looks good, the user will click a button such as **"Boost Post Now"** button.
**6. Ad Review & Launch**
Facebook will review the ad — this usually takes anywhere from a few minutes
to a few hours. Once approved, the boosted post goes live and runs according
to the setup.
**7. Track Spend & Performance**
Monitor the following:
* Reach
* Engagement
* Clicks
* Spend
* Budget Remaining
> [card] [Get Facebook Ad Spend & Analytics](/docs/apis/ads/facebook/get-ad-history)
## Ads API Best Practices
1. **Budget Management**: Start with small test budgets (e.g., \$5-10/day) to optimize performance before scaling.
2. **Ad Duration**: Facebook requires a minimum ad runtime of approximately 30 hours between start and end times.
3. **Interest Targeting**: Choose 2-5 relevant interests for optimal targeting precision.
4. **Creative Guidelines**: Use high-quality images and concise messaging to improve engagement rates.
5. **Performance Monitoring**: Regularly check your ad performance and adjust targeting or creative elements as needed.
For detailed information about each endpoint, including request parameters, response formats, and examples, please refer to the individual API endpoint documentation pages.
# Count of Instagram Followers Online
Source: https://www.ayrshare.com/docs/apis/analytics/instagram-follower-count
GET /analytics/getInstagramOnlineFollowers
Retrieve the historical count of your Instagram followers online per hour for a given day, so you can optimize posting times for maximum engagement.
Retrieve the total historical count of your Instagram followers online per hour, which allows you to optimize posting for maximum engagement.
Not available on IG Users with fewer than 100 followers.
Analytics data only available for the last 30 days.
The time period for a requested day is the previous day at T07:00:00.000Z until the current day
at T06:59:59.999Z in UTC.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**date** `string` *required*
A day with format: `YYYY-MM-DD`, for example `2026-07-08`. A date with a time returns error `101`.
**formatUTC** `boolean` (default: false)
Format the output with UTC start and end dates. To get the default output, leave this parameter out.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/analytics/getInstagramOnlineFollowers?date=2023-12-03
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const date = "2023-12-03";
fetch(`https://api.ayrshare.com/api/analytics/getInstagramOnlineFollowers?date=${date}`, {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/analytics/getInstagramOnlineFollowers?date=2023-12-03', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
Bluesky
Facebook
Instagram
LinkedIn
Pinterest
Reddit
Snapchat
Threads
TikTok
YouTube
X/Twitter
The analytics may be retrieved for a specific post by providing either the Ayrshare post ID or the original post's Social Post ID.
## Social Analytics
You can also get the account level social analytics, such as engagement, follower count and demographics, and account meta data.
Use this endpoint if you want to get the analytics for an entire social account, rather than a specific post.
Social account level analytics are available for the following social networks:
Facebook
Google Business Profile
Instagram
LinkedIn
Pinterest
Reddit
Snapchat
Threads
TikTok
YouTube
X/Twitter
You may also choose to get aggregated summary or daily social analytics.
## Link Analytics
You can also get analytics on a shortened link. This is great if you want to know how many clicks a link has received, either one sent via Ayrshare or manually published at the social network.
Link analytics can be found in the /links endpoint.
> [card] [Link Analytics](/docs/apis/links/link-analytics)
>
> Get analytics on a shortened link.
# Analytics on a Post
Source: https://www.ayrshare.com/docs/apis/analytics/post
POST /analytics/post
Retrieve real-time likes, impressions, views, and reactions for a post sent via Ayrshare, with multiplatform partial-success handling.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get real-time analytics such as likes, impressions, views, and reactions for a post that was sent via Ayrshare using the [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id).
For analytics on posts originating outside of Ayrshare, see [Analytics by Social Post ID](/docs/apis/analytics/social-by-id).
> [!WARNING]
>
> **Facebook: some reach and video metrics were retired by Meta (June 15, 2026).** Meta removed the unique-impression and 3-second video-view Insights metrics across all Graph API versions, so the Facebook `analytics` object no longer returns `impressionsUnique`, `impressionsFanUnique`, `impressionsOrganicUnique`, `impressionsPaidUnique`, or `videoViewsUnique`. For reach, use `totalMediaViewUnique` (the number of unique people this post was played or displayed to), Meta's Total Unique Media Views replacement for the retired unique-impression fields. `mediaView` remains available for total, non-unique views. Other commonly used fields such as `reactionsByType` and `videoViews` are unaffected. Reference: [Upcoming API Changes — June 15, 2026](/docs/whatsnew/upcoming-api-changes#june-15-2026).
#### Additional Information
The following platforms are currently supported: Bluesky, Facebook Pages, Instagram, X/Twitter, LinkedIn, Pinterest, Reddit, Snapchat, Threads, TikTok, and YouTube.
Facebook and Instagram comment counts shown in the API may differ from counts displayed in the social apps. This occurs for two reasons: - Some users have privacy settings that hide their comments from non-friends or users without mutual connections. These private comments are still included in the total comment count. - There may be inconsistencies in the data reported by Meta.
Instagram igReelsAggregatedAllPlaysCount and playsCount shown in the API may differ from the counts displayed in the social apps. This can occur for a couple of reasons:
The post was promoted. Only organic plays are included in the API response.
There may be inconsistencies in the data reported by Meta.
X/Twitter Threads return as an Array of objects, with each object corresponding to a Tweet in the Thread.
YouTube can take 24-48 hours to process analytics for videos with few video views or channel followers.
TikTok Analytics:
TikTok can take 24-48 hours to update their analytics data, such as video views, demographics, likes, shares, and comments.
For TikTok analytics access, account owners must: 1. Publish at least one video. 2. Tap the "[Turn On](https://www.tiktok.com/feedback?id=7133058093574806018\&lang=en\&type=)" button on the Analytics page of their mobile TikTok app. 3. Have 100 followers to receive additional insights about viewers and content engagement.
TikTok does not return post analytics if the media contains copyrighted material or has been flagged for a copyright violation. Examples of copyrighted material can include audio on reels (TikTok will mute these videos). You can check in the TikTok app under Activity -> System Notifications.
Some TikTok analytics fields may be unavailable if the video has been inactive for more than 7 days. To retrieve this data, generate new activity on the video (view, like, comment, or share) and retry after 24-48 hours. If a video is not returned in the response, the reason is likely to be that the video has been filtered out due to violations, such as music copyright violation. These fields include:
`reach`
`fullVideoWatchedRate`
`totalTimeWatched`
`averageTimeWatched`
`impressionSources`
`audienceCountries`
LinkedIn personal (member) profiles now return an expanded post analytics matrix. The available member metrics are:
`impressionCount` — Impressions on the post
`uniqueImpressionsCount` — Unique members reached (LinkedIn `MEMBERS_REACHED`)
`likeCount` — Reaction count
`commentCount` — Comments count
`shareCount` — Reshares of the post
`engagement` — Organic clicks, likes, comments, and shares over impressions
`reactions` — Per-type reaction breakdown (when reactions are present)
For video posts: `videoViews`, `videoViewers`, and `videoWatchTimeMs`
Video metrics are only returned for video posts and are unavailable more than one year after the post was created.
Instagram Story insights are only available for 24 hours, regardless of whether stories are archived or highlighted. Note:
Story media metrics with values less than `5` return as `0`.
For Stories created by users in Europe and Japan, the `replies` metric returns a value of `0`.
Facebook Story analytics are not available.
Pinterest analytics data (impressions, user followers, and clicks) becomes available after 24-72 hours.
For YouTube Shorts, views will return the number of times a Short starts to play or replay, with no minimum watch time requirement. Starting August 24, 2026, this counting applies to all YouTube video formats, including long-form videos and live streams.
## Multiplatform Reads & Partial Success
A single [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id) can span several platforms. Ayrshare fans out one request ("leg") per platform and returns a **partial success** when some legs succeed and others fail — the healthy platforms' analytics are always returned, and each failed leg is enumerated in a top-level `errors[]` array.
**Some platforms succeed, some fail:** the response is HTTP `200` with `status: "partial"`. The healthy per-platform `analytics` blocks are returned as usual, and a top-level `errors[]` array lists every failed leg with its `platform`, `status`, `code`, `message`, and `id`.
**Every platform fails:** the response has `status: "error"` and the full `errors[]` array. The HTTP status is mapped from the representative top-level error code. Code `485` maps to HTTP `404`; other representative codes use their own mappings.
**All platforms succeed:** the response is unchanged — HTTP `200`, `status: "success"`, and no `errors[]` key.
> [!WARNING]
>
> **Behavior change — inspect `errors[]`, don't branch on HTTP status.** Because a multiplatform read with a failing leg now returns HTTP `200` instead of collapsing the whole response to an error, integrators should always check for the presence of a top-level `errors[]` array to detect per-platform failures rather than relying on the HTTP status code alone.
### Expired or Unavailable Instagram Story Analytics
An Instagram Story analytics leg that is expired or unavailable — so its insights cannot be retrieved — surfaces in `errors[]` with code [`485`](/docs/errors/errors-ayrshare#expired-or-unavailable-story-errors). A representative message is *"Instagram Story expired or unavailable — comments/insights cannot be retrieved."* Facebook Story analytics remain unavailable and are not covered by this behavior. If another platform succeeds, its analytics are still returned and the overall response is **HTTP `200`**. For an all-fail response, representative code `485` maps to HTTP `404`; other representative codes use their own mappings. Match on the `code` (`485`), not the exact message text. (See also the note above: Instagram Story insights are only available for 24 hours.)
#### Example: Partial Success Response
```json 200: Partial Success theme={"system"}
{
"facebook": {
"id": "1397547544885713_2159201585286968",
"postUrl": "https://www.facebook.com/1397547544885713_2159201585286968",
"analytics": {
"commentsCount": 1,
"likeCount": 23,
"sharesCount": 12,
"mediaView": 450
},
"lastUpdated": "2026-07-14T18:44:29.778Z",
"nextUpdate": "2026-07-14T19:19:29.778Z"
},
"status": "partial",
"id": "IHvCLacgPc6hMU9IQ6oK",
"errors": [
{
"platform": "instagram",
"status": "error",
"code": 485,
"message": "Instagram Story expired or unavailable — comments/insights cannot be retrieved.",
"id": "17895695668004550"
}
]
}
```
## Daily Post Analytics
Send `daily: true` with `startDate` and `endDate` to get each post's analytics day by day for that
period. Each day carries the last analytics Ayrshare collected for the post that day (UTC), with
`fetchedAt` showing when. Each day's values are the post's totals as of `fetchedAt`, not the
activity during that day. Days with nothing collected are left out. Without `daily`, the request
works as described above.
A network the profile is not linked to returns error `156`, and one that needs Meta identity
verification returns error `326`, as they do without `daily`. After a network is relinked, days
collected before the new link are left out.
```json theme={"system"}
{
"status": "success",
"id": "Post ID",
"instagram": {
"id": "17895695668004550",
"postUrl": "https://www.instagram.com/p/CBbVz8gnTqS/",
"daily": [
{
"date": "2026-10-01",
"analytics": { "likeCount": 12, "commentsCount": 2 },
"fetchedAt": "2026-10-01T18:04:11.000Z"
},
{
"date": "2026-10-02",
"analytics": { "likeCount": 15, "commentsCount": 3 },
"fetchedAt": "2026-10-02T17:58:40.000Z"
}
]
}
}
```
With `searchPlatformId` and `postIds`, the platform's value is a list with one entry per post, in
the order requested. An X or Threads thread is also a list, with one entry per post in the thread.
`postUrl` is optional: it is included when a returned day has a post URL stored. A post with nothing
collected in the period returns `daily: []` without `postUrl`.
### Daily analytics for many posts
To get daily analytics for all of a profile's posts, for example in a daily report:
1. List the posts with [Get History](/docs/apis/history/get-history). History filters by publish date, not
by when analytics were fetched, so pick a window that holds every post you report on, such as
`lastDays=90`, not the report's dates. If that window holds more posts than `limit` returns,
split it into shorter `startDate`/`endDate` windows. Each post's Social Post IDs are in
`postIds[].id`, with the network in `postIds[].platform`.
2. For each network, call this endpoint with `daily: true`, the report's `startDate` and `endDate`,
`searchPlatformId: true`, that one network in `platforms`, and up to 100 of its Social Post IDs in
`postIds`.
```json theme={"system"}
{
"daily": true,
"startDate": "2026-10-30T00:00:00Z",
"endDate": "2026-10-31T00:00:00Z",
"searchPlatformId": true,
"platforms": ["instagram"],
"postIds": ["17895695668004550", "17841405822304914"]
}
```
A short period, such as the last day, keeps each response small.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
[Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id) returned from the [/post
endpoint](/docs/apis/post/post). This is the top-level Ayrshare `id` returned and not the social post
ids in `postIds`.
**platforms** `array`
String array of platforms to retrieve analytics. Accepts an array of strings with values:
```json theme={"system"}
{
"platforms": [
"bluesky",
"facebook",
"instagram",
"linkedin",
"pinterest",
"reddit",
"snapchat",
"threads",
"tiktok",
"twitter",
"youtube"
]
}
```
If platforms is not included, analytics will be returned for all social networks to which the post was sent.
**daily** `boolean` (default: false)
When set to `true`, returns the post's analytics day by day, from the analytics Ayrshare has
already collected for it, instead of fetching them from the social network. Requires
`startDate` and `endDate`. Also works with `searchPlatformId` and `postIds` for [Social Post
IDs](/docs/apis/analytics/social-by-id). See [Daily Post Analytics](#daily-post-analytics).
**startDate** `string`
Required when `daily` is `true`. Start of the period, in ISO 8601 format: a date such as
`2026-10-01` (read as midnight UTC), or a date-time with `Z` or an offset, such as
`2026-10-01T00:00:00Z`. A date-time without `Z` or an offset returns error `101`. Includes
analytics collected from this time.
**endDate** `string`
Required when `daily` is `true`. End of the period, in the same format as `startDate`, for
example `2026-10-08T00:00:00Z`. Includes analytics collected before this time. The period can be
at most 31 days.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"id": "Post ID",
"platforms": ["bluesky", "facebook", "instagram", "linkedin",
"pinterest", "snapchat", "threads", "tiktok", "twitter", "youtube"]}' \
-X POST https://api.ayrshare.com/api/analytics/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/analytics/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
id: "Post ID", // required
platforms: [
"bluesky",
"facebook",
"instagram",
"linkedin",
"pinterest",
"snapchat",
"threads",
"tiktok",
"twitter",
"youtube"
] // optional
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': 'Post ID',
'platforms': ['bluesky', 'facebook', 'instagram', 'linkedin',
'pinterest', 'snapchat', 'threads', 'tiktok', 'twitter', 'youtube']}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/analytics/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
'Post ID', // Replace with your actual Post ID
'platforms' => ['bluesky', 'facebook', 'instagram', 'linkedin',
'pinterest', 'snapchat', 'threads', 'tiktok', 'twitter', 'youtube'] // optional
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"id": "Post ID",
"platforms": []string{"bluesky", "facebook", "instagram", "linkedin",
"pinterest", "snapchat", "threads", "tiktok", "twitter", "youtube"},
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/analytics/post",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostAnalyticsPOSTRequest_csharp
{
class PostAnalytics
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/analytics/analytics/post";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"id\": \"Post ID\"," +
"\"platforms\": [\"bluesky\", \"facebook\", \"instagram\", \"linkedin\", \"pinterest\", \"snapchat\", \"tiktok\", \"twitter\", \"youtube\"]}";
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
> [!NOTE]
>
> When cumulative metrics (e.g., likes, comments, views) are temporarily unavailable from the social network, the API automatically backfills them from stored data. Two optional fields may appear in the per-platform `analytics` object:
>
> * **`backfilledFrom`** (string, ISO 8601) — Present when one or more cumulative metrics were substituted from stored data. The timestamp indicates when the stored data was last updated.
> * **`recoveredFrom`** (string, ISO 8601) — Present when the entire analytics response was recovered from stored data due to a complete API failure. The timestamp indicates when the stored data was last updated. For Snapchat, where `analytics` is a list, `recoveredFrom` appears beside `analytics` instead.
>
> Stored data older than 4 days is considered stale and will not be used for backfill or recovery.
> [!NOTE]
>
> **LinkedIn personal (member) analytics — re-link required.** Personal LinkedIn profiles linked before member post analytics shipped do not have the required analytics scopes. Analytics requests for those profiles return [error code `475`](/docs/errors/errors-ayrshare#linkedin-analytics-errors) ("re-link your LinkedIn profile to enable analytics"). The account owner must re-link their LinkedIn profile on the Social Accounts page to grant the new scopes. Allow a few minutes after re-linking for code `475` to clear (Ayrshare and LinkedIn both briefly cache the permission state, typically \~5-10 minutes). Posting is unaffected.
>
> LinkedIn member post counts are best-effort: the aggregate `shareCount` (RESHARE), `likeCount` (REACTION), and `commentCount` (COMMENT) totals may differ slightly from the numbers shown in the LinkedIn UI.
```json 200: Response {2, 24, 154, 236, 270, 321, 391, 407, 439, 453, 588, 741} theme={"system"}
{
"bluesky": {
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lez7fwx457", // Bluesky Social Post ID
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx457",
"analytics": {
"cid": "bafyreigh7nz7x6pl4atgsextqtcbh33mg66yqt3caeihutn7ojkmpdtnzm", // Bluesky Content ID
"created": "2025-01-05T18:04:28.316Z",
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lfb57nxgxs2e", // Bluesky Social Post ID
"indexedAt": "2025-01-05T18:04:29.150Z",
"labels": [],
"likeCount": 3,
"post": "The day is what you make it!",
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx45723",
"quoteCount": 2,
"replyCount": 1,
"repostCount": 1,
"viewer": { // Information about the viewing user's relationship
"threadMuted": false, // Whether the viewing user has muted the thread containing this content
"embeddingDisabled": false // Whether embedding/sharing of this content is disabled for the viewing user
}
}
},
/* If the FB page has fewer than 100 likes then not all analytics data available. */
"facebook": {
"id": "1397547544885713_2159201585286968", // Facebook Social Post ID
"postUrl": "https://www.facebook.com/1397547544885713_2159201585286968",
"analytics": {
"blueReelsPlayCount": 136, // Reels: The number of times your reel starts to play after an impression is already counted. This is defined as reels sessions with 1ms or more of playback and excludes replays.
"commentsCount": 1, // Count of comment for the post
// impressionsFanUnique / impressionsOrganicUnique / impressionsPaidUnique / impressionsUnique were retired by Meta (June 15, 2026); see the note at the top of this page.
"likeCount": 23, // Count of likes for the post
"likedBy": [ // Users who liked the post
{
"id": "7101149746568432",
"name": "John Smith"
}
],
"mediaUrls": [
{
"media": {
"image": {
"height": 405,
"src": "https://scontent-lga3-1.xx.fbcdn.net/v/t",
"width": 720
},
"source": "https://video-lga3-2.xx.fbcdn.net/o1/v/t2/f2/m69/AQN"
},
"mediaType": "video",
"type": "story"
}
],
"mediaView": 12, // The number of times this post was played or displayed. Content includes videos, posts, stories and ads.
"mediaViewIsFromAds": 0, // The number of times this post was played or displayed from ads.
"mediaViewIsFromFollowers": 0, // The number of times this post was played or displayed from followers.
"postVideoAvgTimeWatched": 4470, // Reels: The average time (in ms) your reel was played, including any time spent replaying the reel during a single instance of it playing. Because this metric includes replays, this number could be greater than the total length of the reel
"postVideoSocialActions": { // Reels: The number of comments, shares, and reactions on your reel
"share": 1
},
"postVideoViewTime": 608011, // Reels: Total time (in ms) your reel was played, including any time spent replaying your reel.
"reactions": {
"like": 1, // Like reactions - The "like" reaction counts include both "like" and "care" reactions.
"love": 1, // Love reactions
"anger": 1, // Anger reactions
"haha": 1, // Haha reactions
"wow": 1, // Wow reactions
"sorry": 1, // Sorry reactions
"total": 6 // Total number of reactions
},
"reactionsByType": 6, // The total types of reactions
"sharesCount": 34, // Total number of times post shared
"totalMediaViewUnique": 9, // Reach: the number of unique people this post was played or displayed to.
/** The following fields are available for videos */
"totalVideo10SViews": 621, // Total number of times your video was viewed for 10 seconds or viewed to the end, whichever came first.
"totalVideo10SViewsAutoPlayed": 564, // Number of times people clicked to play your video and viewed it for 10 seconds or viewed it to the end, whichever came first.
"totalVideo10SViewsClickedToPlay": 46, // Number of times your video started automatically playing and people viewed it for 10 seconds, or viewed it to the end, whichever came first.
"totalVideo10SViewsOrganic": 621, // Number of times your video was viewed for 10 seconds or viewed to the end, whichever came first, without a paid promotion.
"totalVideo10SViewsPaid": 0, // Number of times your video was viewed for 10 seconds or viewed to the end, whichever came first, after a paid promotion.
"totalVideo10SViewsSoundOn": 380, // Number of times your video sound was turned on and was viewed for 10 seconds or viewed to the end, whichever came first.
"totalVideo10SViewsUnique": 621, // Number of unique people who viewed your video for 10 seconds or viewed to the end, whichever came first.
"totalVideo15SViews": 525, // Total number of times your video was viewed for at least 15 seconds.
"totalVideo60SExcludesShorterViews": 0, // Total number of times your video was viewed for 60 seconds only if the video is 60 seconds or longer.
"totalVideoAvgTimeWatched": 8494, // Lifetime: Average time video viewed
"totalVideoCompleteViews": 430, // Total number of times your video was watched at 95% of its length, including watches that skipped to this point.
"totalVideoCompleteViewsAutoPlayed": 374, // Number of times your video started automatically playing and people watched it at 95% of its length, including watches that skipped to this point.
"totalVideoCompleteViewsClickedToPlay": 56, // Number of times people clicked to play your video and watched it at 95% of its length, including watches that skipped to this point.
"totalVideoCompleteViewsOrganic": 430, // Number of times your video was watches at 95% of its length without any paid promotion, including watches that skipped to this point.
"totalVideoCompleteViewsOrganicUnique": 430, // The number of unique people who watched your video at 95% of its length without any paid promotion, including people that skipped to this point
"totalVideoCompleteViewsPaid": 0, // Number of times your video was watched at 95% of its length after paid promotion, including watches that skipped to this point.
"totalVideoCompleteViewsPaidUnique": 0, // The number of unique people who watched your video at 95% of its length after paid promotion, including people that skipped to this point.
"totalVideoCompleteViewsUnique": 430, // Total number of unique people who watched your video at 95% of its length or more, including people that skipped to this point.
// The totalVideoImpressions* family (Video Impressions) was retired by Meta (June 15, 2026); see the note at the top of this page.
"totalVideoReactionsByTypeTotal": { // The total number of reactions to your post by type.
"like": 13,
"haha": 2
},
"totalVideoStoriesByActionType": { // Number of stories created about your Page Video, by action type.
"like": 15
},
"totalVideoViewTimeByAgeBucketAndGender": { // Video view time (in ms) by age bucket and gender.
"f1317": 23521,
"m1317": 184052,
"u1824": 2774,
"f1824": 118193,
"m1824": 1340281,
"u2534": 11975,
"f2534": 407006,
"m2534": 3960750,
"u3544": 27054,
"f3544": 332576,
"m3544": 5477331,
"u4554": 41804,
"f4554": 375449,
"m4554": 3794202,
"u5564": 35717,
"f5564": 159041,
"m5564": 3582574,
"u65+": 8245,
"f65+": 423739,
"m65+": 2866201
},
"totalVideoViewTimeByDistributionType": { // Video view time (in ms) by distribution type. On asset level, there are three possible distribution types: (page_owned/shared/crossposted)
"pageOwned": 23172485
},
"totalVideoViewTimeByRegionId": { // Video view time (in ms) by region ID.
"californiaUnitedStates": 791788,
"texasUnitedStates": 480891,
"karnatakaIndia": 390297,
"uttarPradeshIndia": 368698,
"tamilNaduIndia": 359103,
"queenslandAustralia": 295491,
"newSouthWalesAustralia": 284222,
"newYorkUnitedStates": 250035,
"maharashtraIndia": 248465,
"washingtonUnitedStates": 237430
},
"totalVideoViewTotalTime": 23172485, // Total time (in ms) video has been viewed.
"totalVideoViewTotalTimeOrganic": 23172485, // Total time (in ms) video has been viewed in Feed or ticker or on your Page's Timeline.
"totalVideoViewTotalTimePaid": 0, // Total time (in ms) video has been viewed after paid promotion.
"totalVideoViews": 1283,
"totalVideoViewsAutoplayed": 1237, // Number of times your video started automatically playing and people viewed it for 3 seconds or viewed it to the end, whichever came first.
"totalVideoViewsByDistributionType": { // Video views by distribution type. On asset level, there are three possible distribution types: (page_owned/shared/crossposted)
"pageOwned": 1283
},
"totalVideoViewsClickedToPlay": 46, // Number of times people clicked to play your video and viewed it for 3 seconds or viewed it to the end, whichever came first.
"totalVideoViewsOrganic": 1283, // Number of times your video was viewed for 3 seconds or viewed to the end, whichever came first, without any paid promotion.
"totalVideoViewsPaid": 0, // Number of times your video was viewed for 3 seconds or viewed to the end, whichever came first, after paid promotion.
"totalVideoViewsSoundOn": 743, // Number of times your video sound was turned on and was viewed for 3 seconds or viewed to the end, whichever came first.
"videoViews": 3 // Times your videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds. During a single instance of a video playing, we'll exclude any time spent replaying the video. This includes live views.
// videoViewsUnique was retired by Meta (June 15, 2026); see the note at the top of this page.
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
"instagram": {
"id": "17856916804532520", // Instagram Social Post ID
"postUrl": "https://www.instagram.com/p/ChALNdaOT3G/",
"analytics": {
/* FEED Data */
"caption": "It always seems impossible until it's done. - Nelson Mandela",
"commentsCount": 1, // Comments total
"created": "2022-09-07T22:39:06Z", // Time post was created
"engagementCount": 3, // Likes, comments, and saves
"followsCount": 2, // Number of followers gained
"likeCount": 1, // Likes total - organic likes, promoted post likes not included
"mediaProductType": "FEED", // Type of IG Share: AD, FEED, STORY or REELS
"mediaType": "IMAGE", // Media type: CAROUSEL_ALBUM, IMAGE, or VIDEO
"mediaUrls": [
{
"mediaUrl": "https://scontent.cdninstagram.com/v/t51.82787-15/516601117_17872451562389336_3472858781474899926_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=107&ccb=1-7&_nc_sid=18de74&_nc_ohc=-LOY7upopXoQ7kNvwEkqUMq&_nc_oc=AdmN47ZBlmciajpv7udoP1ku6J-FBgBGIrRskGgPmIOjT5D3WI9F21_Fg1Gi60-aI70&_nc_zt=23&_nc_ht=scontent.cdninstagram.com&edm=AEQ6tj4EAAAA&_nc_gid=PZY4V4Pvo8ePuq2CfSBp1A&oh=00_AfQplZLqrCEYwXkCutbmMAGIgpcBUkKqGcfRdDLZU4QHRA&oe=6872DFF9"
}
],
"profileActivityCount": 1,// The number of actions people take when they visit your profile after engaging with your post
"profileVisitsCount": 11, // The number of times your profile was visited.
"reachCount": 71, // Unique accounts that have seen the post
"savedCount": 1, // Unique accounts have saved the post
"sharesCount": 12, // Number of times the post was shared
"username": "jackthegiant",
"viewsCount": 71, // Total number of times the post has been seen.
/* REELS Data */
"caption": "Gorgeous gifts for new Mum & Bub \n\n@mikimoocreations @romperandco @obdesigns @salusbody @enaproducts",
"clipsReplaysCount": 21, // The number of times your reel starts to play again after an initial play of your reel. This is defined as replays of 1ms or more in the same reel session.
"commentsCount": 1, // Comments total
"created": "2022-09-15T06:58:55Z",
"engagementCount": 183, // Number of likes, saves, comments, and shares on the reel, minus the number of unlikes, unsaves, and deleted comments.
"igReelsAggregatedAllPlaysCount": 42, // The number of times your reel starts to play or replay after an impression is already counted. This is defined as plays of 1ms or more. Replays are counted after the initial play in the same reel session. Organic plays only; promoted post plays are not included.
"igReelsAvgWatchTimeCount": 23, // The average amount of time spent playing the reel. This is calculated by the watch time divided by the number of plays.
"igReelsVideoViewTotalTimeCount": 21, // The total amount of time the reel was played, including any time spent replaying the reel.
"likeCount": 21, // Likes total on the reel
"mediaProductType": "REELS", // Type of IG Share: AD, FEED, STORY or REELS
"mediaType": "VIDEO", // Always "VIDEO"
"mediaUrls": [
{
"mediaUrl": "https://instagram.flis9-1.fna.fbcdn.net/o1/v...amug&oe=686ED4D3"
}
],
"playsCount": 680, // Number of times the reels starts to play after an impression is already counted. This is defined as video sessions with 1 ms or more of playback and excludes replays. Organic plays only; promoted post plays are not included.
"reachCount": 689, // Number of unique accounts that have seen the reel at least once. Reach is different from impressions, which can include multiple views of a reel by the same account.
"savedCount": 0, // Number of saves of the reel.
"sharesCount": 2, // Number of shares of the reel.
"username": "mordiallocflorist",
"viewsCount": 40, // Total number of times the reel has been seen.
/* STORY Data - Fewer than 5 insights will not show metric data. */
"commentsCount": 2,
"created": "2024-09-16T08:37:57Z",
"engagementCount": 10,
"followsCount": 0,
"likeCount": 3,
"mediaProductType": "STORY",
"mediaType": "IMAGE",
"mediaUrls": [
{
"mediaUrl": "https://scontent.cdninstagram.com/v/t51.82787-15/517935...we32"
}
],
"notEnoughViews": true, // True if fewer than 5 unique insights. If fewer than 5 unique insights, metric data not available.
"navigationCount": 15, // Total nav interactions of taps and swipes
"profileActivityCount": 3,
"profileVisitsCount": 1,
"reachCount": 10,
"repliesCount": 3,
"sharesCount": 2,
"swipeForwardCount": 1, // Swipes "Next Story" on the native interface.
"tapBackCount": 2, // Taps "Back" on the native interface.
"tapForwardCount": 9, // Taps "Forward" on the native interface.
"tapExitCount": 3, // Taps "Exit" on the native interface.
"username": "ayrshare",
"viewsCount": 10, // Total number of times the story has been seen.
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
/* Corporate LinkedIn Account Example. Past 12 months, using a rolling 12-month window. */
"linkedin": {
"id": "6783586276949000192", // LinkedIn Social Post ID
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:6783586276949000192",
"type": "corporate", // corporate or personal. Please see notes on personal analytics
"analytics": {
"clickCount": 0, // Clicks on a post - company only
"commentCount": 0, // Comments count
"comments": [ // Comment IDs - can be used to look up comments
"urn:li:comment:(urn:li:activity:7226002855806021632,7226004971362627585)"
],
"commentsState": "OPEN", // Comments allowed or not
"engagement": 0, // Organic clicks, likes, comments, and shares over impressions - company only
"impressionCount": 0, // Impressions on a post - company only
"likeBy": [
"urn:li:person:Z_yXaxh_AB" // Look up the user with the brands endpoint
],
"likeCount": 1, // Likes on a count
"reactions": { // Get reactions on a LinkedIn share
"like": 1, // "Like in the UI
"praise": 2, // "Celebrate" in the UI
"maybe": 3, // "Curious" in the UI
"empathy": 3, // "Love" in the UI
"interest": 2, // "Insightful" in the UI
"appreciation": 5 // "Support" in the UI
},
"shareCount": 0, // Shares on a post - company only
"totalFirstLevelComments": 1, // COunt of the firt level comments
"uniqueImpressionsCount": 0, // Unique impressions for a post - company only
"videoViews": 1 // Number of times video viewed - company only
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
/* Personal LinkedIn Account Example. Past 12 months, using a rolling 12-month window. */
"linkedin": {
"id": "urn:li:share:7221186087895920444",
"type": "personal",
"analytics": {
"commentCount": 12, // Comments count
"comments": [
"urn:li:comment:(urn:li:activity:7221186088390799360,7224469537784479744)"
],
"commentsState": "OPEN",
"engagement": 0.07, // Organic clicks, likes, comments, and shares over impressions
"impressionCount": 1543, // Impressions on the post
"likeBy": [
{
"from": {
"name": "John Smith",
"id": "qDyUWJx2s",
"url": "https://www.linkedin.com/in/js",
"description": "Business Analyst at Ayrshare"
},
"media": {
"id": "urn:li:image:C4D03AQG_8MpwM6tDzw",
"mediaExpiresSeconds": 1728518400000,
"url": "https://media.licdn.com/dms/image/C4D03AQG_8MpwM6tDzw"
},
"platform": "linkedin",
"profileImageUrl": "https://media.licdn.com/dms/image/C4D03AQG_8MpwM",
"userName": "js",
"originalUrn": "urn:li:like:(urn:li:person:qDyUWJxqIO,urn:li:activity:722118608839074444)",
"type": "like"
}
],
"likeCount": 87, // Reaction count
"reactions": { // Per-type reaction breakdown
"like": 60,
"praise": 15,
"maybe": 0,
"empathy": 12,
"interest": 0,
"appreciation": 0
},
"share": "urn:li:activity:7221186088390794444",
"shareCount": 9, // Reshares of the post
"totalFirstLevelComments": 1,
"uniqueImpressionsCount": 1201, // Unique members reached (MEMBERS_REACHED)
"videoViewers": 280, // Video posts only
"videoViews": 320, // Video posts only
"videoWatchTimeMs": 451000 // Video posts only
},
"lastUpdated": "2024-08-04T22:46:15.660Z",
"nextUpdate": "2024-08-04T22:57:15.660Z"
},
"pinterest": {
"id": "718464946813167315", // Pinterest Social Post ID
"postUrl": "https://www.pinterest.com/pin/718464946813167315/",
"analytics": {
"altText": "Hello",
"boardId": "480126078963936788",
"boardOwner": {
"username": "johnsmith"
},
"boardSectionId": 12232,
"createdAt": "2024-10-20T06:25:08",
"creativeType": "REGULAR",
"description": "The most amazing board",
"dominantColor": "#4e6279",
"hasBeenPromoted": false,
"id": "480126010294119688",
"impression": 26,
"isOwner": true,
"isStandard": true,
"link": "https://www.mywebsite.com",
"media": {
"media_type": "image",
"images": {
"150x150": {
"width": 150,
"height": 150,
"url": "https://i.pinimg.com/150x150/2b/86/64/2b.jpg"
},
"400x300": {
"width": 400,
"height": 300,
"url": "https://i.pinimg.com/400x300/2b/86/64/2b.jpg"
},
"600x": {
"width": 564,
"height": 296,
"url": "https://i.pinimg.com/564x/2b/86/64/2b.jpg"
},
"1200x": {
"width": 1200,
"height": 630,
"url": "https://i.pinimg.com/1200x/2b/86/64/2b.jpg"
}
}
},
"note": "",
"outboundClick": 4,
"parentPinId": 23233,
"pinClick": 2,
"pinMetrics": null,
"productTags": [],
"profileVisit": 5, // Available for images only
"quartile95PercentView": 4,
"save": 1,
"saveRate": 1,
"title": "Looking great",
"totalComments": 2,
"totalReactions": 1,
"userFollow": 1, // Available for images only
/* Available for videos */
"video10sView": 3,
"videoAvgWatchTime": 0,
"videoMrcView": 0,
"videoStart": 0,
"videoV50WatchTime": 0
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
"reddit": {
"id": "182zitr",
"postUrl": "https://www.reddit.com/r/test/comments/182zitr/reddit_post_title/",
"analytics": {
"author": "ayrshare",
"created": "2023-11-24T19:10:06.000Z",
"permalink": "/r/test/comments/182zitr/reddit_post_title/",
"subreddit": "test",
"title": "Reddit Post Title",
"ups": 2,
"upvoteRatio": 1,
"url": "https://www.reddit.com/r/test/comments/182zitr/reddit_post_title/"
},
"lastUpdated": "2023-11-24T19:10:41.635Z",
"nextUpdate": "2023-11-24T19:21:41.635Z"
},
"snapchat": {
"id": "490edf20-4068-5bab-bf65-a5882b1ezuec",
"postUrl": "https://www.snapchat.com/add/chrisalpha1980/490edf20-4068-5bab-bf65-a5882b1ezuecc",
"analytics": [
{
"avgViewTime": 5230, // Average view time in milliseconds
"completes": 187, // Number of times the post was played to completion
"interactions": 42, // Number of times a user interacted with an asset. These include: tap forwards/backwards, swipe-away
"mediaId": "490edf20-4068-5bab-bf65-a5882b1ezuec",
"replies": 8, // Number of replies to this snap/story
"shares": 12, // Number of times this post has been shared
"snapCombinedUniques": 642, // Combined total of snapPaidUniques and storyUniques
"snapCombinedViews": 829, // Combined total of snapPaidViews and views
"snapPaidUniques": 174, // The sum of paid uniques for ads promoting the snaps within the Public Story
"snapPaidViews": 203, // The sum of paid impression for ads promoting the snaps within the Public Story
"storyAvgViewTime": 4870, // The calculation of the storyViewTime divided by storyUniques (milliseconds)
"storyFavorites": 28, // Number of times the post has been liked
"storySubscribes": 5, // Number of subscribe events occuring on the story post
"storyUniques": 468, // Unique users who have viewed a story/snap within a story
"storyViewTime": 2279160, // Milliseconds a story & snap in story asset has been viewed
"storyViews": 626, // Views for Story container
"swipeDowns": 15, // Number of swipe downs on this post
"swipeUps": 23, // Number of swipe ups on this post
"uniqueSessions": 512, // Number of unique sessions an asset has been engaged in
"viewTime": 3264000, // Milliseconds this post has been viewed
"viewers": 598, // Number of unique users who have viewed this post
"views": 712 // Number of times an asset has been viewed
}
],
"lastUpdated": "2025-05-21T11:14:40.570Z",
"nextUpdate": "2025-05-21T11:25:40.570Z"
},
"threads": {
"id": "17890643139123701",
"postUrl": "https://www.threads.com/@ayrshare/post/DI4nmXrNQA1",
"analytics": {
"views": 2,
"likes": 1,
"replies": 1,
"reposts": 0,
"shares": 0,
"quotes": 0
},
"lastUpdated": "2025-04-25T22:41:05.752Z",
"nextUpdate": "2025-04-25T22:52:05.752Z"
},
"tiktok": {
"id": "7034682002927550598", // TikTok Social Post ID
"postUrl": "https://www.tiktok.com/@borneild/video/7034682002927550598?utm_campaign=tt4d_open_api&utm_source=awawnhyictaos7o",
"analytics": {
"audienceCities": [ // Available 24-48 hours after posting. City distribution of video viewers for the top 10 cities. Must be an active post. See endpoint details.
{
"city_name": "US Teton County",
"percentage": 3.1
},
{
"city_name": "US Queens",
"percentage": 6.3
}
],
"audienceCountries": [ // Available 24-48 hours after posting. Country distribution of video viewers for the top 10 countries. Must be an active post. See endpoint details.
{
"country": "GB",
"percentage": 0.0029
},
{
"country": "US",
"percentage": 0.9604
}
],
"audienceGenders": [ // Updated by TikTok every 24-48 hours.
{
"percentage": 0.25,
"gender": "Female"
},
{
"percentage": 0.5,
"gender": "Male"
},
{
"percentage": 0.25,
"gender": "Other"
}
],
"audienceTypes": [
{
"percentage": 0,
"type": "FOLLOWER_PERCENT"
},
{
"percentage": 0,
"type": "NEW_VIEWER"
},
{
"percentage": 0,
"type": "NON_FOLLOWER_PERCENT"
},
{
"percentage": 0,
"type": "RETURN_VIEWER"
}
],
"averageTimeWatched": 5.6679, // Available 24-48 hours after posting.
"commentsCount": 23, // Total number of lifetime comments. Available 24-48 hours after posting.
"created": "2022-08-09T15:08:22Z",
"embedUrl": "https://www.tiktok.com/embed/v2/7129893524253756713",
"fullVideoWatchedRate": 0.0866, // Percentage of views that completed watching the full video. Available 24-48 hours after posting.
"impressionSources": [ // Different sources for the impressions, ranked from the largest contribution to the smallest. Available 24-48 hours after posting.
{
"impression_source": "Search",
"percentage": 0.0217
},
{
"impression_source": "Sound",
"percentage": 0
},
{
"impression_source": "Follow",
"percentage": 0
},
{
"impression_source": "For You",
"percentage": 0.917
},
{
"impression_source": "Hashtag",
"percentage": 0
},
{
"impression_source": "Personal Profile",
"percentage": 0
}
],
"likeCount": 22, // Total number of lifetime likes. Available 24-48 hours after posting.
"mediaType": "video",
"musicTitle": "♬ original sound - tiff",// If available
"musicUrl": "https://www.tiktok.com/music/original-sound-6689804660171082501?refer=embed", // If available
"name": "Mackly", // If available
"post": "Scramble up ur name & I'll try to guess it😍❤️ #foryoupage #petsoftiktok #aesthetic",
"postUrl": "https://www.tiktok.com/@tiktoktime/video/7129893524253756713?utm_campaign=tt4d_open_api&utm_source=awawnhyictaos7o7",
"reach": 252, // The number of people who watched your published content at least once. Available 24-48 hours after posting.
"shareCount": 1, // Total number of lifetime shares. Available 24-48 hours after posting.
"tags": [ // Tags included in the description
{
"tag": "#foryoupage",
"url": "https://www.tiktok.com/tag/foryoupage"
},
{
"tag": "#petsoftiktok",
"url": "https://www.tiktok.com/tag/petsoftiktok"
},
{
"tag": "#aesthetic",
"url": "https://www.tiktok.com/tag/aesthetic"
}
],
"thumbnailHeight": 576, // If available
"thumbnailUrl": "https://p16-sign.tiktokcdn-us.com/obj/tos-useast5-p-0068-tx",
"thumbnailWidth": 1006, // If available
"url": "https://www.tiktok.com/@tiktoktime",
"videoDuration": 13.984, // Video duration in seconds.
"videoViewRetention": [ // This metric indicates how many of your viewers are still watching after a certain amount of time.
{
"percentage": 0.1,
"second": "155"
},
{
"percentage": 0.07,
"second": "234"
}
],
/* Total number of lifetime users who viewed the video. Available 24-48 hours after posting.
If the user swipes away from the ad then swipes back, it would be counted as 2 impressions.
Therefore, a new video view will be counted again with the new impression session.
*/
"videoViews": 34
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
/* Note: if the post was sent as a Twitter Thread, the return will be an array, "twitter": [] */
"twitter": {
"id": "1313589441919827982", // Twitter Social Post ID
"postUrl": "https://www.twitter.com/myaccount/1313589441919827982",
"analytics": {
"created": "2022-09-07T22:12:10.000Z",
"entities": {
"urls": [
{
"start": 77, // Starting character position (inclusive)
"end": 96, // Ending character position (exclusive)
"url": "https://t.co/abc123", // Shortened t.co URL
"expandedUrl": "https://www.ayrshare.com/docs", // Full URL after redirect
"displayUrl": "ayrshare.com/docs", // User-friendly display version
"unwoundUrl": "https://www.ayrshare.com/docs" // Final destination URL
}
],
"hashtags": [
{
"start": 97, // Starting character position (inclusive)
"end": 112, // Ending character position (exclusive)
"tag": "SocialMediaAPI" // Hashtag text without #
}
],
"mentions": [
{
"start": 113, // Starting character position (inclusive)
"end": 122, // Ending character position (exclusive)
"username": "ayrshare" // Username without @
}
],
"cashtags": [
{
"start": 123, // Starting character position (inclusive)
"end": 128, // Ending character position (exclusive)
"tag": "META" // Stock symbol without $
}
],
"annotations": [
{
"start": 46, // Starting character position (inclusive)
"end": 54, // Ending character position (exclusive)
"probability": 0.9456, // Confidence score (0.0 to 1.0)
"type": "Product", // Entity type: Person, Place, Product, Organization, Other
"normalizedText": "Ayrshare" // Standardized entity name
}
]
},
"media": [
{
"width": 1920,
"durationMs": 28240,
"height": 1080,
"mediaKey": "7_1739849636389814272",
"previewImageUrl": "https://pbs.twimg.com/ext_tw_video_thumb/1739849636389814272/pu/img/bCkAdkD0R00-ZlkY.jpg",
"type": "video",
"mediaUrls": [ // available for videos and gifs
{
"bitRate": 256000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/480x270/4RoZCqynednVMFgy.mp4?tag=12"
},
{
"bitRate": 832000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/640x360/RYy5iHTIxu7_iyZm.mp4?tag=12"
},
{
"bitRate": 2176000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/1280x720/9UpR90ekFA-9Ucxo.mp4?tag=12"
},
{
"contentType": "application/x-mpegURL",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/pl/0AVy2xIOLQcsrEZG.m3u8?tag=12&container=fmp4"
}
]
}
],
"name": "Wonder World",
"post": "Just launched our social media campaign using Ayrshare! Check out the API at https://t.co/abc123 #SocialMediaAPI @ayrshare $META",
"publicMetrics": { // Organic and paid metrics available publicly. Use this to measure Tweet engagement.
"retweetCount": 0, // Retweets count
"quoteCount": 0, // Times the Tweet was quoted count
"likeCount": 0, // Likes for the Tweet count
"replyCount": 0, // Replies count
"quoteCount": 0, // Times the Tweet was quoted count
"bookmarkCount": 0, // Bookmarks count
"impressionCount": 23 // How many times the Tweet has been viewed (not unique by user). A view is counted if any part of the Tweet is visible on the screen.
},
"nonPublicMetrics": { // Organic and paid metrics not available publicly. Not available for Tweets older than 30 days. Use this to determine the total number of impressions generated for the Tweet.
"userProfileClicks": 1, // User profile clicks
"engagements": 1, // How many times the Tweet has been engaged with.
"impressionCount": 5, // How many times the Tweet has been viewed (not unique by user). A view is counted if any part of the Tweet is visible on the screen.
"video": { // Users who played through to each quartile in a video. This reflects the number of quartile views across all Tweets in which the given video has been posted.
"playback25Count": 4,
"playback50Count": 2,
"playback75Count": 1,
"playback0Count": 4,
"playback100Count": 1
}
},
"organicMetrics": { // Non-paid metrics. Not available for Tweets older than 30 days. Use this to measure organic engagement for the Tweet.
"likeCount": 0, // Tweet likes
"impressionCount": 5, // How many times the Tweet has been viewed (not unique by user). A view is counted if any part of the Tweet is visible on the screen.
"replyCount": 0, // Replies count
"retweetCount": 0, // R countetweets
"userProfileClicks": 1, // User profile clicks
"video": { // Users who played through to each quartile in a video. This reflects the number of quartile views across all Tweets in which the given video has been posted.
"playback25Count": 4,
"playback0Count": 4,
"playback100Count": 1,
"viewCount": 4,
"playback50Count": 2,
"playback75Count": 1
}
},
"poll": { // Present if Tweet has a poll
"durationMinutes": 5, // Duration in minutes
"endDatetime": "2023-05-29T17:28:05.000Z",
"votingStatus": "open", // "open" or "closed"
"id": "1663234520718371960",
"options": [
{
"position": 1,
"label": "yes",
"votes": 3
},
{
"position": 2,
"label": "maybe",
"votes": 1
},
{
"position": 3,
"label": "no",
"votes": 0
}
]
},
"urls": [ // Available for long Tweets only
{
"start": 866,
"end": 889,
"url": "https://t.co/3r6xz4hBnM",
"expandedUrl": "https://www.cnn.com",
"displayUrl": "cnn.com"
}
],
"username": "wondrous"
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
"youtube": {
"id": "RWJMpOUGicU", // YouTube Social Post ID
"postUrl": "https://youtu.be/RWJMpOUGicU",
"analytics": {
"averageViewDuration": 6, // The average length, in seconds, of video playbacks.
"averageViewPercentage": 92.01, // The average percentage of a video watched during a video playback.
"channelTitle": "youtubeHandle",
"comments": 22,
"created": "2022-08-15T19:14:44Z",
"description": "Doubt is not a pleasant condition, but certainty is absurd. - Voltaire",
"dislikes": 3,
"engagedViews": 148, // The number of views counted past the initial seconds of playback — YouTube's view-counting methodology prior to August 24, 2026.
"estimatedMinutesWatched": 23, // The number of minutes that users watched videos for the specified channel, content owner, video, or playlist.
"likes": 54,
"liveBroadcastDetails": { // Only present if liveBroadcast is "live" or "upcoming"
"activeLiveChatId": "Cg0KC3Ez", // The ID of the currently active live chat attached to this video. Not present if broadcast complete and chat no longer live
"actualStartTime": "2023-05-04T10:14:56Z", // The time that the broadcast actually started.
"actualEndTime": "2023-05-04T10:15:15Z", // The time that the broadcast actually ended.
"concurrentViewers": 384343, // The number of viewers currently watching the broadcast. Not present if the broadcast has ended.
"scheduledEndTime": "2023-05-04T10:15:00Z", // The time that the broadcast is scheduled to end if a scheduled end time was set.
"scheduledStartTime": "2023-05-04T10:15:00Z" // The time that the broadcast is scheduled to begin.
},
"liveBroadcast": "live", // Values: "none", "live", "upcoming"
"madeForKids": false,
"privacyStatus": "public",
"publishedAt": "2023-05-08T12:30:26Z",
"subscribersGained": 23, // The number of times that users subscribed to a channel.
"subscribersLost": 1, // The number of times that users unsubscribed from a channel.
"tags": ["sweet", "sour"], // Tags assigned to video
"thumbnailUrl": "https://i.ytimg.com/vi/RWJMpOUGicU/default.jpg",
"title": "Yo time",
"videosAddedToPlaylists": 2, // The number of times that videos were added to any YouTube playlists. The videos could have been added to the video owner's playlist or to other channels' playlists.
"views": 153
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
"status": "success",
"code": 200,
"id": "IHvCLacgPc6hMU9IQ6oK"
}
```
```json 200: Backfilled Response theme={"system"}
{
"facebook": {
"id": "104923907983682_108329000309742",
"postUrl": "https://www.facebook.com/104923907983682_108329000309742",
"analytics": {
"commentsCount": 5,
"likeCount": 23,
"sharesCount": 12,
"mediaView": 450,
"backfilledFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
},
"status": "success",
"code": 200,
"id": "IHvCLacgPc6hMU9IQ6oK"
}
```
```json 200: Recovered Response theme={"system"}
{
"facebook": {
"id": "104923907983682_108329000309742",
"postUrl": "https://www.facebook.com/104923907983682_108329000309742",
"analytics": {
"commentsCount": 5,
"likeCount": 23,
"sharesCount": 12,
"mediaView": 450,
"recoveredFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
},
"status": "success",
"code": 200,
"id": "IHvCLacgPc6hMU9IQ6oK"
}
```
```json 404 - Bad Request theme={"system"}
/* Often if manually deleted at social network */
{
"status": "error",
"code": 116,
"id": "N7GAZeJSAYcdCpHSm3xQ", // Ayrshare Post ID
"facebook": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent and the post at the social network has not been deleted.",
"id": "104619420979033_588326339980968" // Facebook Platform ID
},
"instagram": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent and the post at the social network has not been deleted.",
"id": "18349092271045074" // Instagram Platform ID
},
"linkedin": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent and the post at the social network has not been deleted.",
"id": "urn:li:share:7038277251594878976" // LinkedIn Platform ID
},
"pinterest": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent and the post at the social network has not been deleted. Pin not found."
},
"twitter": {
"action": "post",
"status": "error",
"code": 186,
"message": "Could not find tweet with id: [1632511562660433929].",
"id": "1632511562660433929" // Twitter Platform ID
},
"youtube": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent and the post at the social network has not been deleted.",
"id": "rAMFoqZeOGg" // YouTube Platform ID
},
"lastUpdated": "2023-03-06T21:59:06.064Z",
"nextUpdate": "2023-03-06T22:10:06.064Z"
}
```
```json 404 - Not Found theme={"system"}
{
"action": "analytics",
"status": "error",
"code": 186,
"message": "Post ID not found.",
"id": "7aVBe0jw27DNwFq92KZ"
}
```
# Analytics on a Social Network
Source: https://www.ayrshare.com/docs/apis/analytics/social
POST /analytics/social
Get analytics and demographics on a user's social profile, such as impressions, views, and followers
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get analytics and demographics on a user's social profile, such as impressions, views, and followers.
Currently available for Bluesky, Facebook Pages, Google My Business, Instagram, LinkedIn, Pinterest, Reddit, Snapchat, Threads, TikTok, X/Twitter, and YouTube.
> [!WARNING]
>
> **Facebook: some reach and video metrics were retired by Meta (June 15, 2026).** Meta removed the unique impression and 3-second video-view (unique) Insights metrics across all Graph API versions, so the Facebook `analytics` object no longer returns the `pagePostsImpressions*` family (`pagePostsImpressions`, `pagePostsImpressionsPaid`, `pagePostsImpressionsUnique`, `pagePostsImpressionsOrganicUnique`, `pagePostsImpressionsViral*`, `pagePostsImpressionsNonviral*`, `pagePostsServedImpressionsOrganicUnique`) or `pageVideoViewsUnique`. For reach, use `pageTotalMediaViewUnique`, Meta's Total Unique Media Views replacement for the retired unique-impression fields. Meta reports it per day, and the value is those daily counts added together, so a person who viewed your content on two different days counts twice. `pageMediaView` remains available for total, non-unique views. `pagePostEngagements`, `pageVideoViews`, and `pageVideoViewsPaid` are unaffected. Reference: [Upcoming API Changes — June 15, 2026](/docs/whatsnew/upcoming-api-changes#june-15-2026).
Facebook Page analytics is only available on Pages with 100 or more likes, such as demographics. Facebook typically updates their metrics once every 24 hours.
Instagram may take up to 48 hours to calculate the analytics data. Follower count analytics is not available with fewer than 100 followers. Demographic metrics only return the top 45 performers, only viewers for whom we have demographic data are used in demographic metric calculations, and demographic data is not returned if the Instagram User has fewer than 100 engagements during the last 30 days.
When retrieving Instagram social analytics, demographic information may not appear in the response for particular metrics. Demographic information will appear when the metrics have more than 100 people in each breakdown. Please see [Instagram Analytics Demographics Warning](/docs/help-center/technical-support/instagram-analytics-demographics-warning) for more information.
LinkedIn supports both Company Page analytics and personal (member) profile analytics. For personal profiles, the `analytics` object includes a lifetime `followersCount`, daily follower growth in `followersDaily[]`, and aggregate post metrics (`impressionCount`, `uniqueImpressionsCount`, `likeCount`, `commentCount`, `shareCount`). LinkedIn `count` totals are eventually consistent, but not immediately consistent, and can take up to 24-48 hours in some cases. The aggregate `shareCount`, `likeCount`, and `commentCount` for personal profiles are best-effort and may differ slightly from the numbers shown in the LinkedIn UI.
TikTok can take 24-48 hours to update their analytics data, such as video views, demographics, likes, shares, and comments.
Please see the [post analytics endpoint](/docs/apis/analytics/post) for additional info.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**platforms** `array` *required*
Social media platforms to get analytics. Accepts an array of strings with values:
```json theme={"system"}
{
"platforms": ["bluesky", "instagram", "facebook",
"gmb", "linkedin", "pinterest", "reddit",
"snapchat", "threads", "tiktok", "twitter", "youtube"]
}
```
**quarters** `integer`
Specifies how many quarters of historical data to return. A quarter is:
* 85 days for Facebook
* 90 days for Google Business Profile, Instagram, TikTok, and YouTube
* 90 days for Snapchat (capped at 1 quarter / 90 days max due to Snapchat API limitations)
Available for Facebook, Google Business Profile, Instagram, Snapchat, TikTok, and YouTube platforms. Valid values: 1–4.
Only values greater than 0 activate date filtering.
**Date filtering (Google Business Profile, Instagram & TikTok):** Date filtering is active when `daily=true` OR `quarters > 0`.
If neither `daily` nor `quarters` is provided, all-time data is returned with no date filter applied
(for Google Business Profile, all-time means the trailing 24 months).
Note: `quarters: 0` is now treated as no date range (all-time data). Previously, `quarters: 0` was
treated as `quarters: 1`.
**daily** `boolean` (default: false)
When set to `true`, returns analytics data as daily time-series values instead of aggregated
totals. This option is only available for Facebook, Google Business Profile, Instagram, Snapchat, TikTok, and YouTube platforms. Due
to the larger data size, using [compression](/docs/apis/overview#compression) is recommended.
For Google Business Profile, Instagram, and TikTok, setting `daily=true` also activates date filtering using a default
shorter quarters window.
**Instagram reach:** With `daily=true`, the Instagram response returns a nested `reach` object
(containing `period` and a `values` time-series) instead of the scalar `reachCount`
field that is returned in non-daily mode.
**period60Days** `boolean` (default: true)
For TikTok analytics, when set to true, this returns only the 60-day aggregate totals for comments, shares, and views (`commentCountTotal`, `shareCountTotal`, `viewCountTotal`).
This provides faster response times compared to retrieving the full analytics history. Set to `false` to get the full analytics history.
Note: Do not use this parameter together with `daily=true` as they are incompatible.
Important: As of March 1, 2025, TikTok moved to 60-day totals. As of April 2026, TikTok uses
quarters-based date filtering — use the `quarters` parameter to control the date window (e.g., `quarters: 1` = 90 days, `quarters: 2` = 180 days). See [upcoming changes](/docs/whatsnew/upcoming-api-changes#march-1-2025) for details.
**youtube** `object`
Platform-specific options for YouTube analytics.
**`lifetime`** (boolean, default: `false`): When set to `true`, includes `lifetimeLikes` in the response - the sum of likes across all public videos on the channel. This is computed by fetching all videos and summing their like counts, so it may take longer for channels with many videos.
**Threshold:** Channels with more than 1,000 videos will return `lifetimeLikes: null` and a warning in the top-level [`warnings`](#warnings) array. This prevents excessive API usage.
**Caching:** Per-channel, with shorter TTLs for non-success outcomes so retries pick up state changes promptly:
* Successful `lifetimeLikes` value: **24 hours**.
* 1,000-video bailout (warning `code: 445`): **1 hour** — short enough that a channel which deletes videos to drop below the threshold doesn't have to wait a full day for a real value.
* Transient YouTube Data API failures (warning `code: 446`): **not cached** — the next request retries.
**Note:** Deleted or private videos are excluded from the sum, so the total may differ from the "true" lifetime likes for channels that have removed videos.
**Request example:**
```json theme={"system"}
{
"platforms": ["youtube"],
"youtube": { "lifetime": true }
}
```
**userId** `string`
X/Twitter only. This parameter allows you to retrieve posts from a specific X/Twitter user by their numeric ID, rather than from your linked account.
For example, to get all posts from the handle `@Google`, you would use their numeric userId `20536157`.
You can find any X/Twitter user's numeric userId by using the [Brands Get User](/docs/apis/listen/brand-user) endpoint.
Note: Use only the API KEY in the header to make this request. Do not include the Profile Key.
**userName** `string`
X/Twitter only. This parameter allows you to retrieve posts from a specific X/Twitter user by their handle, rather than from your linked account.
For example, to get all posts from the handle `@Google`.
Note: Use only the API KEY in the header to make this request. Do not include the Profile Key.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"platforms": ["bluesky", "facebook", "gmb", "instagram", "linkedin",
"pinterest", "reddit", "snapchat", "threads", "tiktok", "twitter", "youtube"],
"quarters": 1}' \
-X POST https://api.ayrshare.com/api/analytics/social
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/analytics/social", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
platforms: [
"bluesky",
"facebook",
"gmb",
"instagram",
"linkedin",
"pinterest",
"reddit",
"snapchat",
"threads",
"tiktok",
"twitter",
"youtube"
],
quarters: 1
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'platforms': ['bluesky', 'facebook', 'gmb', 'instagram',
'linkedin', 'pinterest', 'reddit', 'snapchat',
'threads', 'tiktok', 'twitter', 'youtube']}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/analytics/social',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
['bluesky', 'facebook', 'gmb', 'instagram', 'linkedin',
'pinterest', 'reddit', 'snapchat', 'threads', 'tiktok', 'twitter', 'youtube'] // required
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"platforms": []string{"bluesky", "facebook", "gmb", "instagram", "linkedin",
"pinterest", "reddit", "snapchat", "threads", "tiktok", "twitter", "youtube"},
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/social",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace SocialPOSTRequest_csharp
{
class Social
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/analytics/social";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"platforms\": [\"bluesky\", \"facebook\", \"gmb\", \"instagram\", " +
"\"linkedin\", \"pinterest\", \"reddit\", \"snapchat\", \"threads\", \"tiktok\", \"twitter\", \"youtube\"]}";
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
> [!NOTE]
>
> When cumulative metrics (e.g., followers, likes) are temporarily unavailable from the social network, the API automatically backfills them from stored data. Two optional fields may appear in the per-platform `analytics` object:
>
> * **`backfilledFrom`** (string, ISO 8601) — Present when one or more cumulative metrics were substituted from stored data. The timestamp indicates when the stored data was last updated.
> * **`recoveredFrom`** (string, ISO 8601) — Present when the entire analytics response was recovered from stored data due to a complete API failure. The timestamp indicates when the stored data was last updated. For Snapchat, where `analytics` is a list, `recoveredFrom` appears beside `analytics` instead.
>
> Stored data older than 4 days is considered stale and will not be used for backfill or recovery.
>
> **LinkedIn `reactions`:** The post-level cumulative `reactions` metric (an object of per-type reaction counts) is subject to this empty-only backfill. When LinkedIn rate-limits the reactions fetch, the value carries forward from the last successful snapshot — and `backfilledFrom` is set to that snapshot's timestamp — instead of regressing to empty.
> [!NOTE]
>
> **LinkedIn personal (member) analytics — re-link required.** Personal LinkedIn profiles linked before member analytics shipped do not have the required analytics scopes. Social analytics requests for those profiles return [error code `475`](/docs/errors/errors-ayrshare#linkedin-analytics-errors) ("re-link your LinkedIn profile to enable analytics"). The account owner must re-link their LinkedIn profile on the Social Accounts page to grant the new scopes. Allow a few minutes after re-linking for code `475` to clear (Ayrshare and LinkedIn both briefly cache the permission state, typically \~5-10 minutes). Posting is unaffected.
> [!NOTE]
>
> **`warnings`** (array of objects, optional top-level field) — Present only when Ayrshare needs to inform the caller of a non-fatal condition (e.g., an opt-in computation was skipped). Absent from the response when there is nothing to warn about.
>
> Each entry is a structured object, not a free-form string:
>
> | Field | Type | Description |
> | - | - | - |
> | `action` | string | The operation context that produced the warning (e.g. `"analytics"`). |
> | `status` | string | Always `"warning"` for entries in the `warnings` array. |
> | `code` | number | Stable Ayrshare warning code. Safe to match on in client code. |
> | `message` | string | Human-readable description. |
> | `details` | string | Optional additional context specific to this warning instance. |
>
> Known warning codes:
>
> * `445` — `lifetimeLikes` skipped because the YouTube channel exceeds the 1,000-video threshold.
> * `446` — `lifetimeLikes` unavailable because the YouTube Data API returned errors for the uploads playlist or every `videos.list` batch.
```json 200: Success Aggregate {3, 29, 112, 129, 194, 256, 277, 355, 392, 461, 480, 558, 598} theme={"system"}
{
"status": "success",
"bluesky": {
"analytics": {
"associated": { // Group of associated features and capabilities
"lists": 0, // Number of lists created by the user
"feedgens": 0, // Number of custom feeds generated by the user
"starterPacks": 0, // Number of starter packs created by the user
"labeler": false // Whether the user can create content labels
},
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:n7atr", // URL to user's profile picture
"created": "2024-12-14T20:39:21.343Z", // Timestamp when the account was created
"displayName": "Jumping Jack", // User's chosen display name
"followersCount": 15, // Number of users following this account
"followsCount": 5, // Number of users this account follows
"handle": "jack.bsky.social", // User's unique handle/username
"id": "did:plc:n7atrjd22xgkmgwig6dzlh", // Bluesky Social ID
"indexedAt": "2024-12-14T20:39:21.343Z", // Timestamp when this data was last indexed
"labels": [], // Array of content labels applied to the account
"postsCount": 20, // Number of posts made by the user
"viewer": { // Information about the viewing user's relationship
"muted": false, // Whether the viewing user has muted this account
"blockedBy": false // Whether this account has blocked the viewing user
}
},
"lastUpdated": "2025-01-11T20:05:05.214Z",
"nextUpdate": "2025-01-11T20:16:05.214Z"
},
"facebook": {
/* See specific time ranges - Lifetime unless otherwise noted */
"analytics": {
"about": "The best site to watch movies online.",
"birthday": "11/20/1985", // Applicable to Pages representing people
"caterory": "Movie", // The Page's category. e.g. Product/Service, Computers/Technology.
"emails": ["john@example.com"],
"engagement": { // If available, otherwise use fanCount
"count": 587, // Page like count, same as fanCount
"socialSentence": "587 people like this."
},
"fanCount": 587,
"followersCount": 587,
"id": "102619320979033",
"instagramBusinessAccount": {
"id": "17841452212707498" // Present if an Instagram account is linked with the Facebook page
},
"isPublished": true, // If the FB Page is publicly visable.
"link": "https://www.facebook.com/102619320979033",
"location": { // If a location is set
"street": "142 W 57th St",
"zip": "10019"
},
"name": "theGoodone",
"pageFollows": 2,
"pageMediaView": 929, // The number of times your content was played or displayed. Content includes videos, posts, stories and ads.
"pageMediaViewIsFromAds": 1, // The number of times your content was played or displayed from ads.
"pageMediaViewIsFromFollowers": 18, // The number of times your content was played or displayed from followers.
"pagePostEngagements": 20, // The number of interactions with your posts such as reactions, comments, shares and more.
// The pagePostsImpressions* family and pagePostsServedImpressionsOrganicUnique were retired by Meta (June 15, 2026); see the note at the top of this page.
"pageTotalMediaViewUnique": 412, // Reach: unique people your content was played or displayed to, counted per day and added up across the range.
// --- Page Video Data ---
"pageVideoCompleteViews30s": 32, // The number of times your Page's videos played for at least 30 seconds, or for nearly their total length if they're shorter than 30 seconds. During a single instance of a video playing, we'll exclude any time spent replaying the video.
"pageVideoCompleteViews30sAutoplayed": 19, // Autoplayed 30s complete.
"pageVideoCompleteViews30sClickToPlay": 5, // Click to play 30s complete.
"pageVideoCompleteViews30sOrganic": 23, // Organic 30s complete.
"pageVideoCompleteViews30sPaid": 2, // Paid 30s complete.
"pageVideoCompleteViews30sRepeatViews": 3, // Repeat 30s complete.
"pageVideoCompleteViews30sUnique": 3, // Unique 30s complete.
"pageVideoRepeatViews": 4, // The number times of repeat views.
"pageVideoViews": 31, // The number of times your Page's videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds. During a single instance of a video playing, we'll exclude any time spent replaying the video.
"pageVideoViewsAutoplayed": 115, // Number of views that autoplayed on a user's screen
"pageVideoViewsOrganic": 32, // The number of organic video views.
"pageVideoViewsPaid": 6, // The number of times your Page's promoted videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds. For each impression of a video, we'll count video views separately and exclude any time spent replaying the video.
"pageVideoViewsByPaidNonPaid": { // Breakdown of view views by paid and non-paid
"total": 32,
"unpaid": 5,
"paid": 27
},
"pageVideoViewsByUploadedHosted": { // Daily video views on a page-level broken down by all variants of page-uploaded and page-hosted variants.
"pageUploaded": 147,
"pageUploadedFromCrossposts": 0,
"pageUploadedFromShares": 3,
"pageHostedCrosspost": 0,
"pageHostedShare": 0,
"pageOwned": 144
},
"pageVideoViewsClickToPlay": 32, // The number of times your Page's videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds, after people clicked play. During a single instance of a video playing, we'll exclude any time spent replaying the video.
"pageVideoViewsOrganic": 147, // The number of times your Page's videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds, by organic reach. During a single instance of a video playing, we'll exclude any time spent replaying the video.
"pageVideoViewsPaid": 0, // The number of times your Page's promoted videos played for at least 3 seconds, or for nearly their total length if they're shorter than 3 seconds. For each impression of a video, we'll count video views separately and exclude any time spent replaying the video.
// pageVideoViewsUnique was retired by Meta (June 15, 2026); see the note at the top of this page.
"reactions": { // Total over past 180 days
"like": 1, // Like reactions - The "like" reaction counts include both "like" and "care" reactions.
"love": 1, // Love reactions
"anger": 1, // Anger reactions
"haha": 1, // Haha reactions
"wow": 1, // Wow reactions
"sorry": 1, // Sorry reactions
"total": 6 // Total number of reactions
},
"unreadMessageCount": 12, // Total unread FB Messages - if applicable
"username": "ayrshare",
"verified": true, // verified Facebook Page
"website": "https://www.theGoodone.com"
},
"quarters": 4,
"lastUpdated": "2024-04-25T21:28:10.877Z",
"nextUpdate": "2024-04-25T21:39:10.877Z"
},
/*
* Google Business Profile analytics totals are for the past 24 months unless a date
* range is active, which happens when `daily=true` or `quarters` is greater than 0.
* (`quarters: 0` is not a range — it returns the same 24-month totals.) With a range
* active the window is `quarters` × 90 days, and `daily=true` without a `quarters`
* greater than 0 uses one quarter. `quarters` is then returned alongside `analytics`
* (see the Daily example).
*/
"gmb": {
"analytics":
{
"businessBookings": 0, // The number of bookings received from the business profile.
"businessConversations": 0, // The number of message conversations received on the business profile.
"businessDirectionRequests": 328, // The number of times a direction request was requested to the business location.
"businessFoodOrders": 0, // The number of food orders received from the business profile.
"businessImpressionsDesktopMaps": 153, // Business impressions on Google Maps on Desktop devices. Multiple impressions by a unique user within a single day are counted as a single impression.
"businessImpressionsDesktopSearch": 9675, // Business impressions on Google Search on Desktop devices. Multiple impressions by a unique user within a single day are counted as a single impression.
"businessImpressionsMobileMaps": 65, // Business impressions on Google Maps on Mobile devices. Multiple impressions by a unique user within a single day are counted as a single impression.
"businessImpressionsMobileSearch": 2070, // Business impressions on Google Search on Mobile devices. Multiple impressions by a unique user within a single day are counted as a single impression.
"callClicks": 2, // The number of times the business profile call button was clicked.
"websiteClicks": 134 // The number of times the business profile website was clicked.
},
"lastUpdated": "2024-04-25T21:28:10.877Z",
"nextUpdate": "2024-04-25T21:39:10.877Z"
},
"instagram": {
"analytics": {
/*
* Demographics: The following audience demographic data is only available
* for Intagram Users with at least 100 followers and engagements (last 30 days).
* Additionally, audience demographic data is only available when
* there is more than 100 people in each demographic category.
*/
"audienceCity": {
"Sydney, New South Wales": 2,
"London, England": 2,
"Bridgewater, New Jersey": 1,
"Puli, Nantou": 1
},
"audienceCityEngagedAudienceDemographics": {
"KIRKLAND, WASHINGTON": 3,
"LAKE BOSWORTH, WASHINGTON": 6,
"WASHOUGAL, WASHINGTON": 1,
"SEATTLE, WASHINGTON": 1,
"BURLINGTON, WASHINGTON": 2
},
"audienceCountry": {
"TW": 1,
"HK": 5,
"SG": 1,
"AU": 2
},
"audienceCountryEngagedAudienceDemographics": {
"DE": 0,
"GB": 0,
"US": 161
},
"audienceGenderAge": {
"F.18-24": 1,
"F.25-34": 6,
"F.35-44": 13,
"F.45-54": 16
},
"audienceGenderAgeEngagedAudienceDemographics": {
"F.13-17": 2,
"F.18-24": 11,
"F.25-34": 15,
"F.35-44": 24
},
"biography": "What to watch next? Get recommendations from your friends.",
"commentsCount": 6, // Total comments of the past 500 posts
"followersCount": 2, // Current total of followers - must have at least 100 followers to show
"followsCount": 19, // Current total of users followed
"id": "17941424040207809",
"igId": 24372260340, // legacy Instagram user ID - only available when Instagram is linked via Facebook Page
"igLoginId": "84739261504829371", // Instagram native user ID - only available when Instagram is linked via direct Instagram login
"likeCount": 116, // Total like of the past 500 posts
"mediaCount": 266, // Total media count on the account, always lifetime. Not filtered by `quarters` or `daily`.
"name": "theGoodone",
"profilePictureUrl": "https://scontent.fphl1-1.fna.fbcdn.net/v/t51.2885-15/74638883_536953567098493_7984001172816003072_n.jpg?_nc_cat=102&ccb=1-5&_nc_sid=86c713&_nc_ohc=krzDDPMjIZQAX-m1PsT&_nc_ht=scontent.fphl1-1.fna&edm=AL-3X8kEAAAA&oh=00_AT9uZhfkQyHWuThTgktvXxB8XEK_xV1_z72frRwtyGwHTA&oe=6246E46D",
"reachCount": 1, // 180 day period - Total number of unique users who have viewed at least one media. Scalar value returned in non-daily mode; when daily=true a nested `reach` object is returned instead (see the Daily success example).
"shareCount": 234, // Total number of shares on the Instagram account's content (posts, stories, reels, videos, live videos) aggregated per-day over the active date window. Follows the same 90-day rolling maximum as `viewsCount` and respects `quarters` / `daily`. Defaults to `0` for accounts where Meta does not return the `shares` metric.
"username": "thegoodone",
"viewsCount": 123212, // 90 day rolling window (maximum Meta allows on the User Metrics endpoint) - Total number of times the IG User's media have been viewed
"website": "https://www.mywebsite.com/"
},
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
},
/* LinkedIn Corporate: Lifetime analytics since start of account */
"linkedin": {
// The likeCount and commentCount fields do not count likes or comments made by companies, including the company that authored the share and are not decremented when a like or comment is deleted.
"analytics": {
"clickCount": 10,
"clicks": { // Only available for organizations that have career pages. This is a paid product.
"mobileCareersPageClicks": {
"careersPageJobsClicks": 2,
"careersPagePromoLinksClicks": 0,
"careersPageEmployeesClicks": 0
},
"careersPageClicks": {
"careersPagePromoLinksClicks": 5,
"careersPageBannerPromoClicks": 0,
"careersPageJobsClicks": 0,
"careersPageEmployeesClicks": 3
}
},
"commentCount": 12,
"engagement": 0.06060606060606061, // Number of organic clicks, likes, comments, and shares over impressions.
"followers": {
"organicFollowerCount": 7,
"paidFollowerCount": 2,
"totalFollowerCount": 12 // Total numuber might be higher than organic + paid. For total use this field.
},
"impressionCount": 264,
"likeCount": 6, // Number of likes. This field can become negative when members who liked a sponsored share later unlike it.
"shareCount": 3, // Number of mentions of the company in a share across LinkedIn
"uniqueImpressionsCount": 51,
"views": {
"aboutPageViews": 2,
"allDesktopPageViews": 8,
"allMobilePageViews": 8,
"allPageViews": 16,
"careersPageViews": 0, // Only available for organizations that have career pages
"desktopAboutPageViews": 0,
"desktopCareersPageViews": 0,
"desktopInsightsPageViews": 0,
"desktopJobsPageViews": 0,
"desktopLifeAtPageViews": 0,
"desktopOverviewPageViews": 8,
"desktopPeoplePageViews": 0,
"desktopProductsPageViews": 0,
"insightsPageViews": 0,
"jobsPageViews": 0,
"lifeAtPageViews": 0,
"mobileAboutPageViews": 2,
"mobileCareersPageViews": 0,
"mobileInsightsPageViews": 0,
"mobileJobsPageViews": 0,
"mobileLifeAtPageViews": 0,
"mobileOverviewPageViews": 6,
"mobilePeoplePageViews": 0,
"mobileProductsPageViews": 0,
"overviewPageViews": 14,
"peoplePageViews": 0,
"productsPageViews": 0
}
},
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
},
/* LinkedIn Personal (member) */
"linkedin": {
"description": "Founder",
"id": "Z_yXaxh_Et", // LinkedIn Personal ID
"name": "John Doe",
"platform": "linkedin",
"profileImageUrl": "https://media.licdn.com/dms/image/v2/C5103AQHORT70jVfKVA/profile",
"url": "https://www.linkedin.com/in/johndoe",
"userName": "johndoe",
"analytics": {
"commentCount": 410, // Aggregate comments
"followersCount": 4210, // Lifetime follower count
"followersDaily": [ // Daily follower growth (trailing 30 days)
{ "memberFollowersCount": 12, "dateRange": { "start": { "year": 2026, "month": 6, "day": 9 } } }
],
"impressionCount": 98000, // Aggregate impressions across posts
"likeCount": 2300, // Aggregate reactions
"shareCount": 190, // Aggregate reshares
"uniqueImpressionsCount": 41000 // Aggregate unique members reached
}
},
/* 90 days summary and daily details, board analytics full history */
"pinterest": {
"analytics": {
"board": {
"boardPinsModifiedAt": "2023-03-14T22:32:06Z",
"collaboratorCount": 0,
"createdAt": "2019-03-07T22:02:22Z",
"description": "",
"followerCount": 24,
"id": "4299544013183293",
"media": {
"pinThumbnailUrls": [
"https://i.pinimg.com/150x150/62/08/8b/62088b44a6152de7198efd3c2bc5c0.jpg",
"https://i.pinimg.com/150x150/ac/b7/c9/acb7c9f8096317180c20022e9b9b8a.jpg",
"https://i.pinimg.com/150x150/5a/d7/56/5ad756fb64a688934bd06853dff3a2.jpg",
"https://i.pinimg.com/150x150/5a/d7/56/5ad755db64a688934bd06853dff3a2.jpg",
"https://i.pinimg.com/150x150/a9/22/b6/a922ef5289639b617c24704d806998.jpg"
],
"imageCoverUrl": "https://i.pinimg.com/400x300/f8/ec/14/f8ec14e7770a53b2e3595773564950.jpg"
},
"name": "Places to Retire",
"pinCount": 64,
"privacy": "PUBLIC",
"username": "glueUp"
},
"clickthrough": 23,
"clickthroughRate": 0.34,
"closeup": 3,
"closeupRate": 0.43,
"engagement": 54,
"engagementRate": 0.43,
"fullScreenPlay": 23,
"fullScreenPlaytime": 42,
"impression": 12231,
"outboundClick": 256,
"outboundClickRate": 0.0.23,
"pinClick": 32,
"pinClickRate": 0.23,
"quartile95PercentView": 42,
"save": 45,
"saveRate": 23,
"video10sView": 902,
"videoAvgWatchTime": 234,
"videoMrcView": 23,
"videoStart": 34,
"videoV50WatchTime": 343,
"daily": [
{
"date": "2021-09-28",
"metrics": {
"SAVE_RATE": 0,
"OUTBOUND_CLICK_RATE": 0,
"IMPRESSION": 0,
"CLOSEUP_RATE": 0,
"ENGAGEMENT_RATE": 0,
"VIDEO_10S_VIEW": 0,
"VIDEO_AVG_WATCH_TIME": 0,
"ENGAGEMENT": 0,
"CLOSEUP": 0,
"FULL_SCREEN_PLAY": 0,
"SAVE": 0,
"PIN_CLICK_RATE": 0,
"OUTBOUND_CLICK": 0,
"VIDEO_MRC_VIEW": 0,
"VIDEO_V50_WATCH_TIME": 0,
"QUARTILE_95_PERCENT_VIEW": 0,
"VIDEO_START": 0,
"CLICKTHROUGH": 0,
"PIN_CLICK": 0,
"CLICKTHROUGH_RATE": 0,
"FULL_SCREEN_PLAYTIME": 0
}
}
]
},
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
},
/* Lifetime analytics since start of account */
"reddit": {
"analytics": {
"acceptFollowers": true, // Whether the user accepts new followers
"awardeeKarma": 125, // Karma points received from Reddit awards given by others
"awarderKarma": 45, // Karma points earned from giving awards to others
"canCreateSubreddit": true, // Whether the user has permission to create new subreddits
"coins": 250, // Number of Reddit coins the user currently has
"commentKarma": 2847, // Karma points earned from comments
"created": "2023-03-15T14:22:30.000Z", // Account creation date in ISO format
"friends": 12, // Number of Reddit friends the user has added
"hasSubscribed": true, // Whether the user has subscribed to any subreddits
"hasVerifiedEmail": true, // Whether the user has verified their email address
"hideFromRobots": false, // Whether the user has opted to hide their profile from search engines
"iconImg": "https://www.redditstatic.com/avatars/default_5.png", // URL to the user's avatar image
"id": "abc123def456", // Unique Reddit user ID
"inboxCount": 3, // Number of unread messages in the user's inbox
"isEmployee": false, // Whether the user is a Reddit employee
"isGold": false, // Whether the user has Reddit Gold/Premium subscription
"isMod": false, // Whether the user is a moderator of any subreddits
"isSponsor": false, // Whether the user is a Reddit sponsor
"isSuspended": false, // Whether the user's account is currently suspended
"linkKarma": 1567, // Karma points earned from submitted links/posts
"linkedIdentities": [ // Array of external identity providers linked to the account
"https://accounts.google.com"
],
"name": "RedditUser_Example123", // Reddit username
"over18": true, // Whether the user is over 18 (affects NSFW content visibility)
"profileImageSize": null, // Dimensions of the profile image (null if using default avatar)
"profileImageUrl": "https://i.redd.it/1234567890.png", // URL to custom profile image (empty if using default avatar)
"suspensionExpiration": null, // When account suspension expires (null if not suspended)
"totalKarma": 4414, // Total karma points (sum of link karma and comment karma)
"url": "https://www.reddit.com/user/RedditUser_Example123", // Direct URL to the user's Reddit profile
"verified": true // Whether the user has a verified Reddit account
},
"lastUpdated": "2025-07-29T00:31:47.080Z",
"nextUpdate": "2025-07-29T00:42:47.080Z"
},
"snapchat": {
"analytics": [
{
"adsSubscribes": 6, // Number of new subscribers acquired through ads
"avgViewTime": 5430, // Average view time in milliseconds per viewer
"favorites": 42, // Number of times content was favorited
"interactions": 156, // Number of times users interacted with content (taps, swipes)
"lensAvgViewTime": 8720, // Average view time in milliseconds for lens content
"lensPlays": 387, // Number of times lenses were played
"lensSubscribers": 18, // Total number of lens subscribers
"lensSubscribes": 5, // New lens subscribers
"lensUniques": 342, // Unique users who viewed lenses
"lensViewTime": 2832800, // Total lens view time in milliseconds
"lensViews": 432, // Total number of lens views
"mediaId": "43548e97-edf1-44f9-984a-0a38470875bc",
"playTime": 4268700, // Total play time in milliseconds
"profilePaidViews": 176, // Profile views from paid promotions
"profileViews": 834, // Total profile views
"replies": 23, // Number of replies to snaps/stories
"savedStoryAvgViewTime": 4950, // Average view time for saved stories in milliseconds
"savedStoryFavorites": 19, // Number of favorites on saved stories
"savedStorySnapCombinedUniques": 523, // Combined unique viewers of saved stories
"savedStorySnapCombinedViews": 712, // Combined views of saved stories
"savedStorySnapPaidUniques": 142, // Unique paid viewers of saved stories
"savedStorySnapPaidViews": 178, // Number of paid views of saved stories
"savedStorySubscribes": 8, // Number of subscribes from saved stories
"savedStoryUniques": 381, // Unique viewers of saved stories
"savedStoryViewTime": 1925850, // Total view time of saved stories in milliseconds
"savedStoryViews": 534, // Total views of saved stories
"scans": 98, // Number of Snapcode scans
"screenshots": 12, // Total number of screenshots taken
"shares": 36, // Number of times content was shared
"snapCombinedUniques": 734, // Combined unique viewers across snaps
"snapCombinedViews": 912, // Combined total views across snaps
"snapPaidUniques": 203, // Unique users who viewed snaps through paid promotion
"snapPaidViews": 267, // Number of paid snap views
"snapViewTime": 3784200, // Total snap view time in milliseconds
"socialUnlocks": 54, // Number of social unlocks
"spotlightAvgViewTime": 7230, // Average view time for Spotlight content in milliseconds
"spotlightCombinedUniques": 1247, // Combined unique viewers of Spotlight content
"spotlightCombinedViews": 1672, // Combined views of Spotlight content
"spotlightFavorites": 89, // Number of favorites on Spotlight content
"spotlightPaidUniques": 324, // Unique paid viewers of Spotlight content
"spotlightPaidViews": 412, // Number of paid views of Spotlight content
"spotlightSubscribes": 17, // Number of subscribes from Spotlight content
"spotlightUniques": 923, // Unique viewers of Spotlight content
"spotlightViewTime": 6765290, // Total view time of Spotlight content in milliseconds
"spotlightViews": 1260, // Total views of Spotlight content
"storyAvgViewTime": 5180, // Average view time for stories in milliseconds
"storyFavorites": 37, // Number of favorites on stories
"storySubscribers": 63, // Total number of story subscribers
"storySubscribes": 9, // New story subscribers
"storyUniques": 532, // Unique viewers of stories
"storyViews": 687, // Total views of stories
"subscribers": 143, // Total number of subscribers
"subscribes": 12, // New subscribers in the period
"swipeDowns": 21, // Number of swipe downs
"swipeUps": 43, // Number of swipe ups
"uniqueScreenshots": 9, // Number of unique users who took screenshots
"uniqueSessions": 672, // Number of unique sessions
"unsubscribes": 3, // Number of unsubscribes in the period
"viewTime": 4568300, // Total view time in milliseconds
"viewers": 723, // Number of unique viewers
"views": 879 // Total number of views
}
],
"lastUpdated": "2025-05-21T20:24:02.787Z",
"nextUpdate": "2025-05-21T20:35:02.787Z"
},
"threads": {
"analytics": {
"biography": "Post to social media with an API for yourself or your users: Instagram, Facebook, Twitter, TikTok, YouTube, and more",
"followersCount": 123,
"id": "92732926562",
"isEligibleForGeoRestrictions": true,
"isVerified": true,
"likes": 1121,
"name": "Ayrshare",
"profileImageUrlHttps": "https://scontent.cdninstagram.com/v/t51.2885-15/357665262_1390047998231434_3968630539537390629_n.jpg?stp=dst-jpg_e35_tt6&_nc_cat=104&ccb=1-7&_nc_sid=18de74&_nc_ohc=CHlHbGcrCaAQ7kNvwFqZTDz&_nc_oc=Adkj3ocdD7bHn5qffGIB20t1C7icONIg8CS7t4Bcf6sssxxve-k94PkCEFDXGsY6ND0&_nc_zt=23&_nc_ht=scontent.cdninstagram.com&edm=AP4hL3IEAAAA&oh=00_AfTPLboqmm2iSOGMfvS29exnVLn5U8y9QPnL6cwLQIcZ9g&oe=68915ECD",
"quotes": 2,
"replies": 196,
"reposts": 3,
"username": "ayrshare",
"views": 36721
},
"lastUpdated": "2025-04-27T15:40:28.885Z",
"nextUpdate": "2025-04-27T15:51:28.885Z"
},
"tiktok": {
"analytics": {
"addressClicks": 2, // Number of clicks collected on the Address button past 60 days
"appDownloadClicks": 1, // Number of clicks collected on the App Download button past 60 days
"audienceAges": [ // Updated by TikTok every 24-48 hours. Only available for TikTok accounts with at least 100 followers.
{
"percentage": 0.125,
"age": "45-54"
},
{
"percentage": 0.053,
"age": "55+"
},
{
"percentage": 0.138,
"age": "18-24"
},
{
"percentage": 0.36,
"age": "25-34"
},
{
"percentage": 0.324,
"age": "35-44"
}
],
"audienceCountries": [ // Updated by TikTok every 24-48 hours.
{
"percentage": 0.25,
"country": "NG"
},
{
"percentage": 0.75,
"country": "US"
}
],
"audienceGenders": [ // Updated by TikTok every 24-48 hours.
{
"percentage": 0.25,
"gender": "Female"
},
{
"percentage": 0.5,
"gender": "Male"
},
{
"percentage": 0.25,
"gender": "Other"
}
],
"bio": "My tiktok account",
"bioLinkClicks": 2, // Number of clicks collected on the Bio Link button past 60 days
"commentCountPeriod": "90 days",// quarters × 90 days when date range is active (e.g., "90 days", "180 days"); "60 days" when no date filter; "partial" when the post history could not be fully retrieved
"commentCountTotal": 1807, // TikTok caps profile-level lookback at 60 days. When `quarters` is set, this is the sum of lifetime engagement on posts created within the window (not engagement that occurred within the window).
"displayName": "Me and You",
"durationAverage": "10.72", // Deprecated. Will be removed in the future. Updated every 12 hours
"emailClicks": 0, // Number of clicks collected on the Email button past 60 days
"followerCount": 34, // Current follower count
"followingCount": 39, // Current following count
"leadSubmissions": 0, // Number of leads submitted past 60 days
"isBusinessAccount": true, // Whether the account is a business account
"isVerified": false, // Whether TikTok has provided a verified badge to the account after confirming that it belongs to the user it represents
"likeCountTotal": 2, // Genuine all-time total - the only TikTok profile metric that returns true lifetime data (sourced from TikTok's `total_likes` field).
"phoneNumberClicks": 0, // Number of clicks collected on the Phone Number button past 60 days
"profileViews": 79346, // Total profile views past 60 days
"shareCountPeriod": "90 days", // quarters × 90 days when date range is active (e.g., "90 days", "180 days"); "60 days" when no date filter; "partial" when the post history could not be fully retrieved
"shareCountTotal": 4, // TikTok caps profile-level lookback at 60 days. When `quarters` is set, this is the sum of lifetime engagement on posts created within the window (not engagement that occurred within the window).
"url": "https://vm.tiktok.com/ZTRuw5kM6/",
"userImage": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/f123c4b57351e266",
"username": "@funnyone",
"videoCountTotal": 18, // Video total video count
"viewCountPeriod": "90 days", // quarters × 90 days when date range is active (e.g., "90 days", "180 days"); "60 days" when no date filter; "partial" when the post history could not be fully retrieved
"viewCountTotal": 1493 // TikTok caps profile-level lookback at 60 days. When `quarters` is set, this is the sum of lifetime engagement on posts created within the window (not engagement that occurred within the window).
},
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
},
/* Lifetime analytics since start of handle */
"twitter": {
"analytics": {
"created": "2019-11-12T15:22:57Z",
"description": "The best X account",
"displayName": "Ayrshare",
"followersCount": 11253,
"followingCount": 4242,
"id": 11842743387998232,
"isIdentityVerified": false, // Whether the account is a verified account
"likeCount": 561, // The number of likes created by this user
"listedCount": 17, // The number of lists that include this user
"location": "New York, NY",
"mostRecentTweetId": "1848848832072667321",
"name": "ayrshare",
"parody": false, // Whether the account is a parody account
"pinnedTweet": {
"text": "Happy Days!",
"id": "184884883207266733342",
"editHistoryTweetIds": [
"18488488320726673223"
]
},
"profileBannerUrl": "https://pbs.twimg.com/profile_banners/87253/15041912",
"profileImageUrl": "http://pbs.twimg.com/profile_images/1184/lvQZPpt_normal.png",
"protected": false, // User has chosen to protect their posts (private posts)
"receivesYourDM": true, // User has chosen to receive direct messages from others
"subscription": {
"subscribesToYou": false
},
"subscriptionType": "Premium", // Premium, PremiumPlus, None
"tweetCount": 2930, // The number of Tweets (including retweets) issued by the user
"url": "https://t.co/2XqeJiBy",
"username": "ayrshare",
"verified": true, // When true, indicates that the user has a verified account
"verifiedType": "blue", // "blue", "business", "none"
"website": "https://www.wondrouswaffles.com/"
},
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
},
"youtube": {
/* Lifetime analytics since start of channel */
"analytics": {
"averageViewDuration": 99, // The average length, in seconds, of video playbacks.
"averageViewPercentage": 127.06, // The average percentage of a video watched during a video playback.
"comments": 8,
"created": "2010-01-10T15:30:57Z",
"description": "This is the channel you want",
"dislikes": 1, // The number of times that users indicated that they disliked a video by giving it a negative rating.
"engagedViews": 41, // The number of views counted past the initial seconds of playback — YouTube's view-counting methodology prior to August 24, 2026.
"estimatedMinutesWatched": 72, // The number of minutes that users watched videos for the specified channel, content owner, video, or playlist.
"hiddenSubscriberCount": false, // Indicates whether the channel's subscriber count is publicly visible.
"isLinked": true,
"likes": 3, // Sum of likes across all videos on the channel within the date window determined by `quarters` (default: last 360 days). YouTube does not expose a lifetime channel-wide like counter. For a lifetime total, pass `youtube: { lifetime: true }` to get the `lifetimeLikes` field.
"lifetimeLikes": 8750, // Opt-in lifetime sum of likes across every public video on the channel. Shown here because this sample assumes the request included `youtube: { lifetime: true }` — the broad request example at the top of this page does NOT include that flag, so `lifetimeLikes` will be absent from responses to that example request. May be `null` if channel exceeds 1,000-video threshold. Cached for 24 hours per channel. Deleted and private videos are excluded.
"longUploadsStatus": "allowed",
"playlistId": "UUsp6CnxiNbUUYKZwXQ", // Primary playlist
"playlists": [ // All playlists associated with the connected channel
{
"description": "Playlist 1",
"id": "PLcLpmVOb3fDaf7",
"publishedAt": "2022-10-21T21:05:55Z",
"title": "Super Playlist",
"url": "https://www.youtube.com/playlist?list=PLcLpmVOb3fDaf7"
},
{
"description": "Playlist 2",
"id": "FLsp6CnxiNbU",
"publishedAt": "2012-02-26T19:16:55Z",
"title": "Favorites",
"url": "https://www.youtube.com/playlist?list=FLsp6CnxiNbU"
}
],
"privacyStatus": "public", // "public", "private", or "unlisted"
"shares": 34, // The number of times that users shared a video through the Share button
"subscriberCount": "67", // The number of subscribers that the channel has.
"subscribersGained": 34, // The number of times that users subscribed to a channel.
"subscribersLost": 2, // The number of times that users unsubscribed from a channel.
"thumbnailUrl": "https://yt3.ggpht.com/ytc/AMLnZu_lgyzY2EFDAP-no-rj",
"title": "The Best Channel",
"url": "https://www.youtube.com/@youtube53", // The Channel URL using the custom handle
"videoCount": "202",
"videosAddedToPlaylists": 23,
"videosRemovedFromPlaylists": 2,
"viewCount": "5", // The sum of the number of times all the videos in all formats have been viewed for a channel for all time.
"views": 44 // The number of video views that occurred in the last time period (quarters)
},
"quarters": 4,
"lastUpdated": "2022-05-09T00:52:30.530Z",
"nextUpdate": "2022-05-09T02:07:30.530Z"
}
// The top-level `warnings` array (see the "warnings" Info block above)
// would appear here as a sibling of `youtube`/`instagram`/etc. only when
// a non-fatal condition applies — e.g. `lifetimeLikes` being skipped
// because a channel exceeds the 1,000-video threshold. It is omitted in
// this success example because `lifetimeLikes: 8750` was computed
// successfully above.
}
```
```json 200: Success Daily {4, 459, 497, 555, 1474, 1716} theme={"system"}
// Available for Facebook, Google Business Profile, Instagram, Snapchat, TikTok, and YouTube
// Recommended: https://www.ayrshare.com/docs/apis/overview#compression
{
"facebook": {
"analytics": {
"about": "One of the best stie ever",
"category": "Local Business",
"emails": [ "hello@ayrshare.com" ],
"fanCount": 48519,
"followersCount": 48681,
"id": "885526541490222",
"instagramBusinessAccount": {
"id": "1784145407231999"
},
"isPublished": true,
"link": "https://www.facebook.com/885526541490222",
"location": { // Physical location details for the Page
"city": "Rome",
"country": "Italy",
"latitude": 41.853696407098,
"longitude": 12.573806071995,
"street": "Via Vincenzo",
"zip": "00174"
},
"name": "Super Site",
"overallStarRating": 0, // The average star rating of the page.
"pageFollows": {
"period": "day",
"values": [
{
"value": 3,
"endTime": "2025-08-12T07:00:00.000Z"
},
{
"value": 3,
"endTime": "2025-08-13T07:00:00.000Z"
},
],
"total": 3
},
"pageMediaView": {
"period": "day",
"values": [
{
"value": 100,
"endTime": "2025-08-12T07:00:00.000Z"
},
{
"value": 200,
"endTime": "2025-08-13T07:00:00.000Z"
}
],
"total": 300
},
"pageMediaViewIsFromAds": {
"period": "day",
"values": [
{
"value": 22,
"endTime": "2025-08-12T07:00:00.000Z"
},
{
"value": 41,
"endTime": "2025-08-13T07:00:00.000Z"
}
],
"total": 63
},
"pageMediaViewIsFromFollowers": {
"period": "day",
"values": [
{
"value": 67,
"endTime": "2025-08-12T07:00:00.000Z"
},
{
"value": 29,
"endTime": "2025-08-13T07:00:00.000Z"
}
],
"total": 96
},
"pagePostEngagements": { // The number of interactions with your posts such as likes, comments, shares and more
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30S": { // The number of times videos played for at least 30 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SAutoplayed": { // The number of times videos played for at least 30 seconds when autoplayed
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SClickToPlay": { // The number of times videos played for at least 30 seconds after clicking play
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SOrganic": { // The number of times videos played for at least 30 seconds through organic reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SPaid": { // The number of times videos played for at least 30 seconds through paid reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SRepeatViews": { // The number of times videos were replayed after playing for at least 30 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoCompleteViews30SUnique": { // The number of unique people who watched your videos for at least 30 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoRepeatViews": { // The number of times videos were replayed
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewTime": { // Total time in seconds that videos were viewed
"period": "day",
"total": 2.33,
"values": [
{
"value": 2.33,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews": { // The number of times your videos were viewed
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10S": { // The number of times videos played for at least 10 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SAutoplayed": { // The number of times videos played for at least 10 seconds when autoplayed
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SClickToPlay": { // The number of times videos played for at least 10 seconds after clicking play
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SOrganic": { // The number of times videos played for at least 10 seconds through organic reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SPaid": { // The number of times videos played for at least 10 seconds through paid reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SRepeatViews": { // The number of times videos were replayed after playing for at least 10 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViews10SUnique": { // The number of unique people who watched your videos for at least 10 seconds
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsAutoplayed": { // The number of times your videos autoplayed
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsByPaidNonPaid": { // Breakdown of video views by paid vs non-paid distribution
"period": "day",
"values": [
{
"value": {
"total": 0,
"unpaid": 0,
"paid": 0
},
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": {
"total": 0,
"unpaid": 0,
"paid": 0
},
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsByUploadedHosted": { // Breakdown of video views by uploaded vs hosted content
"period": "day",
"values": [
{
"value": {
"pageUploaded": 0,
"pageUploadedFromCrossposts": 0,
"pageUploadedFromShares": 0,
"pageHostedCrosspost": 0,
"pageHostedShare": 0,
"pageOwned": 0
},
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": {
"pageUploaded": 0,
"pageUploadedFromCrossposts": 0,
"pageUploadedFromShares": 0,
"pageHostedCrosspost": 0,
"pageHostedShare": 0,
"pageOwned": 0
},
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsClickToPlay": { // The number of times people clicked play to view your videos
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsOrganic": { // The number of times your videos were viewed through organic reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
"pageVideoViewsPaid": { // The number of times your videos were viewed through paid reach
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-11-03T07:00:00.000Z"
},
{
"value": 0,
"endTime": "2024-11-04T08:00:00.000Z"
}
]
},
// pageVideoViewsUnique was retired by Meta (June 15, 2026); see the note at the top of this page.
"phone": "+39067265341",
"reactions": { // Breakdown of reactions on your Page's content
"like": 142,
"love": 9,
"wow": 1,
"haha": 0,
"sorry": 0,
"anger": 0,
"total": 152
},
"unreadMessageCount": 0,
"username": "ayrshare",
"verified": false,
"website": "http://www.ayrshare.com"
},
"quarters": 1,
"lastUpdated": "2024-03-19T17:48:21.329Z",
"nextUpdate": "2024-03-19T17:59:21.329Z"
},
"gmb": { // Google Business Profile daily values - date range window determined by quarters parameter (90 days per quarter)
"analytics": {
"businessBookings": {
"period": "day",
"values": [
{ "value": 0, "endTime": "2026-09-05T00:00:00.000Z" },
{ "value": 0, "endTime": "2026-09-06T00:00:00.000Z" }
],
"total": 0
},
"businessConversations": { "period": "day", "values": [ ... ], "total": 0 },
"businessDirectionRequests": {
"period": "day",
"values": [
{ "value": 4, "endTime": "2026-09-05T00:00:00.000Z" },
{ "value": 7, "endTime": "2026-09-06T00:00:00.000Z" }
],
"total": 11
},
"businessFoodOrders": { "period": "day", "values": [ ... ], "total": 0 },
"businessImpressionsDesktopMaps": { "period": "day", "values": [ ... ], "total": 12 },
"businessImpressionsDesktopSearch": { "period": "day", "values": [ ... ], "total": 803 },
"businessImpressionsMobileMaps": { "period": "day", "values": [ ... ], "total": 9 },
"businessImpressionsMobileSearch": { "period": "day", "values": [ ... ], "total": 154 },
"callClicks": { "period": "day", "values": [ ... ], "total": 1 },
"websiteClicks": {
"period": "day",
"values": [
{ "value": 2, "endTime": "2026-09-05T00:00:00.000Z" }, // Days Google reports no data for are returned with "value": 0
{ "value": 0, "endTime": "2026-09-06T00:00:00.000Z" }
],
"total": 2
}
},
"quarters": 1,
"lastUpdated": "2026-09-07T10:12:04.101Z",
"nextUpdate": "2026-09-07T10:23:04.101Z"
},
"instagram": {
"analytics": {
"audienceCity": {
"KIRKLAND, WASHINGTON": 7,
"LAKE BOSWORTH, WASHINGTON": 20,
"BURLINGTON, WASHINGTON": 5,
"SEATTLE, WASHINGTON": 22,
"ARLINGTON HEIGHTS, WASHINGTON": 7,
},
"audienceCountry": {
"MM": 1,
"CA": 2,
"US": 2201,
"LK": 1
},
"audienceGenderAge": {
"F.13-17": 11,
"F.18-24": 83,
"M.13-17": 9,
"M.18-24": 85,
"M.65+": 10,
"U.55-64": 25,
"U.65+": 12
},
"biography": "Official Instagram feed for the best IG account",
"commentsCount": 552,
"followersCount": 2299,
"followsCount": 26,
"id": "17841407938064444",
"igId": 79483284444, // legacy Instagram user ID - only available when Instagram is linked via Facebook Page
"igLoginId": "84739261504829371", // Instagram native user ID - only available when Instagram is linked via direct Instagram login
"likeCount": 30808,
"mediaCount": 537,
"name": "Best IG Account",
"profilePictureUrl": "https://scontent-lga3-1.xx.fbcdn.net/v/t51.2885-15",
"reach": { // Returned only when daily=true (replaces the scalar `reachCount` from non-daily mode)
"period": "day", // Granularity of the time-series buckets
"values": [ // Per-day reach time-series
{
"value": 387,
"endTime": "2024-07-23T07:00:00.000Z"
},
{
"value": 46,
"endTime": "2024-07-24T07:00:00.000Z"
},
{
"value": 4,
"endTime": "2024-07-25T07:00:00.000Z"
}
]
},
"username": "instagram",
"website": "https://www.instagram.com"
},
"lastUpdated": "2024-07-25T16:09:33.608Z",
"nextUpdate": "2024-07-25T16:20:33.608Z"
},
"snapchat": {
"analytics": [
{
"adsSubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 0
},
"avgViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 7126
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 7003
}
],
"total": 14129
},
"favorites": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 4
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 3
}
],
"total": 7
},
"interactions": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 8
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 22
}
],
"total": 30
},
"lensAvgViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3712
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 7837
}
],
"total": 11549
},
"lensDauOverMau": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0.074
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0.042
}
],
"total": 0.116
},
"lensDauOverWau": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0.194
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0.251
}
],
"total": 0.445
},
"lensPlays": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 11
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 45
}
],
"total": 56
},
"lensSubscribers": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 206
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 207
}
],
"total": 413
},
"lensSubscribersGained": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 1
}
],
"total": 4
},
"lensSubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 5
},
"lensUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 30
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 5
}
],
"total": 35
},
"lensViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 1153
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2231
}
],
"total": 3384
},
"lensViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 29
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 25
}
],
"total": 54
},
"mediaId": "6e9193cd-5013-478e-a272-d3d0e881f53f",
"playTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 5273
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 5763
}
],
"total": 11036
},
"profilePaidViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 2
},
"profileViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 25
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 44
}
],
"total": 69
},
"replies": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 2
},
"savedStoryAvgViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 5471
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 3408
}
],
"total": 8879
},
"savedStoryFavorites": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 2
},
"savedStorySnapCombinedUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 19
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 22
}
],
"total": 41
},
"savedStorySnapCombinedViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 13
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 39
}
],
"total": 52
},
"savedStorySnapPaidUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 3
},
"savedStorySnapPaidViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 8
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 4
}
],
"total": 12
},
"savedStorySubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 4
},
"savedStoryUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 16
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 23
}
],
"total": 39
},
"savedStoryViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2287
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 4385
}
],
"total": 6672
},
"savedStoryViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 14
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 12
}
],
"total": 26
},
"scans": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 4
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 5
}
],
"total": 9
},
"screenshots": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 6
}
],
"total": 6
},
"shares": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 4
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 6
},
"snapCombinedUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 39
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 32
}
],
"total": 71
},
"snapCombinedViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 88
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 111
}
],
"total": 199
},
"snapPaidUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 15
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 7
}
],
"total": 22
},
"snapPaidViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 10
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 16
}
],
"total": 26
},
"snapViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3455
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2858
}
],
"total": 6313
},
"socialUnlocks": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 2
},
"spotlightAvgViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 8990
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 5401
}
],
"total": 14391
},
"spotlightCombinedUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 44
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 25
}
],
"total": 69
},
"spotlightCombinedViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 45
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 84
}
],
"total": 129
},
"spotlightFavorites": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 5
},
"spotlightPaidUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 9
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 4
}
],
"total": 13
},
"spotlightPaidViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 13
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 4
}
],
"total": 17
},
"spotlightSubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 0
},
"spotlightUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 33
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 18
}
],
"total": 51
},
"spotlightViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 6286
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 5193
}
],
"total": 11479
},
"spotlightViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 24
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 33
}
],
"total": 57
},
"storyAvgViewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 6740
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 6862
}
],
"total": 13602
},
"storyFavorites": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 4
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 6
},
"storySubscribers": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 546
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 547
}
],
"total": 1093
},
"storySubscribersGained": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 3
}
],
"total": 6
},
"storySubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 2
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 1
}
],
"total": 3
},
"storyUniques": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 9
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 13
}
],
"total": 22
},
"storyViews": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 18
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 25
}
],
"total": 43
},
"subscribers": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 273
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 274
}
],
"total": 547
},
"subscribersGained": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 4
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 2
}
],
"total": 6
},
"subscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 3
}
],
"total": 6
},
"swipeDowns": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 6
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 4
}
],
"total": 10
},
"swipeUps": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 5
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 11
}
],
"total": 16
},
"uniqueScreenshots": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 3
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 3
},
"uniqueSessions": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 34
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 11
}
],
"total": 45
},
"unsubscribes": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 0
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 0
}
],
"total": 0
},
"viewTime": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 11140
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 7310
}
],
"total": 18450
},
"viewers": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 42
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 53
}
],
"total": 95
},
"views": {
"period": "day",
"values": [
{
"endTime": "2026-01-21T00:00:00Z",
"value": 33
},
{
"endTime": "2026-01-22T00:00:00Z",
"value": 74
}
],
"total": 107
}
}
],
"lastUpdated": "2026-04-20T13:13:47.646Z",
"nextUpdate": "2026-04-20T13:24:47.646Z"
},
"tiktok": { // TikTok Analytics with "day" period - date range window determined by quarters parameter
"analytics": {
"addressClicks": { // Number of clicks collected on the Address button past 60 days
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"appDownloadClicks": { // Number of clicks collected on the App Download button past 60 days
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"audienceActivity": { // Hourly follower activity. Updated by TikTok every 24-48 hours after posting. Only available for TikTok accounts with at least 100 followers.
"period": "day",
"total": 14824,
"values": [
{
"value": [
{
"count": 12386,
"hour": "5"
},
{
"count": 2438,
"hour": "9"
}
],
"endTime": "2025-01-21T00:00:00.000Z"
}
]
},
"audienceAges": [ // Updated by TikTok every 24-48 hours after posting. Only available for TikTok accounts with at least 100 followers.
{
"percentage": 0.125,
"age": "45-54"
},
{
"percentage": 0.053,
"age": "55+"
},
{
"percentage": 0.138,
"age": "18-24"
},
{
"percentage": 0.36,
"age": "25-34"
},
{
"percentage": 0.324,
"age": "35-44"
}
],
"audienceCountries": [
{
"country": "SV",
"percentage": 0.003
},
{
"country": "US",
"percentage": 0.808
}
],
"audienceGenders": [
{
"percentage": 0.76,
"gender": "Female"
},
{
"percentage": 0.24,
"gender": "Male"
},
{
"percentage": 0,
"gender": "Other"
}
],
"bio": "This is the bio of the TikTok account",
"bioLinkClicks": { // Number of clicks collected on the Bio Link button past 60 days
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"commentCountTotal": { // Total comment count past 60 days
"period": "day",
"values": [
{
"value": 6,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 6,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"displayName": "ayrshare",
"emailClicks": { // Number of clicks collected on the Email button past 60 days
"period": "day",
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"followerCount": { // Daily follower count snapshots (past 60 days)
"period": "day",
"values": [
{
"value": 58, // Total followers at end of this day
"endTime": "2025-01-21T00:00:00.000Z" // End of day timestamp
},
{
"value": 92,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"leadSubmissions": { // Number of leads submitted past 60 days
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"likeCountTotal": { // Total Likes across past 60 days
"period": "day",
"total": 1161,
"values": [
{
"value": 625, // Number of likes received on this date
"endTime": "2025-01-21T00:00:00.000Z" // End of the day timestamp
},
{
"value": 536,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"phoneNumberClicks": { // Number of clicks collected on the Phone Number button past 60 days
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 0,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"profileViews": { // Total profile views past 60 days
"period": "day",
"total": 713,
"values": [
{
"value": 360,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 353,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"shareCountTotal": { // Total share count past 60 days
"period": "day",
"total": 3,
"values": [
{
"value": 2,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 1,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
},
"url": "https://vm.tiktok.com/ZT2NdEX33/",
"userImage": "https://p16-pu-sign-useast8.tiktokcdn-us.com/tos-useastc",
"username": "ayrshare",
"videoCountTotal": 121, // Total video count
"viewCountTotal": { // Total view count past 60 days
"period": "day",
"total": 17808,
"values": [
{
"value": 9324,
"endTime": "2025-01-21T00:00:00.000Z"
},
{
"value": 8484,
"endTime": "2025-01-20T00:00:00.000Z"
}
]
}
},
"lastUpdated": "2025-01-24T23:33:12.678Z",
"nextUpdate": "2025-01-24T23:44:12.678Z"
},
"youtube": { // If no analytics are available for a given day, not values will be returned
"analytics": {
"averageViewDuration": {
"period": "day",
"total": 4,
"values": [
{
"value": 4,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"averageViewPercentage": {
"period": "day",
"total": 61.99,
"values": [
{
"value": 61.99,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"comments": {
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"created": "2010-01-10T15:30:57Z",
"description": "The best place to find the best products",
"dislikes": {
"period": "day",
"total": 0,
"values": [
{
"value": 0,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"estimatedMinutesWatched": {
"period": "day",
"total": 23.12,
"values": [
{
"value": 23.12,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"handle": "@helmar1066",
"hiddenSubscriberCount": false,
"id": "UCsp6CnxiNbUU0AJtuYKZwXQ",
"isChannelMonetizationEnabled": false,
"isLinked": true,
"likes": {
"period": "day",
"total": 12,
"values": [
{
"value": 12,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"longUploadsStatus": "allowed",
"playlistId": "UUsp6CnxiNbUU0AJtuYKZwXQ",
"playlists": [
{
"description": "",
"id": "PLcLpmVOb3fDaf7Mps-5GnCxRZy_FBBEv4",
"publishedAt": "2022-10-21T21:05:55Z",
"title": "Super One",
"url": "https://www.youtube.com/playlist?list=PLcLpmVOb3fDaf7Mps-5GnCxRZy_FBBEv4",
"channelId": "UCsp6CnxiNbUU0AJtuYKZwXQ"
},
{
"description": "",
"id": "FLsp6CnxiNbUU0AJtuYKZwXQ",
"publishedAt": "2012-02-26T19:16:55Z",
"title": "Favorites",
"url": "https://www.youtube.com/playlist?list=FLsp6CnxiNbUU0AJtuYKZwXQ",
"channelId": "UCsp6CnxiNbUU0AJtuYKZwXQ"
}
],
"privacyStatus": "public",
"shares": {
"period": "day",
"total": 3,
"values": [
{
"value": 3,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"subscriberCount": 0,
"subscribersGained": {
"period": "day",
"total":80,
"values": [
{
"value": 8,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"subscribersLost": {
"period": "day",
"total": 1,
"values": [
{
"value": 1,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"thumbnailUrl": "https://yt3.ggpht.com/ytc/AIdro_lIMzJl5UC7MBEnCzuMpXzai_EvIk5Xh3ErDJ_VTBk=s88-c-k-c0x00ffffff-no-rj",
"title": "helmar1066",
"url": "https://www.youtube.com/@helmar1066",
"videoCount": 5,
"videosAddedToPlaylists": {
"period": "day",
"total": 2,
"values": [
{
"value": 2,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"videosRemovedFromPlaylists": {
"period": "day",
"total": 1,
"values": [
{
"value": 1,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
},
"viewCount": 7,
"views": {
"period": "day",
"total": 3,
"values": [
{
"value": 3,
"endTime": "2024-03-18T00:00:00.000Z"
}
]
}
},
"quarters": 1,
"lastUpdated": "2024-04-25T21:24:45.433Z",
"nextUpdate": "2024-04-25T21:35:45.433Z"
}
"status": "success"
}
```
```json 200: Backfilled Response theme={"system"}
{
"status": "success",
"twitter": {
"analytics": {
"created": "2025-12-19T14:09:06Z",
"description": "",
"displayName": "Andrew Williams",
"followersCount": 1523,
"followingCount": 412,
"tweetCount": 8721,
"likeCount": 3200,
"listedCount": 5,
"username": "andrewwil1985",
"backfilledFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
}
}
```
```json 200: Recovered Response theme={"system"}
{
"status": "success",
"twitter": {
"analytics": {
"created": "2025-12-19T14:09:06Z",
"description": "",
"displayName": "Andrew Williams",
"followersCount": 1523,
"followingCount": 412,
"tweetCount": 8721,
"likeCount": 3200,
"listedCount": 5,
"username": "andrewwil1985",
"recoveredFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
}
}
```
```json 400: Bad Request theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. /rest-api/endpoints Details: One of the platforms requested in not supported."
}
```
```json 400: Social Not Linked theme={"system"}
{
"linkedin": {
"action": "post",
"status": "error",
"code": 156,
"message": "LinkedIn is not properly linked and must be re-linked. Please go to the Social Accounts ppage and re-link LinkedIn."
},
"code": 156,
"status": "error"
}
```
```json 400: Partial Error theme={"system"}
{
// A partial error response is returned if some of the data is available but there is an error.
"youtube": {
"views": 401,
"comments": 2,
"likes": 37,
"dislikes": 2,
"estimatedMinutesWatched": 102,
"averageViewDuration": 12,
"averageViewPercentage": 32.66,
"subscribersGained": 2,
"subscribersLost": 4,
"videosAddedToPlaylists": 2,
"videosRemovedFromPlaylists": 1,
"shares": 25,
"errors": [
{
"action": "authorization",
"status": "error",
"code": 193,
"message": "An error occurred connecting to YouTube. Please try your request once more or re-linking YouTube on the social linking page if the issue persists: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/youtube",
"details": "Error getting YouTube channel and playlist analytics: Insufficient Permission"
}
],
"status": "error"
},
"lastUpdated": "2025-01-27T02:59:13.119Z",
"nextUpdate": "2025-01-27T03:10:13.119Z",
"status": "error"
}
```
# Analytics on a Post by Social ID
Source: https://www.ayrshare.com/docs/apis/analytics/social-by-id
POST /analytics/post
Get analytics for posts that did not originate via Ayrshare by sending the Social Post ID to the analytics endpoint with searchPlatformId set to true.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
> [!WARNING]
>
> `/apis/analytics/social-by-id` is this documentation page, not an API route. Send both single-post and batch requests to `POST /analytics/post` with exactly one `platforms` value and `searchPlatformId: true`: `{"id":"SOCIAL_POST_ID","platforms":["twitter"],"searchPlatformId":true}` for one post, or `{"postIds":["ID_1","ID_2"],"platforms":["twitter"],"searchPlatformId":true}` for up to 100 posts.
Retrieve analytics for posts that did not originate via Ayrshare by providing the low-level [Social Post ID](/docs/apis/overview#social-post-id).
This ID is returned in the `postIds` field of the [/post endpoint](/docs/apis/post/post).
The linked account must be the owner of the post to retrieve the analytics (exception: YouTube; see below). Support platforms: `Bluesky`, `Facebook`, `Instagram`, `LinkedIn`, `Pinterest`, `Reddit`, `Snapchat`, `Threads`, `TikTok`, `Twitter`, and `YouTube`.
The call is the same as the [Analytics on a Post endpoint](/docs/apis/analytics/post). The key
difference is you use the post id return by the social network instead of the Ayrshare ID. Also
include the `searchPlatformId: true` parameter to notify the endpoint you're searching by the
Social Post ID.
Use the [Get All Post History endpoint](/docs/apis/history/overview) to retrieve posts and IDs
originating outside of Ayrshare found in the `id` field.
Recommend to only use for posts not sent via Ayrshare. For posts sent via Ayrshare, use the
[analytics endpoint](/docs/apis/analytics/post).
Analytics on Instagram posts that were published before the user's account was converted to a
business account from a personal account have limited analytics.
If retrieving YouTube analytics for a post that doesn't belong to your channel using the social
ID method, the API will return descriptive metadata about the content while showing zeros for
all numerical metrics. Descriptive information such as title, description, tags, channel title,
privacy status, and thumbnail URLs will be correctly populated, providing context about the
video content. However, all numerical performance metrics including views, likes, comments,
shares, subscriber changes, watch time, and playlist additions will return as zero values.
When using `postIds` for X/Twitter threads, each tweet's Social Post ID must be provided
individually, thread tweets are not automatically included when querying the parent post.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
> [!NOTE]
>
> Either `id` or `postIds` must be provided. Use `id` for a single post or `postIds` for retrieving analytics on multiple posts in a single request.
**id** `string`
[Social Post ID](/docs/apis/overview#social-post-id) returned from the [/post
endpoint](/docs/apis/post/post). This is the `id` field for an individual social network found in the
`postIds` array. Either `id` or `postIds` is required.
**postIds** `array`
Array of [Social Post IDs](/docs/apis/overview#social-post-id) to retrieve analytics for multiple posts in a single request. Maximum of 100 IDs allowed (25 for Snapchat). Either `id` or `postIds` is required.
```json theme={"system"}
{
"postIds": ["1979851549871354062", "2011793803951137234"]
}
```
**platforms** `array` *required*
String array of platforms to retrieve analytics. Only one value is allowed.
Available values:
```json theme={"system"}
{
"platforms": ["bluesky", "facebook", "instagram", "linkedin", "pinterest",
"reddit", "snapchat", "threads", "tiktok", "twitter", "youtube"]
}
```
**searchPlatformId** `boolean` *required* (default: false)
Set to `true` to search by the Social Post ID.
```json Single Post theme={"system"}
{
// Facebook Social Post ID
"id": "104923907983682_108329000309742",
"platforms": [
// Select only one platform at a time:
// facebook, instagram, youtube, threads, tiktok, or twitter
"facebook"
],
"searchPlatformId": true // Required
}
```
```json Multiple Posts theme={"system"}
{
// Array of Social Post IDs (max 100)
"postIds": ["1979851549871354062", "2011793803951137234"],
"platforms": [
// Select only one platform at a time:
// facebook, instagram, youtube, threads, tiktok, or twitter
"twitter"
],
"searchPlatformId": true // Required
}
```
> [!NOTE]
>
> When cumulative metrics (e.g., likes, comments, views) are temporarily unavailable from the social network, the API automatically backfills them from stored data. Two optional fields may appear in the per-platform `analytics` object:
>
> * **`backfilledFrom`** (string, ISO 8601) — Present when one or more cumulative metrics were substituted from stored data. The timestamp indicates when the stored data was last updated.
> * **`recoveredFrom`** (string, ISO 8601) — Present when the entire analytics response was recovered from stored data due to a complete API failure. The timestamp indicates when the stored data was last updated. For Snapchat, where `analytics` is a list, `recoveredFrom` appears beside `analytics` instead.
>
> Stored data older than 4 days is considered stale and will not be used for backfill or recovery.
```json 200: Single Post Response theme={"system"}
{
/**
The response is the same as Analytics on a Post.
Please see that endpoint for details.
Note: Some metrics are not available for posts not owned by the authorized account.
For example: X does not return non-public metrics or organic metrics for Tweets not sent by the authorized user.
*/
}
```
```json 200: Multiple Posts Response theme={"system"}
{
"twitter": [
{
"id": "1313589441919827982", // Twitter Social Post ID
"postUrl": "https://www.twitter.com/myaccount/1313589441919827982",
"analytics": {
"created": "2022-09-07T22:12:10.000Z",
"entities": {
"urls": [
{
"start": 77,
"end": 96,
"url": "https://t.co/abc123",
"expandedUrl": "https://www.ayrshare.com/docs",
"displayUrl": "ayrshare.com/docs",
"unwoundUrl": "https://www.ayrshare.com/docs"
}
],
"hashtags": [
{
"start": 97,
"end": 112,
"tag": "SocialMediaAPI"
}
],
"mentions": [
{
"start": 113,
"end": 122,
"username": "ayrshare"
}
]
},
"name": "Wonder World",
"post": "Just launched our social media campaign using Ayrshare! Check out the API at https://t.co/abc123 #SocialMediaAPI @ayrshare",
"publicMetrics": {
"retweetCount": 0,
"quoteCount": 0,
"likeCount": 0,
"replyCount": 0,
"bookmarkCount": 0,
"impressionCount": 23
},
"nonPublicMetrics": {
"userProfileClicks": 1,
"engagements": 1,
"impressionCount": 5
},
"organicMetrics": {
"likeCount": 0,
"impressionCount": 5,
"replyCount": 0,
"retweetCount": 0,
"userProfileClicks": 1
},
"username": "wondrous"
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
},
{
"id": "1313589441919827999", // Twitter Social Post ID
"postUrl": "https://www.twitter.com/myaccount/1313589441919827999",
"analytics": {
"created": "2022-09-08T14:30:00.000Z",
"name": "Wonder World",
"post": "Another great day for social media automation!",
"publicMetrics": {
"retweetCount": 5,
"quoteCount": 2,
"likeCount": 15,
"replyCount": 3,
"bookmarkCount": 1,
"impressionCount": 150
},
"nonPublicMetrics": {
"userProfileClicks": 8,
"engagements": 12,
"impressionCount": 145
},
"organicMetrics": {
"likeCount": 15,
"impressionCount": 145,
"replyCount": 3,
"retweetCount": 5,
"userProfileClicks": 8
},
"username": "wondrous"
},
"lastUpdated": "2022-04-23T18:44:29.778Z",
"nextUpdate": "2022-04-23T19:19:29.778Z"
}
],
"status": "success",
"code": 200
}
```
```json 200: Backfilled Response theme={"system"}
{
"twitter": [
{
"id": "1313589441919827982",
"postUrl": "https://www.twitter.com/myaccount/1313589441919827982",
"analytics": {
"created": "2022-09-07T22:12:10.000Z",
"name": "Wonder World",
"post": "Just launched our social media campaign!",
"publicMetrics": {
"retweetCount": 5,
"quoteCount": 2,
"likeCount": 15,
"replyCount": 3,
"bookmarkCount": 1,
"impressionCount": 150
},
"username": "wondrous",
"backfilledFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
}
],
"status": "success",
"code": 200
}
```
```json 200: Recovered Response theme={"system"}
{
"twitter": [
{
"id": "1313589441919827982",
"postUrl": "https://www.twitter.com/myaccount/1313589441919827982",
"analytics": {
"created": "2022-09-07T22:12:10.000Z",
"name": "Wonder World",
"post": "Just launched our social media campaign!",
"publicMetrics": {
"retweetCount": 5,
"quoteCount": 2,
"likeCount": 15,
"replyCount": 3,
"bookmarkCount": 1,
"impressionCount": 150
},
"username": "wondrous",
"recoveredFrom": "2026-04-08T14:30:00.000Z"
},
"lastUpdated": "2026-04-09T10:15:00.000Z",
"nextUpdate": "2026-04-09T10:26:00.000Z"
}
],
"status": "success",
"code": 200
}
```
```json 400: Bad Request theme={"system"}
{
"lastUpdated": "2023-12-08T03:40:31.185Z",
"nextUpdate": "2023-12-08T03:51:31.185Z",
"youtube": {
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent, the Profile Key is included if applicable, and the post at the social network has not been deleted. If you are trying to retrieve a post originating outside of Ayrshare, please use the https://www.ayrshare.com/docs/apis/analytics/social-by-id",
"id": "dtuu-jDp4381"
}
}
```
> [!NOTE]
>
> A `code: 186` response means the Social Post ID was not found for the selected account and platform. The URL in the message links back to this documentation page; it does not identify another API endpoint. Correct the request and retry `POST /analytics/post` using one of the request shapes above.
# YouTube Playlists
Source: https://www.ayrshare.com/docs/apis/analytics/youtube-playlists
GET /analytics/youTubePlaylists
Get the playlists for the connected YouTube channel, with each playlist's views, playlist starts, estimated minutes watched, and average view duration.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/analytics/youTubePlaylists
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/analytics/youTubePlaylists", {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`,
},
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/analytics/youTubePlaylists', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace YouTubePlaylistsGETRequest_csharp
{
class YouTubePlaylists
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/analytics/youTubePlaylists";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"channelId": "UCsp6CnxiNbUU0AJtuYKZw",
"playLists": [
{
"averageTimeInPlaylist": 2.2,
"averageViewDuration": 3.1,
"description": "A great list",
"estimatedMinutesWatched": 21,
"estimatedRedMinutesWatched": 23,
"id": "PLcLpmVOb3fDaf7Mps",
"playlistStarts": 23,
"thumbNail": {
"url": "https://i.ytimg.com/vi/default.jpg",
"width": 120,
"height": 90
},
"title": "Super Playlist",
"views": 55,
"viewsPerPlaylistStart": 32
},
{
"averageTimeInPlaylist": 2,
"averageViewDuration": 4.5,
"description": "A great list 2",
"estimatedMinutesWatched": 2,
"estimatedRedMinutesWatched": 3,
"id": "FLsp6CnxiNbUU0AJtu",
"playlistStarts": 23,
"thumbNail": {
"url": "https://i.ytimg.com/vi/default.jpg",
"width": 120,
"height": 90
},
"title": "Favorites",
"views": 20,
"viewsPerPlaylistStart": 23
}
],
"lastUpdated": "2023-01-18T03:09:59.757Z",
"nextUpdate": "2023-01-18T03:42:29.757Z"
}
```
```json 400: Error getting analytics theme={"system"}
{
"action": "analytics",
"status": "error",
"code": 294,
"message": "Error getting analytics."
}
```
# Delete Auto Schedule
Source: https://www.ayrshare.com/docs/apis/auto-schedule/delete-schedule
DELETE /auto-schedule/delete
Delete an auto schedule by its title, or the default schedule if no title is given, and optionally reset the schedule's starting point to the current time.
Delete a particular auto schedule. Provide the title of the schedule or "default" is used.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**title** `string` (default: default)
The title of the schedule to delete.
This should match a schedule title that was previously created using the [Set Auto Schedule endpoint](/docs/apis/auto-schedule/set-schedule).
If no title is provided, the system will attempt to delete the "default" schedule.
**deleteLastScheduleDate** `boolean` (default: false)
Reset the schedule's starting point to the current time.
When set to `true`, this will clear the last used schedule date, causing any new posts to be scheduled starting from the current time.
Any posts that were already scheduled will remain unchanged and publish at their original times.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API Key" \
-H 'Content-Type: application/json' \
-d '{"title": "Schedule Title"}' \
-X DELETE https://api.ayrshare.com/api/auto-schedule/delete
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const title = "Schedule Title";
fetch("https://api.ayrshare.com/api/auto-schedule/delete", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({ title }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'title': 'Schedule Title'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/auto-schedule/delete',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$title
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.ayrshare.com/api/auto-schedule/delete",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE", // Set the request method to DELETE
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . $API_KEY
]
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$json = json_decode($response, true);
print_r($json);
}
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace AutoScheduleDELETERequest_csharp
{
class AutoScheduleDELETE
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/auto-schedule/delete";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"title\": \"Schedule Title\"}";
try
{
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri(url),
Content = new StringContent(json, Encoding.UTF8, "application/json")
};
HttpResponseMessage response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```javascript 200: Success theme={"system"}
{
status: "success",
title: "Schedule Title"
}
```
# List Auto Schedule
Source: https://www.ayrshare.com/docs/apis/auto-schedule/list-schedule
GET /auto-schedule/list
List your active auto schedules, including each schedule's title, times, and last scheduled date, which shows the next or most recent schedule date.
List the active auto schedules. Returns an array of schedules with titles, times, and last scheduled date. The `lastScheduleDate` timestamp is the next schedule date or the previously scheduled date if no pending posts.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/auto-schedule/list
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/auto-schedule/list", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/auto-schedule/list', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/auto-schedule/list",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $API_KEY
]
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$json = json_decode($response, true);
print_r($json);
}
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace AutoScheduleGETRequest_csharp
{
class AutoSchedule
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/auto-schedule/list";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```javascript 200: Success theme={"system"}
{
"status": "success",
"schedules": {
"Title 1": {
"lastScheduleDate": "2024-01-05T22:30:00Z",
"schedule": [
"20:03Z",
"22:34Z"
]
},
"Title 2": {
"schedule": [
"13:05Z",
"22:14Z"
],
"lastScheduleDate": "2024-01-04T22:30:00Z",
"daysOfWeek": [
2,
3,
4
]
}
}
}
```
# Auto Schedule Overview
Source: https://www.ayrshare.com/docs/apis/auto-schedule/overview
Auto schedule lets you create posting schedules with times on chosen days and queue posts into the next available time slot via the Publish Post endpoint.
Auto scheduling allows you to create pre-defined posting schedules.
You can set up multiple schedules, each with specific posting times on chosen days.
Auto schedule is distinct from the `scheduleDate` field in the [Publish Post endpoint](/docs/apis/post/post).
## Key Features of Auto Schedule
Create custom schedules, e.g., post at 9 AM, 2 PM, and 5 PM on Mondays and Wednesdays.
Posts are automatically queued in the next available time slot.
No limit on the number of schedules you can create.
Auto schedule is distinct from the `scheduleDate` field in the [Publish Post
endpoint](/docs/apis/post/post) and it is meant to automate scheduling. You should use either auto
schedule or `scheduleDate`, but not both.
## Example Auto Schedule Scenario
1. Current time: Monday, 10 AM (GMT).
2. You create an auto schedule for posting at 9 AM and 5 PM.
3. You add three posts to this schedule.
4. The system automatically schedules these posts for: Monday at 5 PM, Tuesday at 9 AM, and Tuesday at 5 PM.
> [!NOTE]
>
> Deleting a scheduled post doesn't free up its time slot. For example, if you delete the Monday 5
> PM post and add a new post to the same schedule, it will be placed in the next available slot (in
> this case, Wednesday at 9 AM).
## Publish Using Auto Schedule
The schedule may be applied with the [Publish Post endpoint](/docs/apis/post/post) using the following fields:
```json Auto Schedule theme={"system"}
"autoSchedule": {
"schedule": true, // required
"title": "Schedule Title" // optional - case-sensitive
}
```
`schedule`: (required) A Boolean value that must be set to `true` to enable auto scheduling.
`title`: (optional) A String value that specifies the name of the schedule. This `title` should
match the name of a schedule created using the [Set Auto Schedule](/docs/apis/auto-schedule/overview)
endpoint. If not provided, the default title is `default`. Note that the `title` is
case-sensitive and must be an exact match.
See your list of auto schedules by calling the [List Auto Schedules](/docs/apis/auto-schedule/list-schedule) endpoint.
Please see this article on [using the auto scheduling API](https://www.ayrshare.com/blog/understanding-the-ayrshare-auto-schedule-api-endpoint/) for more information.
## User Profile Auto Schedule
Auto schedule for a particular user profile by [adding the PROFILE\_KEY in the header](/docs/apis/overview#profile-key-format).
# Get Pending Auto Schedule Posts
Source: https://www.ayrshare.com/docs/apis/auto-schedule/pending-auto-schedule
GET /auto-schedule/pending
Retrieve every auto schedule post that is still pending and not yet published, using a GET request to /auto-schedule/pending with your API key header.
Retrieve all the pending (not yet published) auto schedule posts.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/auto-schedule/pending
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/auto-schedule/pending", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/auto-schedule/pending', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://api.ayrshare.com/api/auto-schedule/pending",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $API_KEY
]
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$json = json_decode($response, true);
print_r($json);
}
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace AutoSchedulePendingGETRequest_csharp
{
class AutoSchedulePending
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/auto-schedule/pending";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Pending Posts theme={"system"}
{
"status": "success",
"posts": [
{
"id": "Ut1fWU6XkqkMayHGnJZ",
"post": "Today is a great day!",
"platforms": ["facebook", "instagram"],
"status": "pending",
"autoScheduled": true,
"scheduleDate": "2026-11-02T14:00:00Z",
"created": "2026-10-27T09:12:44Z"
}
]
}
```
# Set Auto Schedule
Source: https://www.ayrshare.com/docs/apis/auto-schedule/set-schedule
POST /auto-schedule/set
Set up an auto-post schedule with UTC times, a title, a start date, days of the week, and excluded dates so each post is sent at the next available time.
Set up an auto-post schedule by providing times to send. Post will automatically be sent at the next available time. If no more times are available today, the first available time tomorrow will be used, and so on.
> [!NOTE]
>
> If you're looking to just schedule a post for a future date, please see the `scheduleDate`
> parameter of [/post](/docs/apis/post/post)
Use the auto-schedule by setting the /post autoSchedule parameter to `true` and the `title` if you want to use a particular schedule. Example, set the times to UTC time 13:05Z and 20:14Z and `autoSchedule: true` in the post. The post will be scheduled at the next available time of 13:05Z or 20:14Z.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**schedule** `array` *required*
Array of strings of scheduled times to auto-post. The array will be treated as a set, so duplicates removed.
Format: ISO-8601 UTC. Example: `["13:05Z", "22:14Z"]`. Not required if `setStartDate` provided.
**title** `string` (default: default)
You can create multiple different posting schedules by assigning a unique title to each one. The title must contain only alphanumeric characters - special characters like `*`, `~`, `/`, `[`, or `]` are not allowed.
If you specify the `title` in /post with autoScheduleTitle, that schedule will be used.
**setStartDate** `string`
Set a specific beginning date to start the auto schedule, provide a ISO-8601 UTC date time. E.g. `2021-07-08T12:30:00Z`. The start time will be applied to the provided "title" or will use the default title if one isn't provided.
New posts will go out from the start date onwards. Previously scheduled posts are not affected.
**daysOfWeek** `array`
Specify which days of the week the post should be sent. Values 0-6 (Sunday - Saturday). For example `[1, 3]` will only publish posts on Mondays and Wednesdays.
**excludeDates** `array`
Exclude certain dates from auto scheduling occurring. For example `["2026-01-01"]` to exclude New Years.
Note, only posts auto scheduled after the `excludeDates` has been set will be excluded.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"schedule": ["13:05Z", "20:14Z"], "title": "Instagram Schedule"}' \
-X POST https://api.ayrshare.com/api/auto-schedule/set
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const title = "Instagram Schedule";
fetch("https://api.ayrshare.com/api/auto-schedule/set", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
schedule: ["13:05Z", "20:14Z"], // required
title: title // optional
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```php PHP theme={"system"}
["13:05Z", "20:14Z"], // required
'title' => "Instagram Schedule" // optional
];
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.ayrshare.com/api/auto-schedule/set",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"Authorization: Bearer " . $API_KEY
]
]);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$json = json_decode($response, true);
print_r($json);
}
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace AutoSchedulePOSTRequest_csharp
{
class AutoSchedule
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/auto-schedule/set";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
var payload = new
{
schedule = new[] { "13:05Z", "20:14Z" },
title = "Instagram Schedule"
};
try
{
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(payload),
System.Text.Encoding.UTF8,
"application/json"
);
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"schedule": []string{"13:05Z", "20:14Z"},
"title": "Instagram Schedule"
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/auto-schedule/set",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```javascript 200: Schedule Set theme={"system"}
{
status: "success",
message: "Auto schedule set.",
title: "Instagram Schedule",
}
```
# Create Automation
Source: https://www.ayrshare.com/docs/apis/automations/create-automation
POST /automations
Create a new automation for an Instagram account or Facebook Page that fires one or more actions, such as send_dm, when any of its triggers matches.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description, including the Beta status.
Create a new automation that fires one or more actions when any of its triggers matches. The automation activates immediately. See the [Overview](/docs/apis/automations/overview) for the full catalog of triggers, actions, template variables, and limits.
The connected Instagram account or Facebook Page is derived server-side from the caller's linked profile — **do not pass `accountId`** in the body; the request will be rejected as a validation error if you do. Pass a `profileKey` header to operate under a child profile.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**platform** `string` *required*
Target platform: `instagram` or `facebook` (the linked Facebook Page). Facebook supports the `comment_keyword`, `dm_keyword` and `dm_reaction` triggers. See [Facebook Pages](/docs/apis/automations/overview#facebook-pages).
**name** `string` *required*
Short human-readable name. Used in the dashboard and activity logs.
**description** `string`
Optional longer description.
**triggers** `array` *required*
One or more triggers (1–50). Each entry is a discriminated union on `type`. See [Overview / Triggers](/docs/apis/automations/overview#triggers) for the full catalog.
```json comment_keyword trigger theme={"system"}
{
"type": "comment_keyword",
"postId": "17895695668004550", // required — Instagram media id or Facebook post id, or "all"
"keywords": ["INFO", "LINK"] // required — ≥1 entry, case-insensitive
}
```
```json comment_keyword trigger — every post (evergreen) theme={"system"}
{
"type": "comment_keyword",
"postId": "all", // fires on any post or reel on the account
"keywords": ["INFO", "LINK"]
}
```
```json story_reply trigger theme={"system"}
{
"type": "story_reply",
"storyId": "17900000000000000" // optional — scope to one story, or "all"
}
```
```json dm_reaction trigger theme={"system"}
{
"type": "dm_reaction",
"emoji": "❤️" // optional — scope to one emoji
}
```
```json dm_keyword trigger theme={"system"}
{
"type": "dm_keyword",
"keywords": ["help", "support"] // required — ≥1 entry, case-insensitive
}
```
**actions** `array` *required*
One or more actions (1–50). Each entry is a discriminated union on `type`. See [Overview / Actions](/docs/apis/automations/overview#actions) for the full catalog. Every action also accepts an optional top-level `dedupWindowMinutes` overriding the default 7-day per-recipient dedup window (`0` to disable, max `525600`).
```json send_dm action theme={"system"}
{
"type": "send_dm",
"message": "Hey {{recipient_username}}, here is the link you wanted: https://example.com"
}
```
```json fire_webhook action theme={"system"}
{
"type": "fire_webhook"
// POSTs to your account-level webhook URL (configured in the dashboard).
// See Overview for the JSON payload shape.
}
```
```json send_email action theme={"system"}
{
"type": "send_email",
"to": "alerts@example.com",
"subject": "New {{platform}} engagement from @{{recipient_username}}",
"message": "
"
}
```
```json Action with per-action dedup override theme={"system"}
{
"type": "send_dm",
"message": "Thanks {{recipient_username}}!",
"dedupWindowMinutes": 1440 // 24h instead of the 7-day default
}
```
> [!NOTE]
>
> **Platform compatibility.** Each trigger and action declares which platforms it supports. The validator rejects the request when an entry's `type` is not supported on the requested `platform`. All triggers support Instagram; `comment_keyword`, `dm_keyword` and `dm_reaction` also support Facebook. `send_dm` supports both, `fire_webhook` and `send_email` are platform-agnostic.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"platform": "instagram",
"name": "Spring promo",
"triggers": [
{
"type": "comment_keyword",
"postId": "17895695668004550",
"keywords": ["INFO", "LINK"]
}
],
"actions": [
{
"type": "send_dm",
"message": "Hey {{recipient_username}}, here is the link you wanted: https://example.com"
}
]
}' \
-X POST https://api.ayrshare.com/api/automations
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/automations", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
platform: "instagram",
name: "Spring promo",
triggers: [
{
type: "comment_keyword",
postId: "17895695668004550",
keywords: ["INFO", "LINK"],
},
],
actions: [
{
type: "send_dm",
message: "Hey {{recipient_username}}, here is the link you wanted: https://example.com",
},
],
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {
"platform": "instagram",
"name": "Spring promo",
"triggers": [
{
"type": "comment_keyword",
"postId": "17895695668004550",
"keywords": ["INFO", "LINK"],
}
],
"actions": [
{
"type": "send_dm",
"message": "Hey {{recipient_username}}, here is the link you wanted: https://example.com",
}
],
}
headers = {
"Authorization": "Bearer API_KEY",
"Content-Type": "application/json",
}
r = requests.post(
"https://api.ayrshare.com/api/automations",
json=payload,
headers=headers,
)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
$data = [
"platform" => "instagram",
"name" => "Spring promo",
"triggers" => [
[
"type" => "comment_keyword",
"postId" => "17895695668004550",
"keywords" => ["INFO", "LINK"],
],
],
"actions" => [
[
"type" => "send_dm",
"message" => "Hey {{recipient_username}}, here is the link you wanted: https://example.com",
],
],
];
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "auto_9xKp2Lm4nQ"
}
```
```json 400: Validation failed — unknown template variable theme={"system"}
{
"action": "request",
"status": "error",
"code": 473,
"message": "Validation failed. Please see: https://www.ayrshare.com/docs/introduction",
"details": {
"formErrors": [],
"fieldErrors": {
"actions": ["message contains unknown template variables"]
}
}
}
```
```json 400: Validation failed — comment_keyword trigger missing keywords theme={"system"}
{
"action": "request",
"status": "error",
"code": 473,
"message": "Validation failed. Please see: https://www.ayrshare.com/docs/introduction",
"details": {
"formErrors": [],
"fieldErrors": {
"triggers": ["Invalid input: expected array, received undefined"]
}
}
}
```
```json 400: Validation failed — unrecognized field (e.g. accountId) theme={"system"}
{
"action": "request",
"status": "error",
"code": 473,
"message": "Validation failed. Please see: https://www.ayrshare.com/docs/introduction",
"details": {
"formErrors": ["Unrecognized key: \"accountId\""],
"fieldErrors": {}
}
}
```
```json 403: Plan tier required theme={"system"}
{
"action": "automation",
"status": "error",
"code": 468,
"message": "DM automations require a Business or Enterprise plan"
}
```
```json 403: Feature flag not yet enabled theme={"system"}
{
"action": "request",
"status": "error",
"code": 472,
"message": "This feature is not yet available on your account. Please contact us if you'd like early access."
}
```
```json 400: No linked social account for this platform theme={"system"}
{
"action": "automation",
"status": "error",
"code": 471,
"message": "No social account linked on the requested platform for this profile. Link the account on the Social Accounts page and try again."
}
```
```json 429: Active automation cap reached theme={"system"}
{
"action": "automation",
"status": "error",
"code": 470,
"message": "Active automation limit reached for your plan tier"
}
```
# Delete Automation
Source: https://www.ayrshare.com/docs/apis/automations/delete-automation
DELETE /automations/:id
Soft-delete an automation by ID to stop new dispatches. Activity history stays queryable, it no longer counts against your cap, and it can't be restored.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description.
Soft-deletes the automation. The master row is flagged `deleted: true, active: false`; no new dispatches occur from this point on.
Existing triggers, actions, and activity rows are **not** cascaded. The activity history remains queryable via [`GET /automations/:id/activity`](/docs/apis/automations/get-activity) for as long as the rows exist in Firestore (retention is indefinite for trace and analytics).
Soft-deleted automations no longer count against your active automation cap. They cannot be restored via the API; create a new automation instead.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The automation ID returned from `POST /automations`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X DELETE https://api.ayrshare.com/api/automations/auto_9xKp2Lm4nQ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "auto_9xKp2Lm4nQ";
fetch(`https://api.ayrshare.com/api/automations/${id}`, {
method: "DELETE",
headers: { "Authorization": `Bearer ${API_KEY}` },
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {"Authorization": "Bearer API_KEY"}
automation_id = "auto_9xKp2Lm4nQ"
r = requests.delete(
f"https://api.ayrshare.com/api/automations/{automation_id}",
headers=headers,
)
print(r.json())
```
```php PHP theme={"system"}
$id = "auto_9xKp2Lm4nQ";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations/" . $id,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "DELETE",
CURLOPT_HTTPHEADER => ["Authorization: Bearer API_KEY"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "auto_9xKp2Lm4nQ"
}
```
```json 404: Not found theme={"system"}
{
"action": "automation",
"status": "error",
"code": 469,
"message": "Automation not found"
}
```
# Get Automation Activity
Source: https://www.ayrshare.com/docs/apis/automations/get-activity
GET /automations/:id/activity
Retrieve the last 30 days of dispatch attempts for one automation, newest first, with recipient, matched trigger, per-action results, and status values.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description.
Returns every dispatch attempt for one automation, newest first. Each row records the recipient, the matched trigger, every action's result, and any error.
Rows from the last **30 days** are returned. Older rows exist in Firestore (retention is indefinite for analytics) but are excluded from this endpoint for performance.
## Activity row status
Each row has a top-level `status` plus a per-action `status` inside `actionResults[]`.
| Status | When it's recorded |
| - | - |
| `pending` | Row was just written; the worker hasn't picked it up yet |
| `in_flight` | The worker is currently dispatching |
| `sent` | At least one action was **accepted** by the platform, and none hit an auth error. For `send_dm` this means Meta accepted the message — **not** that the recipient received it (see the note below) |
| `failed` | No action was accepted, and at least one was rejected by the platform or could not run (and none hit an auth error). The reason is on `actionResults[].errorDetails` |
| `auth_error` | The account's Instagram or Facebook Page credentials or messaging permissions could not be used — for example an expired or revoked access token, a missing Facebook Page selection, or Messaging not activated for the profile. The DM was not retried. Per-recipient delivery failures are recorded as `failed`, not here |
| `rate_limited` | The daily DM cap (tier or per-profile) was hit. No DM was sent |
| `deduplicated` | This action already fired to this recipient inside its dedup window (default 7 days, per-action configurable) |
| `skipped` | The automation was suppressed before dispatch — it became inactive or was deleted between fan-out and dispatch, it has no actions configured, or the engagement that triggered it came from the automation's own account — most often a comment on your own post. In that last case no action runs: no DM (an account cannot message itself), and no `fire_webhook` or `send_email` either. An automation that opted in to [`automation.completed`](/docs/apis/automations/overview#automation-completed) still receives that event, with this status |
`pending` and `in_flight` are transient; everything else is terminal.
When an automation has more than one action, the top-level `status` is derived from the per-action results in this order: `auth_error` if any action hit an auth error, then `sent` if any action was accepted, then `deduplicated` if every action was deduplicated, then `skipped` if every action was deliberately suppressed (skipped or deduplicated), otherwise `failed`. Read `actionResults[]` for the per-action detail.
> [!NOTE]
>
> **`sent` confirms Meta accepted the message, not that the recipient received it.** This holds for Instagram DMs and for Messenger DMs from a Facebook Page: the automation records acceptance only and does not track delivery. Instagram exposes no delivery signal on any API surface, and if the recipient's **Message requests** setting blocks strangers, Meta accepts the send and silently drops it. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered).
## Per-action result fields
Each entry in `actionResults[]` describes one action's outcome:
| Field | Type | Description |
| - | - | - |
| `actionId` | string | The action's stable ID within the automation |
| `type` | string | The action type (`send_dm`, `fire_webhook`, `send_email`) |
| `status` | string | Per-action status, using the same vocabulary as the row `status` above |
| `errorDetails` | string \| `null` | Human-readable failure reason when the action did not succeed; `null` (or absent) on success. For a rejected `send_dm` this carries the mapped error message (e.g. the private-reply window is closed, or the comment already received a reply). One exception to `null` on success: a `send_dm` with `messageSource: "fallback"` is `sent` and carries the reason its callback was not used. A `fallback` that then failed to send carries the send error followed by that reason |
| `messageSource` | string | `send_dm` only. Which text the DM was built from: `template` (the static `message`), `callback` (your [`messageUrl`](/docs/apis/automations/overview#dynamic-message-content) answered and its text was used) or `fallback` (`messageUrl` was set but unusable, so `message` was used instead, with the reason on `errorDetails`). It does not say whether the DM was delivered; `status` does. Present once the DM text has been worked out, including sends that then failed. Absent when the action stopped before that (a `skipped` DM to your own account, a `deduplicated` action, or a `failed` configuration error such as a missing comment id), and for other action types |
| `config` | object (optional) | The action's configuration as it was when this fire ran, for example the `send_dm` `message` template. Stays the same if you later edit the automation, so each row shows what was configured at the time. Absent on rows written before this field was added, and when it was removed to fit the size limit (`truncated` is then `true`) |
| `sent` | object (optional) | What the action sent, or tried to send, for this fire. Recorded once the action built its message or payload, including when the send then failed: `status` says whether it went out. Absent when the action stopped before sending, for example a `skipped` result for a comment from your own account, and for `deduplicated` results. See [what `sent` contains](#what-sent-contains) |
| `truncated` | boolean (optional) | `true` when the entry was cut to fit its size limit: a long text value was shortened, or `config` and then `sent` were left out entirely. Absent otherwise |
| `completedAt` | string | ISO 8601 timestamp when the action finished |
### What `sent` contains
| Action type | `sent` fields |
| - | - |
| `send_dm` | `message`: the text the DM was sent with. That is your `message` template with its placeholders filled in for this recipient or, when `messageSource` is `callback`, the text your `messageUrl` returned. `fallbackMessage`: present only when `messageSource` is `callback`; your template, filled in, as it would have been sent without the callback |
| `send_email` | `to`, `subject` and `message`: the email as it was queued, placeholders filled in |
| `fire_webhook` | `payload`: the event fields sent to your webhook. The delivered body also carries the standard webhook fields (`action`, `created`, `hookId`, `refId`, `url`, and `timeStamp` when the webhook is signed). `hookId`: the delivery's id, present when the webhook was queued |
Long text values are cut to 4,000 characters, and `truncated` is set when that happens. Values stored under key names that look like credentials, such as `token`, `secret`, `password` or `apiKey`, are recorded as `"[redacted]"`. A `send_dm` action's `messageUrl` is always recorded as `"[redacted]"` too, because a URL can carry a secret in its path or query string.
For comment-triggered automations (`comment_keyword`), the row also carries a top-level `commentId` — the raw Instagram comment ID the DM was anchored to. It is `null` for message-driven triggers (`dm_keyword`, `story_reply`, `dm_reaction`).
Rows also carry `postId` (the post a `comment_keyword` comment was left on) and `storyId` (the story for `story_reply` and `story_comment`). Each is `null` for the other triggers, and absent on rows written before these fields were added.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The automation ID returned from `POST /automations`.
## Query Parameters
**limit** `number` (default: 25)
Page size. Default `25`, max `100`. Values outside `[1, 100]` are clamped; non-integer or `≤0` returns HTTP 400.
**next** `string`
Opaque cursor from a previous response's `meta.pagination.next`. Omit on the first request.
Forward-only — passing `?previous=` returns HTTP 400.
## Response Shape
The response wraps the result list in `activity[]` and the cursor info in `meta.pagination`:
```json theme={"system"}
{
"status": "success",
"automationId": "auto_9xKp2Lm4nQ",
"activity": [ /* …rows, newest first… */ ],
"meta": {
"pagination": {
"hasMore": true,
"limit": 25,
"next": "eyJ0aW1lIjoiMjAyNi0wNS0xMlQwOTowMDoxMS4wMDBaIiwiaWQiOiJhdXRvXzl4S3AyTG00blE6dHJnX2ExYjJjMzpjb21tZW50X3JlY2VpdmVkOjE4MDEyMzQ1Njc4OTAwMDAwIn0="
}
}
}
```
`meta.pagination.next` is a base64-encoded opaque blob — treat it as a black box and pass it back unchanged in the next request's `?next=`. `meta.pagination.hasMore` is `true` when at least one more page exists.
```bash cURL — first page theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/automations/auto_9xKp2Lm4nQ/activity?limit=25"
```
```bash cURL — next page theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/automations/auto_9xKp2Lm4nQ/activity?limit=25&next=eyJ0aW1lIjoiMjAyNi0wNS0xMlQw..."
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "auto_9xKp2Lm4nQ";
// Paginate through every page
let next;
do {
const url = new URL(`https://api.ayrshare.com/api/automations/${id}/activity`);
url.searchParams.set("limit", "25");
if (next) url.searchParams.set("next", next);
const res = await fetch(url, {
headers: { "Authorization": `Bearer ${API_KEY}` },
});
const json = await res.json();
console.log(json.activity);
next = json.meta?.pagination?.hasMore ? json.meta.pagination.next : undefined;
} while (next);
```
```python Python theme={"system"}
import requests
headers = {"Authorization": "Bearer API_KEY"}
automation_id = "auto_9xKp2Lm4nQ"
params = {"limit": 25}
while True:
r = requests.get(
f"https://api.ayrshare.com/api/automations/{automation_id}/activity",
params=params,
headers=headers,
).json()
print(r["activity"])
pagination = r.get("meta", {}).get("pagination", {})
if not pagination.get("hasMore"):
break
params["next"] = pagination["next"]
```
```php PHP theme={"system"}
$id = "auto_9xKp2Lm4nQ";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations/" . $id . "/activity?limit=25",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => ["Authorization: Bearer API_KEY"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"automationId": "auto_9xKp2Lm4nQ",
"activity": [
{
"id": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:18012345678901234",
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"triggerType": "comment_keyword",
"recipientId": "17841401234567890",
"recipientUsername": "jane_doe",
"eventId": "comment.received:18012345678901234",
"commentId": "18012345678901234",
"platform": "instagram",
"status": "sent",
"actionResults": [
{
"actionId": "act_d4e5f6",
"type": "send_dm",
"status": "sent",
"errorDetails": null,
"messageSource": "template",
"config": { "message": "Hi {{recipient_username}}, here's the link: https://example.com" },
"sent": { "message": "Hi jane_doe, here's the link: https://example.com" },
"completedAt": "2026-05-12T09:14:48.000Z"
}
],
"keyword": "LINK",
"commentText": "Send me the LINK please!",
"created": "2026-05-12T09:14:22.000Z",
"completedAt": "2026-05-12T09:14:48.000Z"
},
{
"id": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:18012345678905555",
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"triggerType": "comment_keyword",
"recipientId": "17841405555555555",
"recipientUsername": "late_commenter",
"eventId": "comment.received:18012345678905555",
"commentId": "18012345678905555",
"platform": "instagram",
"status": "failed",
"actionResults": [
{
"actionId": "act_d4e5f6",
"type": "send_dm",
"status": "failed",
"errorDetails": "Cannot send private reply: This comment is no longer eligible for a private reply.",
"completedAt": "2026-05-12T09:12:03.000Z"
}
],
"keyword": "LINK",
"commentText": "LINK please!",
"created": "2026-05-12T09:12:01.000Z",
"completedAt": "2026-05-12T09:12:03.000Z"
},
{
"id": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:18012345678900000",
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"triggerType": "comment_keyword",
"recipientId": "17841409876543210",
"recipientUsername": "repeat_user",
"eventId": "comment.received:18012345678900000",
"commentId": "18012345678900000",
"platform": "instagram",
"status": "deduplicated",
"actionResults": [],
"keyword": "LINK",
"commentText": "LINK again please",
"created": "2026-05-12T09:00:11.000Z",
"completedAt": "2026-05-12T09:00:34.000Z"
}
],
"meta": {
"pagination": {
"hasMore": true,
"limit": 25,
"next": "eyJ0aW1lIjoiMjAyNi0wNS0xMlQwOTowMDoxMS4wMDBaIiwiaWQiOiJhdXRvXzl4S3AyTG00blE6dHJnX2ExYjJjMzoxODAxMjM0NTY3ODkwMDAwMCJ9"
}
}
}
```
```json 400: Bad limit theme={"system"}
{
"status": "error",
"message": "limit must be a positive integer"
}
```
```json 400: Backward pagination not supported theme={"system"}
{
"status": "error",
"message": "'previous' pagination is not supported by this endpoint"
}
```
```json 404: Not found theme={"system"}
{
"action": "automation",
"status": "error",
"code": 469,
"message": "Automation not found"
}
```
# Get Automation
Source: https://www.ayrshare.com/docs/apis/automations/get-automation
GET /automations/:id
Fetch one automation by ID, including every attached trigger and action, its active status, and stats such as totalSent and lastTriggeredAt.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description.
Returns one automation including every attached trigger and action. Soft-deleted automations return `469` so an attacker probing for IDs gets the same response as a legitimate "not found".
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The automation ID returned from `POST /automations`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/automations/auto_9xKp2Lm4nQ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "auto_9xKp2Lm4nQ";
fetch(`https://api.ayrshare.com/api/automations/${id}`, {
method: "GET",
headers: { "Authorization": `Bearer ${API_KEY}` },
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {"Authorization": "Bearer API_KEY"}
automation_id = "auto_9xKp2Lm4nQ"
r = requests.get(
f"https://api.ayrshare.com/api/automations/{automation_id}",
headers=headers,
)
print(r.json())
```
```php PHP theme={"system"}
$id = "auto_9xKp2Lm4nQ";
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations/" . $id,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => ["Authorization: Bearer API_KEY"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "auto_9xKp2Lm4nQ",
"platform": "instagram",
"accountId": "17841400000000000",
"name": "Spring promo",
"description": "",
"active": true,
"stats": {
"totalSent": 42,
"totalFailed": 1,
"lastTriggeredAt": "2026-05-12T09:14:22.000Z",
"lastSentAt": "2026-05-12T09:14:48.000Z"
},
"created": "2026-04-30T12:00:00.000Z",
"updated": "2026-05-12T09:14:48.000Z",
"triggers": [
{
"id": "trg_a1b2c3",
"type": "comment_keyword",
"active": true,
"config": {
"postId": "17895695668004550",
"keywords": ["INFO", "LINK"]
}
}
],
"actions": [
{
"id": "act_d4e5f6",
"type": "send_dm",
"active": true,
"config": {
"message": "Hey {{recipient_username}}, here is the link you wanted: https://example.com"
}
}
]
}
```
```json 404: Not found theme={"system"}
{
"action": "automation",
"status": "error",
"code": 469,
"message": "Automation not found"
}
```
# List Automations
Source: https://www.ayrshare.com/docs/apis/automations/list-automations
GET /automations
Returns every active and inactive automation for the caller, excluding soft-deleted ones, with an optional platform filter for instagram or facebook.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description.
Returns every active and inactive automation for the caller. Soft-deleted automations are excluded.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**platform** `string`
Optional. Filter to automations targeting this platform: `instagram` or `facebook`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/automations?platform=instagram"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/automations?platform=instagram", {
method: "GET",
headers: { "Authorization": `Bearer ${API_KEY}` },
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {"Authorization": "Bearer API_KEY"}
r = requests.get(
"https://api.ayrshare.com/api/automations",
params={"platform": "instagram"},
headers=headers,
)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations?platform=instagram",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => ["Authorization: Bearer API_KEY"],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"automations": [
{
"id": "auto_9xKp2Lm4nQ",
"platform": "instagram",
"accountId": "17841400000000000",
"name": "Spring promo",
"description": "",
"active": true,
"stats": {
"totalSent": 42,
"totalFailed": 1,
"lastTriggeredAt": "2026-05-12T09:14:22.000Z",
"lastSentAt": "2026-05-12T09:14:48.000Z"
},
"created": "2026-04-30T12:00:00.000Z",
"updated": "2026-05-12T09:14:48.000Z"
}
]
}
```
```json 403: Plan tier theme={"system"}
{
"action": "automation",
"status": "error",
"code": 468,
"message": "DM automations require a Business or Enterprise plan"
}
```
# Automations API Overview
Source: https://www.ayrshare.com/docs/apis/automations/overview
Build Instagram and Facebook Page rules that send a DM, fire a webhook, or send an email on a comment, a DM or a DM reaction, or an Instagram story reply.
> [!NOTE]
>
> **Beta.** The Automations API is in beta and we are actively collecting feedback. Endpoints, payloads, and limits may change as we iterate. Please send feedback and bug reports to support so we can prioritize the right improvements.
The Automations endpoints let you define rules that automatically react to incoming Instagram engagement and to Facebook Page comments and DMs. Each automation pairs one or more **triggers** (the event that fires the rule) with one or more **actions** (what happens when it fires). A single rule can listen for multiple triggers and dispatch multiple actions — fire a webhook into your analytics pipeline AND send a DM from the same engagement.
The engine only reacts to engagement a user directs at you (a comment, story reply, DM, or reaction) — it never sends follow-triggered DMs, first-message DMs to strangers, or bulk outbound — and it inherits Ayrshare's per-account rate limits, per-recipient deduplication, and idempotent webhook ingestion.
> [!NOTE]
>
> **`sent` means Meta accepted the message, not that the recipient received it.** On Instagram, delivery is ultimately dictated by the recipient's **Message requests** setting: if they don't allow message requests from everyone, Meta returns a success response and silently drops the message, and this is invisible at every API layer. Even a delivered comment-triggered DM arrives as a **message request the recipient must accept** (unless a conversation already exists between the two accounts). See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered) for the full explanation.
## How it works
**1. Create an automation**
`POST /automations` with the triggers and actions you want. The automation activates immediately.
**2. An end user engages**
Someone comments on your post, replies to your story, sends a DM, or reacts to a DM. Meta delivers the webhook to Ayrshare.
**3. Ayrshare matches and dispatches**
A 20–60 second jitter is applied to the engagement to stay inside Instagram's anti-spam heuristics. The engine then looks up every rule matching the event, checks per-action deduplication and your daily DM cap, and runs each action.
**4. Inspect what fired**
`GET /automations/:id/activity` returns the audit log — every dispatch attempt, the per-action results, and any errors.
## Triggers
You can attach up to **50 triggers** to one automation. Each trigger is a discriminated union on the `type` field; type-specific fields live at the same level. The **Platforms** column shows where each trigger is available; see [Facebook Pages](#facebook-pages) for what Facebook needs.
| Type | Fires when | Platforms | Config |
| - | - | - | - |
| `comment_keyword` | A comment matching a keyword lands on a post or reel | `instagram`, `facebook` | `postId` (required — a post ID, or `"all"`); `keywords` (required, ≥1 entry) |
| `story_reply` | A user replies to a story via DM | `instagram` | `storyId` (optional — a story ID, or `"all"`) |
| `dm_reaction` | A user reacts to one of your DMs with an emoji | `instagram`, `facebook` | `emoji` (optional — omit to fire on any emoji) |
| `dm_keyword` | A user sends a DM whose text matches a keyword | `instagram`, `facebook` | `keywords` (required, ≥1 entry) |
Keyword matching is **case-insensitive** and whole-word. An event satisfies a keyword-filtered trigger if it contains any one of the configured keywords.
### Evergreen rules with `"all"`
Set `postId` (or `storyId`) to the literal lowercase string `"all"` to fire on **every** post, reel, or story on the connected account instead of one specific piece of content. This is what you want for a rule that should keep working as new content is published, with no API call per post:
```json theme={"system"}
{
"type": "comment_keyword",
"postId": "all",
"keywords": ["LINK", "INFO"]
}
```
The value must be exactly `"all"`. A variant such as `"ALL"` or `" all"` is rejected with a validation error rather than saved as a post ID that would never match.
`"all"` never widens beyond the connected account — a rule on one profile cannot fire on another profile's content. Your daily DM cap and the engagement-anchored send rules apply exactly as they do to a single-post rule.
> [!WARNING]
>
> Deduplication is counted per automation and recipient, not per post. On an `"all"` rule, a user who comments a keyword on two different posts within the dedup window (7 days by default) receives the DM only once; the second activity is recorded as `deduplicated`. A smaller [`dedupWindowMinutes`](#per-action-dedup-window) only shortens that window; it does not make deduplication per post. Setting it to `0` turns deduplication off, so every matching comment sends a DM, including repeat comments on the same post.
> [!NOTE]
>
> On the story triggers, omitting `storyId` behaves the same as `"all"`. Prefer `"all"` — it states the intent explicitly, and omission will be retired in a future release with advance notice on [Upcoming API Changes](/docs/whatsnew/upcoming-api-changes).
### Facebook Pages
Set `platform` to `facebook` to run an automation on the Facebook Page linked to the profile. DMs are sent from the Page through Messenger.
* **Triggers:** `comment_keyword`, `dm_keyword` and `dm_reaction`. Story triggers are Instagram-only, and a Facebook automation that uses one is rejected with a validation error.
* **Messaging:** the profile needs [Messaging](/docs/apis/messages/overview) activated for Facebook, the same as sending a Facebook DM with the Messages API. Comments on the Page reach automations only while Messaging is active.
* **Recipient:** `{{recipient_username}}` is empty, because Facebook webhooks do not include a username. On DM triggers `{{recipient_id}}` is the user's Page-scoped ID (PSID).
```json theme={"system"}
{
"platform": "facebook",
"name": "Price list on request",
"triggers": [{ "type": "dm_keyword", "keywords": ["price"] }],
"actions": [{ "type": "send_dm", "message": "Here is our price list: https://example.com/prices" }]
}
```
#### Comments on Facebook Page posts
A `comment_keyword` automation on `facebook` fires when someone comments on one of the Page's posts with a matching keyword. `send_dm` then answers with a **private reply**: a Messenger message to the commenter, linked to their comment. The commenter does not need to have messaged the Page first.
* **`postId`:** the Facebook post ID that `/post` returns in `postIds[].id` (for example `104923907983682_108329000309742`), or `"all"` for every post on the Page.
* **Replies count:** a reply to another comment fires the rule too, on the post it belongs to.
* **What does not fire:** comments the Page itself writes (including comments and `firstComment` sent through Ayrshare), and edited, hidden or deleted comments.
* **One reply per comment:** Meta accepts one private reply per comment, within 7 days of the comment. When Meta refuses one, the activity fails with a code from the [private-reply error range](/docs/errors/errors-ayrshare#automation-private-reply-comment-dm-errors).
* **Page admins get no reply:** Meta does not send a private reply to a comment written by an admin of the Page. The rule still fires, and the activity fails with Meta's reason on `errorDetails`: `Cannot Reply to Activity: This user cant reply to this activity`. To test the automation, comment from an account that does not manage the Page.
```json theme={"system"}
{
"platform": "facebook",
"name": "Send the link on request",
"triggers": [{ "type": "comment_keyword", "postId": "all", "keywords": ["LINK"] }],
"actions": [{ "type": "send_dm", "message": "Thanks for commenting! Here is the link: https://example.com" }]
}
```
## Actions
You can attach up to **50 actions** to one automation. They run sequentially; each result is recorded on the activity row.
| Type | Effect | Config |
| - | - | - |
| `send_dm` | Sends a DM (Instagram, or Messenger from your Facebook Page) to the user who triggered the rule, using a [templated message](#template-variables) or [text you generate per fire](#dynamic-message-content). | `message` (required, templated); `messageUrl` (optional); `timeoutMs` (optional) |
| `fire_webhook` | POSTs the automation context to your account-level webhook URL (configure via [`POST /hook/webhook`](/docs/apis/webhooks/register)). | `sendCompletedEvent` (optional, boolean — also send [`automation.completed`](#automation-completed)). Payload shape is fixed; see [below](#fire_webhook-payload) |
| `send_email` | Queues an email via the platform's mail pipeline. | `to` (required, email); `subject` (optional, templated); `message` (required, templated) |
### Per-action dedup window
Every action — regardless of type — additionally accepts an optional top-level `dedupWindowMinutes` field that overrides the **default 7-day** per-recipient dedup window for that action only.
* Set to `0` to **disable** dedup entirely for that action (typical for `fire_webhook` / `send_email` where the receiver expects every event).
* Capped at `525600` (one year).
```json Action with a 24h dedup override theme={"system"}
{
"type": "send_dm",
"message": "Thanks {{recipient_username}}!",
"dedupWindowMinutes": 1440
}
```
### Dynamic message content
By default, `send_dm` sends the same `message` to everyone who triggers the rule. Placeholders such as `{{recipient_username}}` are the only part that changes from person to person. Set **`messageUrl`** on the action and your own server writes the DM for each person instead: every time the rule matches, we ask your endpoint what to say, then send its answer as the DM.
**Example.** Your rule replies to anyone who comments "LINK" on a post, and `messageUrl` points at your server. When Ava comments "LINK please!":
1. We `POST` the details to your `messageUrl`: who commented (`"recipientUsername": "ava"`), what they wrote, and which post it was on.
2. Your server writes a reply for Ava, for example with a tracking link made just for her, and answers `{ "message": "Hi ava, here's your link: https://example.com/r/12345" }`.
3. We send that text to Ava as the DM, exactly as you wrote it.
If your server does not answer within `timeoutMs`, returns an error, or returns something we cannot use, we send Ava the action's regular `message` instead, so she still gets a reply.
Add it to a `send_dm` action like this:
```json send_dm with messageUrl theme={"system"}
{
"type": "send_dm",
"messageUrl": "https://yourapp.example.com/automations/message",
"timeoutMs": 3000,
"message": "Thanks for commenting! Here is the link: https://example.com"
}
```
`messageUrl` is where we ask for the text. `message` is still required and is the fallback: it is never sent to your server, and we DM it instead whenever your server does not answer in time, returns an error, or returns something we cannot use (see the warning below). `timeoutMs` is optional (default 3000, maximum 5000).
**What we send your endpoint.** A `POST` with this JSON body:
```json Callback request body theme={"system"}
{
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"trigger": "comment_keyword",
"platform": "instagram",
"recipientId": "17841401234567890",
"recipientUsername": "ava",
"keyword": "LINK",
"activityId": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:17912345678901234",
"actionId": "act_dm_1",
"eventId": "comment.received:17912345678901234",
"commentId": "17912345678901234",
"commentText": "LINK please!",
"postId": "17998765432109876",
"storyId": null,
"timestamp": "2026-05-12T09:14:22.000Z",
"timeStamp": 1778577262
}
```
| Field | Meaning |
| - | - |
| `automationId`, `triggerId`, `trigger`, `platform` | Which automation and trigger fired, as on the [`fire_webhook` payload](#fire_webhook-payload) |
| `recipientId`, `recipientUsername` | The person the DM goes to. `recipientUsername` is `null` when the trigger does not carry one (for example `dm_keyword`) |
| `keyword` | The keyword that matched, or `null` |
| `activityId` | The activity entry for this fire, so you can match it to [`GET /automations/:id/activity`](/docs/apis/automations/get-activity) |
| `actionId` | Which `send_dm` action is asking, matching `actionResults[].actionId`. Lets one endpoint serve several `send_dm` actions on the same automation |
| `eventId` | Our id for the engagement that caused the fire |
| `commentId` | The Instagram comment id for comment triggers; `null` for DM and story triggers |
| `commentText` | The text that matched: the comment, the DM, or the reaction emoji. `null` when there is none (for example `story_reply`) |
| `postId`, `storyId` | The post or story the engagement landed on, or `null` |
| `timestamp` | When we made the request, ISO 8601 |
| `timeStamp` | The same moment as a Unix time **in seconds**. Present only when the callback is signed, as on our webhooks |
If you have registered a [webhook secret](/docs/apis/webhooks/register), the body is signed with the same secret as your `automations` webhooks, in the same way: `X-Authorization-Content-SHA256` is the HMAC-SHA256 of the raw body, and `X-Authorization-Timestamp` carries the body's `timeStamp`. The verification code you already use for our webhooks works unchanged. If a secret is registered but we cannot read it at that moment, we do **not** send the callback unsigned; we send your `message` template instead.
**Where it can point.** `messageUrl` must be a public HTTPS address. A URL that resolves to a private, loopback, link-local or cloud-metadata address is refused, and redirects are not followed — a `3xx` answer counts as a failure. Either way the template is sent instead.
**What your endpoint sends back.** Return `200` with a JSON body, at most 16 KB, containing a `message` string of 1 to 1000 characters:
```json theme={"system"}
{ "message": "Hi ava — grabbed that link for you: https://example.com/r/12345" }
```
Your text is sent **verbatim**. `{{placeholder}}` substitution is not applied to it, so a literal `{{` in your generated text reaches the recipient as written.
> [!WARNING]
>
> **`message` stays required, and it is not decoration.** If your endpoint times out, returns a non-2xx, returns a body we cannot use, or has been temporarily circuit-broken after repeated failures, we send the rendered `message` template instead. That send can still fail, for example if the template renders empty (see below) or Instagram rejects it. We never retry the callback — a retry risks sending the recipient two messages.
`timeoutMs` defaults to **3000**. The maximum is **5000**: a larger value is rejected with a validation error when you create or update the automation. It covers the whole exchange, including reading your response body. Budget for it: the DM is sent after your endpoint answers, so a slow endpoint delays the message.
Check which path a fire took on [`GET /automations/:id/activity`](/docs/apis/automations/get-activity). A `send_dm` entry in `actionResults[]` carries `messageSource` once the DM text has been worked out, including when the send then failed. It is absent when the action stopped before that, for example a `skipped` DM to your own account or a `deduplicated` action:
| `messageSource` | Meaning |
| - | - |
| `template` | No `messageUrl` configured; the static `message` was used |
| `callback` | Your endpoint answered and its text was used |
| `fallback` | `messageUrl` was set but unusable; `message` was used instead |
`messageSource` says which text the DM was built from, not whether it was delivered. Read the entry's `status` for that.
A fallback on its own does not fail the action: when the template DM is delivered, the entry is `sent`, with the reason the callback was not used on `errorDetails`. If the template DM then fails too, the entry is `failed` and `errorDetails` gives the send error followed by the callback reason. Watch for it: a callback failing on every fire otherwise looks exactly like one that is working.
Keep your `message` sendable on its own. If it renders empty for a recipient, for example `{{recipient_username}}` alone on a DM trigger that carries no username, and your callback text is not used, no DM is sent and the action is `failed` with the reason on `errorDetails`.
### Webhook events
Two events are delivered to the `automations` webhook action you register with [`POST /hook/webhook`](/docs/apis/webhooks/register). Both carry a **`type`** field — switch on it before reading anything else.
| `type` | Sent when |
| - | - |
| `automation.triggered` | The rule's `fire_webhook` action runs |
| `automation.completed` | The dispatch finished, with its outcome |
> [!NOTE]
>
> `automation.completed` is off by default. Turn it on for an automation by setting `"sendCompletedEvent": true` on its `fire_webhook` action. It stays off otherwise, so a receiver built for one payload per fire keeps getting exactly one. `automation.triggered` is not a record of every match: it is sent only when `fire_webhook` itself runs, so a match that ends `rate_limited` or `deduplicated` produces `automation.completed` alone. Neither event is sent for an automation that was deactivated or deleted before the dispatch ran.
#### `automation.triggered`
```json theme={"system"}
{
"type": "automation.triggered",
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"trigger": "comment_keyword",
"platform": "instagram",
"recipientId": "17841401234567890",
"recipientUsername": "jane_doe",
"keyword": "LINK",
"activityId": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:17912345678901234",
"actionId": "act_wh_1",
"eventId": "comment.received:17912345678901234",
"commentId": "17912345678901234",
"commentText": "LINK please",
"postId": "17895695668004550",
"storyId": null,
"timestamp": "2026-05-12T09:14:22.000Z"
}
```
`activityId` is the row `GET /automations/:id/activity` returns, so you can join the two exactly rather than matching on `recipientId` + `triggerId` + `keyword` — which is ambiguous when several people comment the same keyword at once. `actionId` is the `fire_webhook` action that sent the event, and matches `actionResults[].actionId`; it tells two `fire_webhook` actions on the same rule apart.
Fields a trigger doesn't populate are `null` rather than omitted, so the shape is stable. `recipientUsername` is `null` for `dm_keyword`, `dm_reaction` and `story_reply`, and for every Facebook trigger, because Meta's payload doesn't include one. `commentId` is set only for `comment_keyword` and `story_comment`, `postId` only for `comment_keyword`, and `storyId` only for the story triggers. `story_reply` has no `keyword`. For `dm_keyword`, `commentText` is the DM text; for `dm_reaction`, `commentText` and `keyword` are both the reaction emoji.
#### `automation.completed`
Sent only when the automation opts in on its `fire_webhook` action:
```json fire_webhook action with the outcome event on theme={"system"}
{
"type": "fire_webhook",
"sendCompletedEvent": true
}
```
```json theme={"system"}
{
"type": "automation.completed",
"automationId": "auto_9xKp2Lm4nQ",
"triggerId": "trg_a1b2c3",
"trigger": "comment_keyword",
"platform": "instagram",
"recipientId": "17841401234567890",
"activityId": "auto_9xKp2Lm4nQ:trg_a1b2c3:comment.received:17912345678901234",
"eventId": "comment.received:17912345678901234",
"status": "sent",
"actionResults": [
{ "actionId": "act_dm_1", "type": "send_dm", "status": "sent", "errorDetails": null, "messageSource": "callback" },
{ "actionId": "act_wh_1", "type": "fire_webhook", "status": "sent", "errorDetails": null }
],
"errorDetails": null,
"timestamp": "2026-05-12T09:14:58.000Z"
}
```
`status` and each `actionResults[].status` use the same values as the [activity log](#activity-statuses) — `sent`, `failed`, `rate_limited`, `deduplicated`, `skipped`, `auth_error` — so in the normal case you learn how a dispatch ended without polling. Delivery of this event is best-effort, though: if the process stops between recording the outcome and queuing the event, it is not sent. The activity log is the record of truth, so if you need every outcome, reconcile against `GET /automations/:id/activity` for any `activityId` that has not reported one. A `send_dm` result also carries `messageSource`, as on the [activity log](#dynamic-message-content): `template`, `callback` or `fallback`, so you can spot a `messageUrl` that keeps falling back without polling. Match results to your configured actions by `actionId`, not by position — two actions of the same type are only distinguishable that way. `actionResults` is empty in three cases, each with the reason on the top-level `errorDetails` where there is one: `rate_limited` (your daily cap was reached), `deduplicated` (every action was inside its dedup window), and `failed` after the dispatch was retried the maximum number of times without finishing. In that last case some actions may already have run, so a DM may have gone out and `automation.triggered` may already have been sent.
Webhook delivery is at-least-once, so the same event can occasionally arrive twice, and a dispatch that is retried can send `automation.triggered` again with a new `hookId`. Never deduplicate on `hookId`. Key each event type separately, so that processing one never suppresses the other: `automation.completed` on `activityId` (each activity has one outcome), and `automation.triggered` on `activityId` plus `actionId` (one per `fire_webhook` action). The two events are delivered independently, so `automation.completed` can occasionally arrive before `automation.triggered`.
#### If no webhook is registered
`fire_webhook` posts to the `automations` action registered on the profile, falling back to your Primary Profile. There is **no** fallback to another action such as `messages`. If neither registration exists, `automation.triggered` is not silently dropped: the action is recorded on the activity row as `failed`, with the reason in `actionResults[].errorDetails`. (`automation.completed` has nowhere to go either, and is not recorded.) The dispatch's overall status then follows the usual precedence: `auth_error` if any action hit an auth error, otherwise `sent` if any action was accepted, otherwise `failed`. So the missing webhook counts toward the automation's failure stats only when nothing else on the rule sent; when another action did send (and none hit an auth error), the dispatch ends `sent` and the failed webhook shows only in `actionResults[]`.
A `fire_webhook` result of `sent` means the event was queued for delivery to your URL, not that your endpoint accepted it. Delivery is then retried on its own schedule.
## Template variables
`send_dm.message`, `send_email.subject`, and `send_email.message` support `{{placeholder}}` substitution. **Unknown placeholders are rejected at create/update time** (as a `473` validation error) so a typo never silently leaks the literal `{{foo}}` into a customer-facing message.
| Placeholder | Resolves to |
| - | - |
| `{{recipient_username}}` | The engaging user's Instagram handle, on comment triggers. Empty on DM and story-reply triggers, and always on Facebook |
| `{{recipient_id}}` | The engaging user's ID: the Instagram-scoped ID (IGSID) on Instagram, the Page-scoped ID (PSID) on Facebook |
| `{{recipient_name}}` | Reserved; resolves to empty until a future enrichment source populates it |
| `{{sender_username}}` | Reserved; resolves to empty on every platform for now |
| `{{sender_name}}` | Reserved; resolves to empty on every platform for now |
| `{{comment_text}}` | The text of the comment / DM / story reply that fired the trigger |
| `{{comment_id}}` | The platform id of the comment / message that fired |
| `{{comment_sent_at}}` | ISO 8601 timestamp of the event (when available) |
| `{{matched_keyword}}` | The keyword that matched (or the emoji string for `dm_reaction`) |
| `{{platform}}` | Platform identifier: `instagram` or `facebook` |
| `{{trigger_type}}` | Trigger type (e.g. `comment_keyword`) |
> [!NOTE]
>
> **No `sender_email` / `recipient_email`.** These are deliberately not exposed — your billing email has no legitimate place in a DM to a stranger, and Meta does not provide the recipient's email on any IG webhook. Avoiding the placeholders prevents accidental disclosure.
Example template:
```
Hey {{recipient_username}}, thanks for the comment "{{comment_text}}" — here is the link you wanted: https://example.com
```
## Rate limits and caps
| Plan | Active automations (per profile) | Daily DM cap (per account) |
| - | - | - |
| Launch & Business | 10 | 1,000 |
| Enterprise | 50 | 5,000 |
The active-automation cap is counted **per [User Profile](/docs/apis/profiles/overview)**, not per parent account. Each profile under your account gets its own Launch/Business 10 or Enterprise 50, so an account with many profiles can run that many automations on each. It counts active automations and is enforced on both `POST` (create) and `PUT` re-activation (`active: false → true`), each surfacing error code `470`. Need a higher per-profile limit? [Contact support](mailto:support@ayrshare.com) to have it raised for your account.
The **daily DM cap** applies per parent Ayrshare account, shared across all your profiles, with a per-profile sub-cap so one busy profile cannot drain the whole account's quota. When a DM cap is hit, the activity row records status `rate_limited` and no DM is sent.
The **messaging conversation limit** also applies. `send_dm` needs the Messaging add-on on the profile, and each DM it sends counts toward that profile's monthly [conversation limit](/docs/apis/messages/overview#conversation-limit-&-pricing), the same as a DM you send yourself. When the limit is reached, a `send_dm` to a new recipient is not sent and the activity row records status `failed`, with the limit message in `actionResults[].errorDetails`. DMs triggered by a comment (`comment_keyword` and `story_comment`) are the exception: they are still sent at the limit, and still counted.
Structural caps on a single automation: **1–50 triggers**, **1–50 actions**.
Instagram itself caps DMs at roughly 200/hour per account. The engine paces dispatch with a 20–60 second jitter to stay safely under this.
## Activity statuses
A row in `GET /automations/:id/activity` carries a top-level `status` plus a per-action `status` inside `actionResults[]`:
| Status | Meaning |
| - | - |
| `pending` | Just written; the worker hasn't picked it up yet |
| `in_flight` | Worker is currently dispatching |
| `sent` | At least one action was **accepted** by the platform, and none hit an auth error. For `send_dm` this means Meta accepted the message — **not** that the recipient received it (see the note below) |
| `failed` | No action was accepted, and at least one was rejected by the platform or could not run (and none hit an auth error). The reason is on `actionResults[].errorDetails` |
| `auth_error` | The account's Instagram or Facebook Page credentials or messaging permissions could not be used — for example an expired or revoked access token, a missing Facebook Page selection, or Messaging not activated for the profile. The DM was not retried. Per-recipient delivery failures are recorded as `failed`, not here |
| `rate_limited` | The daily DM cap (tier or per-profile) was hit; no DM was sent |
| `deduplicated` | This action already fired to this recipient inside its dedup window |
| `skipped` | The automation was suppressed before dispatch — it became inactive or was deleted between fan-out and dispatch, it has no actions configured, or the engagement that triggered it came from the automation's own account — most often a comment on your own post. In that last case no action runs: no DM (an account cannot message itself), and no `fire_webhook` or `send_email` either. An automation that opted in to [`automation.completed`](/docs/apis/automations/overview#automation-completed) still receives that event, with this status |
`pending` and `in_flight` are transient; everything else is terminal.
When an automation has more than one action, the top-level `status` is derived from the per-action results in this order: `auth_error` if any action hit an auth error, then `sent` if any action was accepted, then `deduplicated` if every action was deduplicated, then `skipped` if every action was deliberately suppressed (skipped or deduplicated), otherwise `failed`. Read `actionResults[]` for the per-action detail.
> [!NOTE]
>
> **`sent` is a platform-acceptance receipt, not a delivery receipt.** A `send_dm` action is marked `sent` the moment Meta accepts the message, whether it is sent from your Instagram account or through Messenger from your Facebook Page. The automation records that acceptance only; it does not track delivery. Instagram does not expose message delivery to any API, and whether the recipient actually receives it depends on their Instagram **Message requests** setting, which Ayrshare cannot read or influence. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered).
## Error codes
The API returns two shapes of error:
* **Business-rule errors** carry a numbered automation `code` (e.g. `{ "action": "automation", "code": 469, ... }`).
* **Validation errors** — any malformed request body (missing or invalid fields, unknown template variables, unrecognized keys) — are returned as a single **`473`** response with a `details` object that lists the offending fields. `details` is the validator's output (`formErrors` plus `fieldErrors`). Branch on `details`, not on a per-condition code. In `fieldErrors`, keys are the top-level request fields (`triggers`, `actions`): a problem inside a specific entry, such as a trigger missing its `keywords`, is reported under that field (e.g. `triggers`), while `formErrors` holds object-level issues such as unrecognized keys.
| Code | HTTP | Meaning |
| - | - | - |
| 468 | 403 | Business or Enterprise plan required |
| 469 | 404 | Automation not found (also returned when the caller doesn't own it) |
| 470 | 429 | Active automation cap reached for your plan tier |
| 471 | 400 | No social account linked on the requested platform for this profile |
| 472 | 403 | Feature not yet available on your account — contact us for early access |
| 473 | 400 | Validation failed (malformed request body) — inspect `details` |
> [!NOTE]
>
> **Launch plans are included.** Business Launch resolves to the Business tier, so Automations
> are available on Launch, Business, and Enterprise. `468` is only returned to plans that do
> not include Automations.
## What Meta does NOT allow
A few commonly-requested capabilities are not supported because Meta doesn't permit them on the public Instagram API:
* **Auto-DM on new followers.** Instagram does not publish a follow webhook.
* **First-message DMs to strangers.** Meta requires the recipient to have engaged first (comment, reply, DM, reaction) before a business account can message them. Every supported trigger is anchored to such an engagement — but note that being *allowed* to send is not the same as the message being *delivered*: the recipient's Instagram **Message requests** setting can still cause Meta to accept and then silently drop the message (see [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered)).
* **Bulk outbound campaigns.** Hourly DM caps and anti-abuse heuristics apply at the platform level.
## Multi-profile usage
The endpoints respect the `profileKey` header. Pass a child profile's key and the automation is created/managed under that profile. Rate limits split across profiles via a per-profile sub-cap so one chatty profile doesn't drain the parent account's quota.
## FAQ
> [accordion] Can I trigger on a new follower?
>
> No. Instagram does not publish a follow webhook, and Meta does not allow third-party apps to send a DM to a user who has not engaged first. Every supported trigger (`comment_keyword`, `story_reply`, `dm_reaction`, `dm_keyword`) is anchored to such an engagement, which is what makes the send permissible.
> [accordion] Why does an activity say `sent` but the recipient never got the DM?
>
> `sent` means Meta **accepted** the message, not that it was delivered. This is true on both Instagram and Facebook (Messenger from your Page): the automation records acceptance only and does not track delivery. On Instagram, delivery depends on the recipient's Instagram **Message requests** setting — if they don't allow message requests from everyone, Meta returns a success response and silently drops the message, with no error, webhook, or other signal on any API surface. A successful comment-triggered DM also arrives as a **message request the recipient must accept** (unless a conversation already exists). This is a permanent Instagram platform limitation. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered).
> [accordion] What happens if my access token is invalid when an automation fires?
>
> If the account's Instagram or Facebook Page credentials or messaging permissions genuinely fail — for example an expired or revoked access token, a missing Facebook Page selection, or Messaging not activated for the profile — the activity row records status `auth_error` and the DM is not retried. Resolving the account state (relinking, selecting a Page, or activating Messaging) lets the next matching engagement fire normally. This only applies to genuine credential and permission failures: if a specific DM could not be delivered for another reason — for example the recipient could not be found, or the triggering engagement came from the account that would send the DM — the row is recorded as `failed` or `skipped` with the reason on `actionResults[].errorDetails`, and relinking will not change the outcome.
> [accordion] Why is there a delay before the DM is sent?
>
> Each dispatch is scheduled 20–60 seconds after the engagement, so DM sends look organic to Instagram's anti-spam systems. The delay is applied to the engagement **before** any rule is matched, so it applies to every action on the rule, `fire_webhook` and `send_email` included — expect your webhook to arrive 20–60 seconds after the comment, not immediately. The activity row's `created` timestamp is when the trigger matched; `completedAt` is when dispatch finished.
> [accordion] Are activity rows kept forever?
>
> Activity rows are retained indefinitely for trace and analytics. The `GET /automations/:id/activity` endpoint returns rows from the last 30 days for performance. (The dedup guard uses its own per-action window — defaulting to 7 days — which is unrelated to the activity lookback.)
> [accordion] Does deleting an automation remove its activity history?
>
> No. Delete is a soft-delete: the master row is flagged `deleted`, no new dispatches occur, but historical activity rows remain readable via the activity endpoint.
## Endpoints
* [`POST /automations`](/docs/apis/automations/create-automation) — create a new automation
* [`GET /automations`](/docs/apis/automations/list-automations) — list your automations
* [`GET /automations/:id`](/docs/apis/automations/get-automation) — fetch one automation with its triggers and actions
* [`PUT /automations/:id`](/docs/apis/automations/update-automation) — partial update; pause via `active: false`
* [`DELETE /automations/:id`](/docs/apis/automations/delete-automation) — soft-delete
* [`GET /automations/:id/activity`](/docs/apis/automations/get-activity) — cursor-paginated dispatch audit log
# Update Automation
Source: https://www.ayrshare.com/docs/apis/automations/update-automation
PUT /automations/:id
Partially update an automation's name, description, active status, triggers, or actions. Sending triggers or actions replaces the existing set atomically.
> [!NOTE]
>
> **Beta.** See the [Automations Overview](/docs/apis/automations/overview) for the full feature description.
Partial update. Only the listed fields are editable; sending anything else (e.g. `platform`, `accountId`) is rejected as a validation error.
Sending `triggers` or `actions` **replaces** the existing children atomically: the old set is deleted and the new set is written in a single Firestore transaction. To change only one trigger, send the full new array.
Re-activating a previously-inactive automation (`active: true`) re-checks the active-cap. If the cap is already at the limit, the update fails with code `470` and no changes are made.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
The automation ID returned from `POST /automations`.
## Body Parameters
**name** `string`
New name.
**description** `string`
New description.
**active** `boolean`
Toggle the automation on or off. Setting this from `false` to `true` re-checks the active automation cap.
**triggers** `array`
Full replacement set of triggers (1–50). Same shape as the create endpoint.
**actions** `array`
Full replacement set of actions (1–50). Same shape as the create endpoint.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"name": "Spring promo (updated)",
"active": true,
"actions": [
{
"type": "send_dm",
"message": "Hey {{recipient_username}}! New link: https://example.com/spring"
}
]
}' \
-X PUT https://api.ayrshare.com/api/automations/auto_9xKp2Lm4nQ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "auto_9xKp2Lm4nQ";
fetch(`https://api.ayrshare.com/api/automations/${id}`, {
method: "PUT",
headers: {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Spring promo (updated)",
active: true,
actions: [
{
type: "send_dm",
message: "Hey {{recipient_username}}! New link: https://example.com/spring",
},
],
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
"Authorization": "Bearer API_KEY",
"Content-Type": "application/json",
}
automation_id = "auto_9xKp2Lm4nQ"
payload = {
"name": "Spring promo (updated)",
"active": True,
"actions": [
{
"type": "send_dm",
"message": "Hey {{recipient_username}}! New link: https://example.com/spring",
}
],
}
r = requests.put(
f"https://api.ayrshare.com/api/automations/{automation_id}",
json=payload,
headers=headers,
)
print(r.json())
```
```php PHP theme={"system"}
$id = "auto_9xKp2Lm4nQ";
$data = [
"name" => "Spring promo (updated)",
"active" => true,
"actions" => [
[
"type" => "send_dm",
"message" => "Hey {{recipient_username}}! New link: https://example.com/spring",
],
],
];
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ayrshare.com/api/automations/" . $id,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer API_KEY",
"Content-Type: application/json",
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "auto_9xKp2Lm4nQ"
}
```
```json 400: Validation failed — editing platform is not allowed theme={"system"}
{
"action": "request",
"status": "error",
"code": 473,
"message": "Validation failed. Please see: https://www.ayrshare.com/docs/introduction",
"details": {
"formErrors": ["Unrecognized key: \"platform\""],
"fieldErrors": {}
}
}
```
```json 404: Not found theme={"system"}
{
"action": "automation",
"status": "error",
"code": 469,
"message": "Automation not found"
}
```
```json 429: Cap reached on reactivation theme={"system"}
{
"action": "automation",
"status": "error",
"code": 470,
"message": "Active automation limit reached for your plan tier"
}
```
# Brand API: Public Account & Competitor Data
Source: https://www.ayrshare.com/docs/apis/brand/overview
Use the Ayrshare Brand API to pull public profile, follower, and engagement data for any social account, to track your brand or benchmark competitors.
> [!NOTE]
>
> **These endpoints have moved.** The Brand section has been reorganized under the new [Listening](/docs/apis/listen/overview) section. All existing Brand endpoints continue to work, but new features like [Keyword Search](/docs/apis/listen/keyword/search-tweets) are available only in the Listening section.
# Delete Comments
Source: https://www.ayrshare.com/docs/apis/comments/delete-comments
DELETE /comments/:id
Delete a single comment or all comments under a post sent via Ayrshare, or delete comments sent outside of Ayrshare using the Social Comment ID.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
The delete endpoint allows you to either delete comments sent via Ayrshare or comments that were sent outside of Ayrshare.
Please see the [Comments Overview](/docs/apis/comments/overview) for more information on the different ID types.
### Delete Comments Sent from Ayrshare
Delete either a single comment or all comments under a post that were sent via Ayrshare.
Please see the [Ayrshare Post ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id) and [Ayrshare Comment ID](/docs/apis/comments/overview#comments-with-ayrshare-comment-id) for more information.
Supported platforms: Bluesky, Facebook, Instagram, LinkedIn, Reddit, TikTok, X/Twitter, and YouTube.
> [!NOTE]
>
> **YouTube comments older than 28 days.** Ayrshare keeps the record of a YouTube comment it sent for 28 days, to stay within the 30-day limit in YouTube's API policies. After that, the Ayrshare ID no longer finds the comment. To delete it, use its [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id) (the YouTube comment `commentId`) with `"searchPlatformId": true`.
### Delete Comments Sent Outside of Ayrshare
Delete a comment that was not sent via Ayrshare by using the `commentId` returned for a particular social network.
This is the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id) from the social networks, not the Ayrshare ID.
Supported platforms: Bluesky, Facebook, Instagram, Threads, TikTok, X/Twitter, and YouTube.
> [!NOTE]
>
> **TikTok — own-authored comments only.** On TikTok, `DELETE /comments` only succeeds for comments authored by the authenticated TikTok account itself (for example, replies you posted via Ayrshare's [reply endpoint](/docs/apis/comments/reply-to-comment) or directly inside the TikTok app). Attempting to delete a comment authored by another user — even one left on a video you own — returns Ayrshare `code: 328`. If you need to moderate third-party comments on your own TikTok videos, contact support so we can confirm what's available for your linked account type.
For example, you get all the comments for a particular Instagram post using the [Get Comments](/docs/apis/comments/get-comments) endpoint with the `searchPlatformId` set to `true`.
```http theme={"system"}
GET https://api.ayrshare.com/api/comments/18231730279304111?platform=instagram&searchPlatformId=true
```
The returned JSON will have a `commentId` for each comment, which you can use to delete the comment. Remember to set the `searchPlatformId` to `true`.
```json {5} theme={"system"}
{
"instagram": [
{
"comment": "What an amazing comment",
"commentId": "17969247335804735",
"created": "2024-11-26T11:49:00Z",
"from": {
"id": "103038435208332",
"username": "john_smith"
},
"hidden": false,
"likeCount": 3,
"platform": "instagram",
"postId": "18231730279304333",
"username": "john_smith"
}
]
}
```
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
Delete a comments sent from Ayrshare:
Delete all comments sent via Ayrshare by providing the [Ayrshare Post ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id).
Delete a single comment by providing the [Ayrshare Comment ID](/docs/apis/comments/overview#comments-with-ayrshare-comment-id).
Delete a comment sent outside of Ayrshare:
Delete a single comment by providing the [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id) from the social network.
Must include the `searchPlatformId` set to `true`.
## Body Parameters
**platforms** `array`
Required if deleting comments sent via Ayrshare. The platforms to delete comments from.
Supported platforms: `bluesky`, `facebook`, `instagram`, `linkedin`, `reddit`, `threads`, `tiktok`, `twitter`, `youtube`.
```json Deleting comments sent via Ayrshare theme={"system"}
DELETE /comments/:id // Ayrshare Post ID or Ayrshare Comment ID
{
"platforms": ["bluesky", "facebook", "instagram", "linkedin", "reddit", "threads", "tiktok", "twitter", "youtube"]
}
```
**platform** `string`
Required if deleting using the [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id), which is the `commentId` from the
social networks.
Supported platforms: `bluesky`, `facebook`, `instagram`, `threads`, `tiktok`, `twitter`, `youtube`. Only one platform is supported at a time.
**searchPlatformId** `boolean` (default: false)
Required if deleting using the [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id), which is the `commentId` from the
social networks, set to `true`.
```json Deleting comments with Social Comment ID theme={"system"}
DELETE /comments/:id // Social Comment ID
{
"searchPlatformId": true,
// bluesky, facebook, instagram, threads, tiktok, twitter, youtube
"platform": "facebook"
}
```
**hide** `boolean` (default: false)
TikTok only. Set to `true`, together with `videoId`, to hide a TikTok comment from public viewers instead of deleting it. The success response returns `action: "hide"` and echoes the comment. Sending `hide=true` without `videoId` returns HTTP `400`.
Hidden comments remain visible to the video owner in TikTok Studio.
```json Hiding a TikTok comment theme={"system"}
DELETE /comments/:id // Social Comment ID
{
"searchPlatformId": true,
"platform": "tiktok",
"hide": true,
"videoId": "7303719953248109358"
}
```
**videoId** `string`
Required when hiding a TikTok comment (`hide=true`). The TikTok video ID the comment belongs to.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X DELETE https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ", {
method: "DELETE",
headers: {
Authorization: `Bearer ${API_KEY}`,
},
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace CommentsDELETERequest_csharp
{
class CommentsDELETE
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.DeleteAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Success Single Comment theme={"system"}
{
"status": "success",
"bluesky": {
"action": "delete",
"status": "success",
"id": "at://did:plc:d/app.bsky.feed.post/3lez", // Bluesky Social Comment ID
"comment": "This is a comment"
},
"facebook": {
"action": "delete",
"status": "success",
"id": "938010233_939392023", // Facebook Social Comment ID
"comment": "This is a comment"
},
"instagram": {
"action": "delete",
"status": "success",
"id": "18010439663043269", // Instagram Social Comment ID
"comment": "This is a comment"
},
"linkedin": {
"action": "delete",
"status": "success",
"id": "7133271664032669696", // LinkedIn Social Comment ID
"comment": "This is a comment"
},
"threads": {
"action": "delete",
"status": "success",
"id": "18064102964006231" // Threads Social Comment ID
},
"tiktok": {
"action": "delete",
"status": "success",
"commentId": "7303719953248109358", // Deprecated December 1, 2023. Use the id field instead.
"id": "7303719953248109358", // TikTok Social Comment ID
"comment": "This is a comment"
},
"twitter": {
"action": "delete",
"status": "success",
"id": "1633128546494459904", // Twitter Social Comment ID
"comment": "This is a comment"
},
"youtube": {
"action": "delete",
"status": "success",
"id": "Ugy2m5u-LS9M29Gn3hd4AaABAg", // YouTub Social Comment ID
"comment": "This is a comment"
}
}
```
```json 200: Sucesss Multiple Comments theme={"system"}
{
"status": "success",
"linkedin": [
{
"action": "delete",
"status": "success",
"id": "7090782997972410368", // LinkedIn Social Comment ID
"comment": "This is a comment"
},
{
"action": "delete",
"status": "success",
"id": "7090783025164103680", // LinkedIn Social Comment ID
"comment": "This is a comment"
}
],
"twitter": [
{
"action": "delete",
"posts": [
{
"action": "delete",
"status": "success",
"id": "1685017310942134272", // Twitter Social Comment ID
"comment": "This is a comment"
}
]
},
{
"action": "delete",
"posts": [
{
"action": "delete",
"status": "success",
"id": "1685017338184146946", // Twitter Social Comment ID
"comment": "This is a comment"
}
]
}
],
"facebook": [
{
"action": "delete",
"status": "success",
"id": "676770944469840_644047184361660", // Facebook Social Comment ID
"comment": "This is a comment"
},
{
"action": "delete",
"status": "success",
"id": "676770944469840_983782432932944", // Facebook Social Comment ID
"comment": "This is a comment"
}
]
}
```
```json 200: Success Hide TikTok Comment theme={"system"}
{
"status": "success",
"tiktok": {
"action": "hide",
"status": "success",
"id": "7303719953248109358", // TikTok Social Comment ID
"comment": "This is a comment"
}
}
```
```json 400: Comment id not found theme={"system"}
{
"action": "comments",
"status": "error",
"code": 219,
"message": "Error getting comments. Please verify the comments are still available."
}
```
# Get Comments
Source: https://www.ayrshare.com/docs/apis/comments/get-comments
GET /comments/:id
Fetch comments on a post by Ayrshare Post ID, Social Post ID, or Comment ID, with multiplatform partial-success responses across networks.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get comments for a post using the Ayrshare post ID, Social Post ID, Ayrshare Comment ID, or Social Comment ID with the Comment API.
Please see the [Comments Overview](/docs/apis/comments/overview) for more information on the different ID types.
> [!TIP]
>
> If using the Ayrshare Post ID no query parameters are required.
## Additional Comment Details
Comment data is updated every 10 minutes for all platforms, with the exception of X. Due to
restrictions imposed by the X API, the comment data for X is refreshed using an exponential
backoff strategy, meaning the intervals between updates gradually increase over time.
On X, requesting comments for a post that is itself a reply returns the replies to that post. An
X post that starts its own conversation is unchanged and still returns the replies from across
that conversation.
In the Facebook response, comment replies to replies always have the same `parent.id`.
Retrieve LinkedIn replies to comments by setting the `"commentId": true` and
`"searchPlatformId": true` query parameters and providing the [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id) in the path parameter.
Facebook and Instagram return up to the most recent 1,000 comments on a post. Please contact us
regarding higher limits on the Enterprise plan.
For a TikTok post still being processed (its `id` is `"pending"`), get-comments returns a clear
"still processing" error ([`code: 288`](/docs/errors/errors-ayrshare#tiktok-comment-errors), HTTP `400`)
rather than a generic failure. Retry once the [`tikTokPublished`
webhook](/docs/apis/post/social-networks/tiktok#tiktok-processing) fires or
[/history](/docs/apis/history/overview) shows the resolved video id.
## Multiplatform Reads & Partial Success
When you request comments with the [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id), the post may span several platforms. Ayrshare fans out one request ("leg") per platform and returns a **partial success** if some legs succeed and others fail — the healthy platforms' comment data is always returned, and each failed leg is enumerated in a top-level `errors[]` array.
**Some platforms succeed, some fail:** the response is HTTP `200` with `status: "partial"`. The healthy platform blocks are returned as usual, and a top-level `errors[]` array lists every failed leg with its `platform`, `status`, `code`, `message`, and `id`.
**Every platform fails:** the response has `status: "error"` and the full `errors[]` array. The HTTP status is mapped from the representative top-level error code. Code `485` maps to HTTP `404`; other representative codes use their own mappings.
**All platforms succeed:** the response is unchanged — HTTP `200`, `status: "success"`, and no `errors[]` key.
> [!WARNING]
>
> **Behavior change — inspect `errors[]`, don't branch on HTTP status.** Because a multiplatform read with a failing leg now returns HTTP `200` instead of collapsing the whole response to an error, integrators should always check for the presence of a top-level `errors[]` array to detect per-platform failures rather than relying on the HTTP status code alone.
### Expired or Unavailable Instagram / Facebook Story Comments
An Instagram or Facebook Story comments leg that is expired or unavailable — so its comments cannot be retrieved — surfaces in `errors[]` with code [`485`](/docs/errors/errors-ayrshare#expired-or-unavailable-story-errors). A representative Instagram message is *"Instagram Story expired or unavailable — comments/insights cannot be retrieved."* If another platform succeeds, its comments are still returned and the overall response is **HTTP `200`**. For an all-fail response, representative code `485` maps to HTTP `404`; other representative codes use their own mappings. Match on the `code` (`485`), not the exact message text.
#### Example: Partial Success Response
```json 200: Partial Success theme={"system"}
{
"facebook": [
{
"comment": "What a great comment",
"commentId": "806720068141593_1849585385469876",
"created": "2026-07-14T19:55:28Z",
"likeCount": 12,
"platform": "facebook"
}
],
"status": "partial",
"id": "Ut2fWU6XkqkMayHGnJZ7",
"lastUpdated": "2026-07-14T22:30:13.035Z",
"nextUpdate": "2026-07-14T22:41:13.035Z",
"errors": [
{
"platform": "instagram",
"status": "error",
"code": 485,
"message": "Instagram Story expired or unavailable — comments/insights cannot be retrieved.",
"id": "17895695668004550"
}
]
}
```
### X Billing and Rate-Limit Failures
A comments read for X can fail on your own X Developer account — most often because it has no API credits left, or because you have hit X's rate limit. These failures return a typed error rather than an empty comment list, so an account problem is never mistaken for a post that has no comments.
**X returns HTTP `402`** (no API credits): Ayrshare returns [`code: 416`](/docs/errors/errors-ayrshare#x-twitter-byo-key-errors) with HTTP `402`.
**X returns HTTP `429`** (rate limit): Ayrshare returns [`code: 364`](/docs/errors/errors-ayrshare#x-twitter-byo-key-errors) with HTTP `429`. When X's `429` says the account is out of credits, Ayrshare returns `code: 416` instead, because waiting will not clear it.
**Authorization failures** return `code: 272`, and any other X failure returns `code: 219` — both with HTTP `400`.
In a multiplatform read, the response has top-level `status: "partial"` and the failed X leg appears in `errors[]` with `status: "error"`, exactly as described above; a single-platform read returns the error at the top level. When X rejects the request outright, the failure is not cached, so the read is retried as soon as you request comments again.
An X post that was deleted, or that your X credentials cannot see, still returns an empty array with no error.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
[Ayrshare Post ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id), [Social Post
ID](/docs/apis/comments/overview#comments-with-social-post-id), or [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id).
## Query Parameters
**searchPlatformId** `boolean` (default: false)
If getting comments on a post published via Ayrshare and using the [Ayrshare Post
ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id), do not include this field - it
defaults to `false`. If getting comments using the [Social Post
ID](/docs/apis/comments/overview#comments-with-social-post-id) or [Social Comment
ID](/docs/apis/comments/overview#comments-with-social-comment-id), which is the ID generated by the
social networks, set to `true`.
**commentId** `boolean` (default: false)
If getting comments using the [Social Comment ID](/docs/apis/overview#social-comment-id), which is the
comment ID generated by the social networks, set to `true`.
If getting comment using the Ayrshare Post ID or Social Post ID, do not include this field - it defaults to `false`.
> [!NOTE]
>
> If using the `commentId` query parameter, you must also set `searchPlatformId` to `true`.
**platform** `string`
Required if `searchPlatformId` or `commentId` is `true`.
When to use:
If using the [Social Post ID](/docs/apis/comments/overview#comments-with-social-post-id) and
`"searchPlatformId": true` field. Supported platforms: `bluesky`, `facebook`, `instagram`,
`linkedin`, `reddit`, `threads`, `tiktok`, `twitter`, `youtube`.
If using the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id) and
`"searchPlatformId": true` and `"commentId": true` fields. Supported platforms: `facebook`,
`instagram`, `linkedin`.
If using the [Ayrshare Post ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id), do not
include this field - comments will be returned from all platforms where the original post was
published.
## Comment GET Request Examples
```json With Ayrshare Post ID theme={"system"}
// Ayrshare Post ID
GET /comments/:id
```
```json With Social Post ID theme={"system"}
// Social Post ID
// Platforms: facebook, instagram, linkedin, tiktok, twitter, youtube
GET /comments/:id?searchPlatformId=true&platform=facebook
```
```json With Social Comment ID theme={"system"}
// Social Comment ID
// Platforms: facebook, instagram, linkedin
GET /comments/:id?commentId=true&searchPlatformId=true&platform=facebook
```
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ",
{
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`,
},
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace CommentsGETRequest_csharp
{
class CommentsGET
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/comments/Ut1fWU6XkqkMayHGnJZ";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Success {2, 19, 60, 99, 141, 156, 194, 229, 280} theme={"system"}
{
"bluesky": [
{
"comment": "Nice one this is",
"commentId": "at://did:plc:62musrcyanhro2lydyhlw7ci/app.bsky.feed.post/3lf4b4j3dqy2i",
"created": "2025-01-06T23:12:58.950Z",
"displayName": "Ayrshare",
"likeCount": 0,
"platform": "bluesky",
"profileImageUrl": "https://cdn.bsky.app/img/avatar/plain/did:plc:62musrcyanhro2lydyhlw7ci/bafkreiegtpqhpwgu6tww2ejsdil4ew3blmt6jh3wlq6zhanxo3wj2ib4du@jpeg",
"quoteCount": 0,
"replies": [],
"replyCount": 0,
"replyTo": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lez7fwx45723",
"repostCount": 0,
"userName": "ayrshare.com"
}
],
"facebook": [
{ // The most recent 1,000 comments retrieved.
"comment": "What a great comment",
"commentId": "806720068141593_1849585385469876", // Facebook Social Comment ID
"commentCount": 1, // The number of replies to the comment
"commentUrl": "https://www.facebook.com/106148652329/posts/184958538876/", // URL to the comment
"created": "2024-02-27T19:55:28Z",
"from": { // If available. Facebook determines availability based on the user's security profile and settings.
"name": "John Smith",
"id": "5075334022535844"
},
"likeCount": 342,
"platform": "facebook",
"userLikes": false,
"replies": [ // Facebook replies to replies have the same parent id regardless of nesting.
{
"comment": "Nice Comment 2",
"commentId": "806720068141593_320599703877348",
"commentCount": 0,
"company": true,
"created": "2024-02-27T19:55:40Z",
"from": {
"name": "Sue Me",
"id": "5075334022535800"
},
"likeCount": 3,
"parent": {
"createdTime": "2024-02-27T19:55:28+0000",
"from": {
"name": "John Smith",
"id": "5075334022535844"
},
"message": "What a great comment",
"id": "806720068141593_1849585385469876"
},
"platform": "facebook",
"userLikes": false
}
]
}
],
"instagram": [
{ // The most recent 1,000 comments retrieved.
"comment": "Love this post.",
"commentId": "18030599567251077", // Instagram Social Comment ID
"created": "2022-10-13T00:52:47Z",
"from": { // If available. Instagram determines availability based on the user's security profile and settings.
"name": "WondrousTimes",
"id": "112775157855689"
},
"hidden": false, // Is the comment hidden
"likeCount": 1,
"parentId": "17999860012861", // The Instagram Social Post ID of the parent post. Only returned when using get by ID on a reply.
"platform": "instagram",
"postId": "18011025266302661", // The Instagram top level post ID (original post).
"replies": [
{
"comment": "@ayrshare Thanks",
"created": "2024-04-04T22:37:39Z",
"from": {
"id": "178414522127073334",
"username": "ayrshare"
},
"hidden": false,
"id": "18023114651100610", // Instagram Social Comment Id
"likeCount": 2,
"parentId": "18030599567251077",
"user": {
"id": "17841452212702234"
},
"username": "ayrshare"
}
],
"user": { // ID of Instagram user who created the comment.
"id": "112775157855689"
},
"userName": "ayrshare" // If available. Instagram determines availability based on the user's security profile and settings.
}
],
// Retrieve LinkedIn replies to comments by using the commentID with the Get Comments with Social Comment ID endpoint.
"linkedin": [
{
"comment": "New wonderful comment",
"commentId": "6961077099692445688", // LinkedIn Social Comment ID
"commentUrn": "urn:li:comment:(urn:li:activity:7141202289402236928,7141205176207503360)", // Social comment id from LinkedIn
"created": "2022-08-04T21:55:11Z",
"from": {
"name": "Sue Smith",
"id": "yb8P0Oq5l8",
"url": "https://www.linkedin.com/in/suesmith",
"description": "Co-Founder at Smiths"
},
"likeCount": 34,
"media": [
{
"type": "image",
"url": "https://media-exp1.licdn.com/dms/image/D4E2CAQH4RRMRaJSR"
}
],
"platform": "linkedin",
"profileImageUrl": "https://media-exp1.licdn.com/dms/image/C5103AQHORT70jVfK",
"userName": "suesmith"
},
{ // Comment from a company
"comment": "Love it",
"commentId": "urn:li:comment:(urn:li:activity:71860989603258818,7186100659052515329)",
"commentUrn": "urn:li:comment:(urn:li:activity:71860989603258818,7186100659052515329)",
"created": "2024-04-16T20:38:28Z",
"founded": 2020,
"from": {
"name": "Ayrshare",
"id": 22327643,
"url": "https://www.linkedin.com/company/22327643",
"description": "Social Media API"
},
"likeCount": 123,
"organizationType": "PRIVATELY_HELD",
"platform": "linkedin",
"userName": "ayrshare",
"website": "https://www.ayrshare.com"
}
],
"reddit": [
{
"comment": "Nice comment",
"commentId": "kaxeilk",
"created": "2023-11-27T03:09:06.000Z",
"from": {
"name": "suppercomment",
"id": "t2_3zaxa"
},
"commentUrl": "/r/test/comments/184szm7/reddit_post_title/kaxeilk/",
"subreddit": "test",
"ups": 1,
"isSubmitter": true
}
],
"threads": [
{
"comment": "An amazing comment!",
"commentId": "17890924860234841",
"commentUrl": "https://www.threads.com/@ayrshare/post/DI4pRHAto1",
"created": "2025-04-25T21:43:57+0000",
"hasReplies": false,
"isQuotePost": false,
"isReply": true,
"isReplyOwnedByMe": true,
"mediaType": "text_post",
"parentId": "17890643139123706",
"platform": "threads",
"postId": "17890643139123706",
"replies": [
{
"comment": "sweet",
"commentId": "17961516995911683",
"commentUrl": "https://www.threads.com/@ayrshare/post/DI4shgspzI1",
"created": "2025-04-25T22:12:22+0000",
"hasReplies": false,
"isQuotePost": false,
"isReply": true,
"isReplyOwnedByMe": true,
"mediaType": "text_post",
"parentId": "17890924860234841",
"platform": "threads",
"postId": "17890643139123706",
"replyAudience": "everyone",
"shortcode": "DI4shgspzI1",
"userName": "ayrshare"
}
],
"replyAudience": "everyone",
"shortcode": "DI4pRHAtoVu",
"userName": "ayrshare"
}
],
"tiktok": [
{
"comment": "The best comment",
"commentId": "7260964914699764523", // TikTok Social Comment ID
"created": "2023-07-28T20:12:23Z",
"displayName": "John Smith",
"liked": false, // Whether the user who posted the video has liked the comment.
"likeCount": 24,
"owner": true, // Whether the user who posted the video made the comment.
"pinned": false, // If the comment is pinned
"platform": "tiktok",
"profileImageUrl": "https://p16-sign-va.tiktokcdn.com/tos-maliva-avt-0068/f123c4b57",
"replies": [
{
"comment": "One reply",
"commentId": "72776074297628920", // TikTok Social Comment ID
"createTime": "2023-09-11 16:34:20",
"liked": false, // Whether the user who posted the video has liked the comment reply.
"likes": 0,
"owner": false, // Whether the user who posted the video made the comment reply.
"parentCommentId": "72776073973290774", // TikTok Parent Social Comment ID
"profileImageUrl": "https://p16-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/bcc6e6778x168.jpeg",
"status": "PUBLIC",
"userId": "51e0cb95a8b14f0f1020aa017",
"username": "ayrshare",
"videoId": "7260964048362310958" // TikTok Social Post ID
}
],
"status": "success",
"userId": "fc904564301a6d383fd19bd4982ba2682404876533c04741a8c3f270376cd3dd",
"username": "helmar1066",
"videoId": "7260964048362310958", // TikTok Social Post ID
"visibility": "public"
}
],
"twitter": [
{
"bookmarkCount": 2,
"comment": "@ayrshare Congrats on this",
"commentId": "1505595871831891068", // Twitter Social Comment ID
"created": "2022-05-14T21:56:14.000Z",
"description": "Eat great!",
"id": "1552209331030036480", // Twitter Social Post ID
"impressionCount": 134,
"likeCount": 341,
"name": "waffles",
"platform": "twitter",
"profileImageUrl": "https://pbs.twimg.com/profile_images/119426890464/lvQZPpt4_normal.png",
"publicMetrics": { // X public metrics of the user who posted the comment
"followersCount": 56,
"followingCount": 2242,
"tweetCount": 32,
"listedCount": 3,
"likeCount": 459,
"mediaCount": 244
},
"quoteCount": 2,
"referencedTweets": [ // ID of the tweet that was replied to
{
"type": "replied_to",
"id": "187203972440121733"
}
],
"replyCount": 3,
"replyTo": { // Who the comment was in reply to
"createdAt": "2012-01-18T21:13:15.000Z",
"description": "Social Media API",
"id": "467793290",
"location": "New York City",
"name": "Ayrshare",
"profileImageUrl": "https://pbs.twimg.com/profile_images/1851502252/ayrshare_normal.gif",
"publicMetrics": { // X public metrics of the user who the comment was in reply to
"followersCount": 46,
"followingCount": 1242,
"tweetCount": 12,
"listedCount": 2,
"likeCount": 439,
"mediaCount": 144
},
"url": "https://t.co/GQgCqytA",
"username": "anotherone"
},
"threadNumber": 1, // If thread, number in sequence
"userName": "waffles"
}
],
"youtube": [
{
"channelUrl": "http://www.youtube.com/channel/sp0CnxiNbUU0AJtuYKZwXQ",
"comment": "Two comments",
"commentId": "Ugx0tiPuoUpXfrY0qvV4AaABAg", // YouTube Social Comment ID
"created": "2022-05-24T19:45:49Z",
"isPublic": true, // This setting indicates whether the comment, including all of its comments and comment replies, is visible to all YouTube users.
"likeCount": 34,
"platform": "youtube",
"profileImageUrl": "https://yt3.ggpht.com/ytc/AKedOLT2hOZirBUGodAg31-QXaGPd9DmrkxR48UXAw=s48-c-k-c0x00ffffff-no-rj",
"replies": [
{
"comment": "amazing reply",
"commentId": "UgzJFjmrrRUr6nsRWi14AaABAg.9ssbUo6Es1n9ssbVwRn2iz",
"created": "2023-08-01T15:59:16Z",
"likeCount": 2,
"platform": "youtube",
"userName": "uman",
"profileImageUrl": "https://yt3.ggpht.com/ytc/AOPcoUDV_W7h3ZSEcFYrcEuXUPxVCTuF4NTw=s48-c-k-c0x00ffffff-no-rj",
"channelUrl": "http://www.youtube.com/channel/sp0CnxiNbUU0AJtuYKZwXQ",
"parentId": "UgzJFjmrrRUr6nsRWi14AaABAg"
}
],
"userName": "ayrshare"
},
{
"channelUrl": "http://www.youtube.com/channel/sp0CnxiNbUU0AJtuYKZwXQ",
"comment": "One comment",
"commentId": "Ugx0tiPuoUpXfrY0qvV4AaABAa", // YouTube Social Comment ID
"created": "2022-05-24T19:00:10Z",
"isPublic": true, // This setting indicates whether the comment, including all of its comments and comment replies, is visible to all YouTube users.
"likeCount": 123,
"platform": "youtube",
"profileImageUrl": "https://yt3.ggpht.com/ytc/AAKedOLT2hOZirBUGodAg31-QXaGPd9DmrkxR48UXAw=s48-c-k-c0x00ffffff-no-rj",
"userName": "ayrshare"
}
],
"status": "success",
"id": "Ut2fWU6XkqkMayHGnJZ7",
"lastUpdated": "2023-03-26T22:30:13.035Z",
"nextUpdate": "2023-03-26T22:41:13.035Z",
}
```
```javascript 404: Comments Not Found theme={"system"}
{
"action": "post",
"status": "error",
"code": 186,
"message": "Post ID not found. Please verify the top level ID returned from the /post endpoint is being sent.",
"id": "Ut2fWU6XkqkMayHGnJZ7"
}
```
# Comments API Overview
Source: https://www.ayrshare.com/docs/apis/comments/overview
Post, reply, get, and delete comments using the Ayrshare Post ID, Social Post ID, Ayrshare Comment ID, or Social Comment ID, plus how to disable comments.
The Comments endpoints allow your users to post, reply, retrieve, and delete comments on their posts.
There are four types of comment IDs: Ayrshare Post ID, Social Post ID, Ayrshare Comment ID, and Social Comment ID.
Each of these IDs is used for different purposes.
To receive new replies on connected Threads posts in real time, register a [`comments` webhook](/docs/apis/webhooks/actions#comments-action).
## Comment ID Types
### Comments with Ayrshare Post ID
The typical flow is when you publish a post via Ayrshare, you get the Ayrshare Post ID.
You can then use this [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id) to manage comments on that post.
Supported platforms: Bluesky, Facebook Pages, Instagram, LinkedIn, Reddit, Threads, TikTok, Twitter, and YouTube.
For example:
**1. Publish a Post**
Use the [/post](/docs/apis/post/post) endpoint to publish a post via Ayrshare, which can be sent to multiple social networks in a single call.
In the return is the top level [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id).
```json {12} theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"id": "106638148652329_591370753547555",
"postUrl": "https://www.facebook.com/106638148652329/posts/591370753547555",
"platform": "facebook"
}
],
"id": "E4UdUr1yRbvI7qqCSlaq", // Ayrshare Post ID
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc055",
"post": "What an amazing day",
"validate": true
}
```
**2. Post a Comment**
Use the [/comments](/docs/apis/comments/post-comment) endpoint to post a comment with the Ayrshare Post ID.
The comment will be automatically posted to every social network where the original post was published.
For example, if you published the post to Facebook and Instagram, the comment will appear on both platforms.
```http theme={"system"}
POST https://api.ayrshare.com/api/comments/:id
```
**3. Get Comments**
Use the [/comments](/docs/apis/comments/get-comments) endpoint to get the comments for the post with the Ayrshare Post ID.
This will return all comments from every social network where the post was published.
For example, if you posted to Facebook and Instagram, you'll get comments from both platforms.
```http theme={"system"}
GET https://api.ayrshare.com/api/comments/:id
```
### Comments with Social Post ID
Sometimes you may want to manage comments on a post that were not published via Ayrshare.
In this case you can use the [Social Post ID](/docs/apis/overview#social-post-id), which is the post ID assigned by the social network.
> [!NOTE]
>
> On Facebook, Instagram, and TikTok, the post must belong to your own connected account or page, for example a post you published directly in the Instagram app. These platforms do not let you get or reply to comments on other users' posts, even with the Social Post ID or Social Comment ID. On Bluesky, LinkedIn, X, and YouTube, whether you can work with comments on another user's post depends on that network's rules and the post's settings. See [Commenting on Other Users' Social Posts](/docs/help-center/technical-support/commenting-on-another-user-post).
> [!NOTE]
>
> If the post was published via Ayrshare, use [the Ayrshare Post
> ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id).
For example:
**1. Get Post History**
Start by getting the Instagram posts published outside of Ayrshare with the [Get All Post History endpoint](/docs/apis/history/history-platform).
The endpoint returns a list of posts with the social `id` of the post.
```json {13} theme={"system"}
{
"status": "success",
"posts": [
{
"mediaUrl": "https://scontent.cdninstagram.com/v/t51.2885-15/7531.jpg",
"permalink": "https://www.instagram.com/p/B5OBT3ygpfg/",
"commentsCount": 0,
"created": "2022-05-20T17:26:03Z",
"likeCount": 0,
"mediaProductType": "FEED",
"mediaType": "IMAGE",
"username": "thegoodone",
"id": "17833140557332933", // Instagram Social Post ID
"thumbnailUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51..jpg",
"post": "The #Mandalorian is on tonight instead of Friday",
"postUrl": "https://www.instagram.com/p/B5OBT3ygpfg/"
},
]
}
```
> [!NOTE]
>
> The social post `id` is also returned in the `postIds` field of the [post endpoint](/docs/apis/post/post).
**2. Get Comments on a Post**
Use the returned `id` to [get all the comments](/docs/apis/comments/get-comments) for a specific Instagram post using the social `id`, `searchPlatformId` set to `true`, and `platform` set to `instagram`.
```http theme={"system"}
GET https://api.ayrshare.com/api/comments/17833140557332933?platform=instagram&searchPlatformId=true
```
**3. Use the Comment ID to Get Comment Details**
The returned JSON from the previous step will have a `commentId` for each comment, which is the [social comment ID](/docs/apis/overview#social-comment-id).
```json {5} theme={"system"}
{
"instagram": [
{
"comment": "What an amazing comment",
"commentId": "17969247335804735", // Social Comment ID
"created": "2024-11-26T11:49:00Z",
"from": {
"id": "103038435208332",
"username": "john_smith"
},
"hidden": false,
"likeCount": 3,
"platform": "instagram",
"postId": "18231730279304333",
"username": "john_smith"
}
]
}
```
### Comments with Ayrshare Comment ID
Manage comments on a post that were published via Ayrshare by using the [Ayrshare Comment ID](/docs/apis/overview#ayrshare-comment-id).
The Ayrshare comment ID is the comment ID assigned from Ayrshare and can be found in the `commentId` field in the response when [posting a comment](/docs/apis/comments/post-comment).
This is often used if you want to get details on a particular comment published via Ayrshare.
### Comments with Social Comment ID
Manage comments on a post that were not published via Ayrshare by using the [Social Comment ID](/docs/apis/overview#social-comment-id).
The social comment ID is the comment ID from the social network, not the Ayrshare ID, and can be found in the `commentId` field in the response when [getting comments](/docs/apis/comments/get-comments).
```http theme={"system"}
GET https://api.ayrshare.com/api/comments/17969247335804735?platform=instagram&searchPlatformId=true&commentId=true
```
This is often used if you want to get details on a particular comment published outside of Ayrshare, such as LinkedIn replies to comments.
## Disable Comments
You can disable comments when publishing a post using the `disableComments` field in the [/post endpoint](/docs/apis/post/post). Comments will only be disabled for Instagram, LinkedIn, and TikTok.
You may also enable or disable comments on an already published post using the [update post](/docs/apis/post/update-post) endpoint and the `disableComments` field.
## Platform Limitations
> [!NOTE]
>
> **TikTok — `DELETE /comments` is own-authored only.** [`DELETE /comments`](/docs/apis/comments/delete-comments) on TikTok only succeeds for comments authored by the authenticated TikTok account itself (your own replies). Attempting to delete a comment authored by another user returns Ayrshare `code: 328`. For moderation of third-party comments on your own TikTok videos, contact support so we can confirm what's available for your linked account type.
```
```
# Post a Comment
Source: https://www.ayrshare.com/docs/apis/comments/post-comment
POST /comments
Add a comment to a post with an Ayrshare Post ID or Social Post ID, including supported platforms, body parameters, and @mentions in comments.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Add a comment to a post with an Ayrshare Post ID (posts sent via Ayrshare) or a Social Post ID (posts sent outside of Ayrshare) using the Comments API.
Comments added to an X Thread will be posted to the first Tweet.
Instagram Story comments are not yet supported by Meta.
See the `platforms` field below for supported platforms depending on the ID type.
> [!NOTE]
>
> Facebook, Instagram, and TikTok only let you comment on posts from your own connected account or page, for example a post you published directly in the Instagram app. On these platforms you cannot comment on other users' posts, even with the Social Post ID.
>
> On LinkedIn and X, Ayrshare does not check who owns the post. Whether a comment on another user's post succeeds depends on that network's rules and the post's reply settings. See [Commenting on Other Users' Social Posts](/docs/help-center/technical-support/commenting-on-another-user-post).
Please see the [Comments Overview](/docs/apis/comments/overview) for more information on the different ID types.
Supported platforms: Bluesky, Facebook, Instagram, LinkedIn, Reddit, TikTok, X, and YouTube.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
Ayrshare Post ID or Social Post ID from the [/post endpoint](/docs/apis/post/post).
**comment** `string` *required*
Text of the new comment to add to the post.
**searchPlatformId** `boolean` (default: false)
Set to `true` if posting a comment using the [Social Post ID](/docs/apis/comments/overview#comments-with-social-post-id), which is the post `id` from the social networks.
**platforms** `array` *required*
Specify the platforms to add comments.
If using an [Ayrshare Post ID](/docs/apis/comments/overview#comments-with-ayrshare-post-id) the supported platforms are: `bluesky`, `facebook`, `instagram`, `linkedin`, `reddit`, `tiktok`, `twitter`, `youtube`.
If no platforms are specified, the comment will be published to all connected platforms that support comments.
```json Post a Comment with the Ayrshare Post ID theme={"system"}
{
"id": "Ut1fWU6XkqkMayHGnJZ", // Ayrshare Post ID
"comment": "An amazing comment!", // required
"platforms": [
"bluesky",
"facebook",
"instagram",
"linkedin",
"reddit",
"tiktok",
"twitter",
"youtube"
]
}
```
If using a [Social Post ID](/docs/apis/comments/overview#comments-with-social-post-id) the supported platforms are -only one platform allowed at a time : `facebook`, `instagram`, `linkedin`, `tiktok`, `twitter`.
```json Post a Comment with the Social Post ID theme={"system"}
{
"id": "1288899996423983105", // Social Post ID
"comment": "An amazing comment!", // required
"searchPlatformId": true, // required
// facebook, instagram, linkedin, tiktok, twitter
"platforms": ["facebook"] // Only specify one platform
}
```
**mediaUrls** `array`
Attach an image by providing the image URL for Facebook or X/Twitter.
> [!NOTE]
>
> Only one image is supported in the `mediaUrls` field. Supported platforms are Facebook, LinkedIn, and X/Twitter.
```json Post a Comment with an Image theme={"system"}
{
"id": "Ut1fWU6XkqkMayHGnJZ", // Ayrshare Post ID
"comment": "An amazing comment!", // required
"platforms": ["facebook"], // Facebook, LinkedIn, or X/Twitter only
"mediaUrls": ["https://img.ayrshare.com/gb.jpg"]
}
```
## @Mentions in Comments
You can include @mentions directly in the `comment` text string. There is no separate parameter for mentions. Simply add `@handle` in your comment text.
```json Comment with @Mention theme={"system"}
{
"id": "Ut1fWU6XkqkMayHGnJZ",
"comment": "Great post @CompanyName! Love this.",
"platforms": ["linkedin", "twitter", "facebook"]
}
```
> [!NOTE]
>
> **LinkedIn Mentions:** LinkedIn mentions are resolved server-side by Ayrshare. Use the [LinkedIn Search endpoint](/docs/apis/listen/search/linkedin-search) to look up the correct vanity name or member name. For organizations, use `@handle`. For members, use `@vanity_name` or `@[Full Name]`.
For detailed mention syntax and limitations per platform, see:
* [Bluesky mentions](/docs/apis/post/social-networks/bluesky#bluesky-mentions)
* [Facebook mentions](/docs/apis/post/social-networks/facebook#facebook-page-mentions)
* [Instagram mentions](/docs/apis/post/social-networks/instagram#instagram-mentions)
* [LinkedIn mentions](/docs/apis/post/social-networks/linkedin#linkedin-mentions)
* [Reddit mentions](/docs/apis/post/social-networks/reddit#reddit-mentions)
* [Threads mentions](/docs/apis/post/social-networks/threads#threads-mentions)
* [TikTok mentions](/docs/apis/post/social-networks/tiktok#tiktok-mentions)
* [X/Twitter mentions](/docs/apis/post/social-networks/x-twitter#mentions)
* [YouTube mentions](/docs/apis/post/social-networks/youtube#youtube-mentions)
See [Post Verification](/docs/testing/post-verification) for the platform-by-platform mention rules and recommendations.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"id": "Ut1fWU6XkqkMayHGnJZ", "platforms": ["bluesky", "facebook", "instagram", "linkedin", "reddit", "tiktok", "twitter", "youtube"], "comment": "An amazing comment!"}' \
-X POST https://api.ayrshare.com/api/comments
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/comments", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
id: "Ut1fWU6XkqkMayHGnJZ", // required
platforms: ["bluesky", "facebook", "instagram", "linkedin", "reddit", "tiktok", "twitter", "youtube"], // required
comment: "An amazing comment!", //required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': 'Ut1fWU6XkqkMayHGnJZ',
'platforms': ['bluesky', 'facebook', 'instagram', 'linkedin', 'reddit', 'tiktok', 'twitter', 'youtube'],
'comment': 'An amazing comment!'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/comments',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
'Ut1fWU6XkqkMayHGnJZ', // Replace with your actual post ID
'platforms' => ['bluesky', 'facebook', 'instagram', 'linkedin', 'reddit', 'tiktok', 'twitter', 'youtube'],
'comment' => 'An amazing comment!'
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"id": "Ut1fWU6XkqkMayHGnJZ", // required
"platforms": []string{"bluesky", "facebook", "instagram", "linkedin", "reddit", "tiktok", "twitter", "youtube"}, // required
"comment": "An amazing comment!", // required
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/comments",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CommentsPOSTRequest_csharp
{
class CommentsPOST
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/comments";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"id\": \"Ut1fWU6XkqkMayHGnJZ\"," +
"\"platforms\": [\"bluesky\", \"facebook\", \"instagram\", \"linkedin\", \"reddit\", \"tiktok\", \"twitter\", \"youtube\"]," +
"\"comment\": \"An amazing comment!\"}";
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"commentID": "IUDSCfdYfFkw_dWhW9PLI", // Ayrshare Comment ID. Used for replying to a comment.
"id": "Ut1fWU6XkqlMayHGnJZ", // Ayrshare Post ID
"bluesky": {
"status": "success",
"commentId": "at://did:plc:62musrcyanhro2lydyhlw7ci/app.bsky.feed.post/3lf4b4j3dqy2i",
"cid": "bafyreiaoivcafelkpzhbbjco23pguyqmy7wucuvfxaifgj34qoyp7nrhhy",
"comment": "Nice one this is",
"platform": "bluesky",
"postUrl": "https://bsky.app/profile/ayrshare.com/post/3lf4b4j3dqy2i"
},
"facebook": {
"status": "success",
"commentId": "358482752285927_363474831785719", // Facebook Social Comment ID
"comment": "The best comment ever!",
"platform": "facebook"
},
"instagram": {
"status": "success",
"commentId": "17060111860440276", // Instagram Social Comment ID
"comment": "The best comment ever!",
"platform": "instagram"
},
"linkedin": {
"status": "success",
"commentId": "urn:li:comment:(urn:li:activity:7141202289402236928,7141205176207503360)", // LinkedIn Social Comment ID
"comment": "Someone's sitting in the shade today because someone planted a tree a long time ago. - Warren Buffett",
"commentUrn": "urn:li:comment:(urn:li:activity:7141202289402236928,7141205176207503360)", // Social comment Id from LinkedIn
"platform": "linkedin"
},
"tiktok": {
"status": "success",
"commentId": "7260964914699764524", // TikTok Social Comment ID
"comment": "The best comment ever!",
"platform": "tiktok",
"videoId": "7260964048362310959"
},
"twitter": {
"status": "success",
"commentId": "1525632403262101648", // Twitter Social Comment ID
"comment": "The best comment ever!",
"postUrl": "https://twitter.com/ayrshare/status/1525632403262101648"
},
"youtube": {
"status": "success",
"commentId": "UgzIEZsDQKXgHsEnwTR4AaABAa", // YouTube Social Comment ID
"comment": "The best comment ever!",
"platform": "youtube"
},
}
```
```json 404: Not Linked & Success theme={"system"}
{
"commentId": "5GizKtM9TFRFFWNDgBZ",
"twitter": {
"status": "success",
"commentId": "1842266484032016",
"comment": "Having a meeting on this",
"platform": "twitter",
"postUrl": "https://twitter.com/ayrshare/status/18422664840320"
},
"action": "post",
"status": "error",
"code": 156,
"message": "Instagram is not linked. Please confirm the linkage on the Social Accounts page in the dashboard.",
"platform": "instagram",
"id": "1w3QI6bU7KE9XyxrN0u", // Ayrshare Post ID
"errors": [
{
"action": "post",
"status": "error",
"code": 156,
"message": "Facebook is not linked. Please confirm the linkage on the Social Accounts page in the dashboard.",
"platform": "facebook"
},
{
"action": "post",
"status": "error",
"code": 156,
"message": "Instagram is not linked. Please confirm the linkage on the Social Accounts page in the dashboard.",
"platform": "instagram"
}
]
}
```
```json 400: Duplicate comment theme={"system"}
{
"twitter": {
"action": "post",
"status": "error",
"code": 134,
"message": "The social network detected duplicate content and blocked this post. Please change the content and try again.",
"post": "Having a meeting on this",
"detail": "You are not allowed to create a Tweet with duplicate content."
},
"code": 134,
"status": "error",
"id": "1w3QI6bU7KE9XyxrN", // Ayrshare Post ID
"errors": [
{
"action": "post",
"status": "error",
"code": 134,
"message": "The social network detected duplicate content and blocked this post. Please change the content and try again.",
"post": "Having a meeting on this",
"detail": "You are not allowed to create a Tweet with duplicate content.",
"id": "1w3QI6bU7KE9XyxrN",
"platform": "twitter"
}
]
}
```
# Reply to a Comment
Source: https://www.ayrshare.com/docs/apis/comments/reply-to-comment
POST /comments/reply/:commentId
Reply by Ayrshare or Social Comment ID on Bluesky, Facebook, Instagram, LinkedIn, TikTok, X, and YouTube, with @mentions and one image on Facebook or LinkedIn.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Reply to a comment, i.e. comment on a comment, on either an Ayrshare made comment, using the [Ayrshare Comment ID](/docs/apis/comments/overview#comments-with-ayrshare-comment-id), or a comment made outside of Ayrshare, using the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id).
Most often, you will reply to a comment made through Ayrshare, but when you want to reply to a comment made outside of Ayrshare, you can use the Social Comment ID, which you get [GET /comment](/docs/apis/comments/get-comments) endpoint.
> [!NOTE]
>
> Facebook, Instagram, and TikTok only let you reply to comments on posts from your own connected account or page. Here, a comment "made outside of Ayrshare" means one left on your own post, for example a comment a follower wrote in the Instagram app. On these platforms you cannot reply to comments on other users' posts, even with the Social Comment ID.
>
> On LinkedIn and X, Ayrshare does not check who owns the post. Whether a reply on another user's post succeeds depends on that network's rules and the post's reply settings. See [Commenting on Other Users' Social Posts](/docs/help-center/technical-support/commenting-on-another-user-post).
Supported platforms: Bluesky, Facebook, Instagram, LinkedIn, TikTok, X, and YouTube.
> [!NOTE]
>
> **YouTube comments older than 28 days.** Ayrshare keeps the record of a YouTube comment it sent for 28 days, to stay within the 30-day limit in YouTube's API policies. After that, the Ayrshare ID no longer finds the comment. To reply to it, use its [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id) (the YouTube comment `commentId`) with `"searchPlatformId": true`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**commentId** `string` *required*
If replying to a comment made through Ayrshare, use the [Ayrshare Comment ID](/docs/apis/comments/overview#comments-with-ayrshare-comment-id) `commentId` returned from the [POST comment endpoint](/docs/apis/comments/post-comment).
For example, the Ayrshare comment ID `commentId` returned from the [POST comment endpoint](/docs/apis/comments/post-comment) is:
```json Ayrshare Comment ID Returned from POST /comment theme={"system"}
{
"commentId": "Ut1fWU6XkqkMayHGnJZ"
}
```
If replying to a comment made outside of Ayrshare, use the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id) `commentId` from the social network.
For example, the social comment ID `commentId` returned from a Facebook [POST /comment](/docs/apis/comments/post-comment) or [GET /comment](/docs/apis/comments/get-comments) endpoint is:
```json Social Comment ID Returned from POST /comment theme={"system"}
{
"facebook": { "commentId": "8392829334_1234567890" }
}
```
## Body Parameters
**platforms** `array` *required*
If replying to a comment made through Ayrshare using the [Ayrshare Comment ID](/docs/apis/comments/overview#comments-with-ayrshare-comment-id), specify the platforms to post the reply. Supported platforms: `bluesky`, `facebook`, `instagram`, `linkedin`, `tiktok`, `twitter`, `youtube`.
If replying to a comment made outside of Ayrshare using the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id), specify the one platform to post the reply. Supported platforms: `facebook`, `instagram`, `linkedin`, `tiktok`, `twitter`.
**comment** `string` *required*
The reply to add to the comment.
**mediaUrls** `array`
Attach an image by providing the image URL. Only one image is supported. Facebook and LinkedIn only.
**searchPlatformId** `boolean` (default: false)
Set to `true` if replying to a comment using the [Social Comment ID](/docs/apis/comments/overview#comments-with-social-comment-id), which is the post `commentId` from the social networks.
**commentUrn** `string`
If replying to a LinkedIn comment with a Social Comment ID, you must also provide the full LinkedIn comment URN, which you get from the [POST /comment endpoint](/docs/apis/comments/post-comment) or [GET /comment endpoint](/docs/apis/comments/get-comments) as `commentUrn` — not the bare `commentId`. The URN has the form `urn:li:comment:(urn:li:activity:,)`.
Only required for LinkedIn and `"searchPlatformId": true`.
**videoId** `string`
If replying to a TikTok comment with a Social Comment ID, you must also provide the TikTok video ID, which you get from the [POST /comment endpoint](/docs/apis/comments/post-comment) or [GET /comment endpoint](/docs/apis/comments/get-comments).
Only required for TikTok and `"searchPlatformId": true`.
**objResponse** `boolean` (default: true)
Response as Object, otherwise as an Array. Default to `true`.
## @Mentions in Comments
You can include @mentions directly in the `comment` text string. There is no separate parameter for mentions. Simply add `@handle` in your comment text.
```json Reply with @Mention theme={"system"}
{
"comment": "Thanks @CompanyName! Great insight.",
"platforms": ["linkedin", "twitter", "facebook"]
}
```
> [!NOTE]
>
> **LinkedIn Mentions:** LinkedIn mentions are resolved server-side by Ayrshare. Use the [LinkedIn Search endpoint](/docs/apis/listen/search/linkedin-search) to look up the correct vanity name or member name. For organizations, use `@handle`. For members, use `@vanity_name` or `@[Full Name]`.
For detailed mention syntax and limitations per platform, see:
* [Bluesky mentions](/docs/apis/post/social-networks/bluesky#bluesky-mentions)
* [Facebook mentions](/docs/apis/post/social-networks/facebook#facebook-page-mentions)
* [Instagram mentions](/docs/apis/post/social-networks/instagram#instagram-mentions)
* [LinkedIn mentions](/docs/apis/post/social-networks/linkedin#linkedin-mentions)
* [Threads mentions](/docs/apis/post/social-networks/threads#threads-mentions)
* [TikTok mentions](/docs/apis/post/social-networks/tiktok#tiktok-mentions)
* [X/Twitter mentions](/docs/apis/post/social-networks/x-twitter#mentions)
* [YouTube mentions](/docs/apis/post/social-networks/youtube#youtube-mentions)
Reply to comment is not supported on Reddit. See [Post Verification](/docs/testing/post-verification) for the platform-by-platform mention rules and recommendations.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"platforms": ["bluesky", "facebook", "instagram", "linkedin", "twitter", "youtube"], "comment": "An amazing reply!"}' \
-X POST https://api.ayrshare.com/api/comments/reply/Ut1fWU6XkqkMayHGnJZ
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/comments/reply/Ut1fWU6XkqkMayHGnJZ", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({
platforms: ["bluesky", "facebook", "instagram", "linkedin", "twitter", "youtube"], // required
comment: "An amazing comment!", //required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'platforms': ['bluesky', 'facebook', 'instagram', 'linkedin', 'twitter', 'youtube'],
'comment': 'An amazing comment!'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/comments/reply/Ut1fWU6XkqkMayHGnJZ',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
['bluesky', 'facebook', 'instagram', 'linkedin', 'twitter', 'youtube'],
'comment' => 'An amazing comment!'
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"platforms": []string{"bluesky", "facebook", "instagram", "linkedin", "twitter", "youtube"}, // required
"comment": "An amazing comment!", // required
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/comments/reply/Ut1fWU6XkqkMayHGnJZ",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CommentsReplyPOSTRequest_csharp
{
class CommentsReply
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/comments/reply/Ut1fWU6XkqkMayHGnJZ";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
var json = @"{
""platforms"": [""bluesky"", ""facebook"", ""instagram"", ""linkedin"", ""twitter"", ""youtube""],
""comment"": ""An amazing comment!""
}";
var content = new StringContent(
json,
Encoding.UTF8,
"application/json"
);
try
{
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"commentId": "Eh61xHheGHvAqXqGISGVa",
"bluesky": {
"status": "success",
"commentId": "at://did:plc:62musrcyanhro2lydyhlw7ci/app.bsky.feed.post/3lf4bquqrxd2d",
"cid": "bafyreie2jt5iw6rk2ekmiq6aljyiv7nj4sjucbpkamqc73tmggh5fmrqha",
"comment": "Truth is ever to be found in simplicity.",
"platform": "bluesky",
"postUrl": "https://bsky.app/profile/ayrshare.com/post/3lf4bquqrxd2d",
"sourceCommentId": "at://did:plc:62musrcyanhro2lydyhlw7ci/app.bsky.feed.post/3lf4b4j3dqy2i"
},
"facebook": {
"status": "success",
"commentId": "729149842469573_696560159169326",
"sourceCommentId": "729149842469573_738966840899770",
"comment": "Truth is ever to be found in simplicity.",
"platform": "facebook"
},
"instagram": {
"status": "success",
"commentId": "18020060386920755",
"sourceCommentId": "17998452434360734",
"comment": "Truth is ever to be found in simplicity.",
"platform": "instagram"
},
"linkedin": {
"status": "success",
"commentId": "7140161360574713856",
"sourceCommentId": "urn:li:ugcPost:7140160850354397187",
"comment": "Truth is ever to be found in simplicity.",
"platform": "linkedin"
},
"tiktok": {
"status": "success",
"commentId": "7484295933044572922",
"sourceCommentId": "7484407021429703422",
"comment": "Truth is ever to be found in simplicity.",
"platform": "tiktok",
"videoId": "7484993689815715122"
},
"twitter": {
"status": "success",
"commentId": "1734395672961441821",
"sourceCommentId": "1734395509526155733",
"comment": "Truth is ever to be found in simplicity.",
"platform": "twitter",
"postUrl": "https://twitter.com/ayrshare/status/1734395672961441821"
},
"youtube": {
"status": "success",
"commentId": "UgxZ55j-FDri8lrTgVR4AaABAg.9yCaV71m1fo9yCaZvu7Hj4",
"sourceCommentId": "UgxZ55j-FDri8lrTgVR4AaABAg",
"comment": "Truth is ever to be found in simplicity.",
"platform": "youtube"
},
"status": "success",
"id": "ytDgi5vyz36F2yshlJka"
}
```
```json 400: Unknown commentId theme={"system"}
{
"facebook": {
"action": "post",
"status": "error",
"code": 215,
"message": "Error posting comment. Please be sure you are posting a valid comment, the original post exists, the original post is publicly available."
},
"code": 215,
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 215,
"message": "Error posting comment. Please be sure you are posting a valid comment, the original post exists, the original post is publicly available.",
"platform": "facebook"
}
],
"id": "5360106757501_178916202161"
}
```
# Add RSS Feed
Source: https://www.ayrshare.com/docs/apis/feeds/add-feed
POST /feed
Add an RSS, Substack, or YouTube feed to auto-post new articles to linked social accounts, with options for useFirstImage, autoHashtag, and platforms.
Add a new RSS feed for automated posting of new articles. Posts will be automatically sent to your linked social accounts: Bluesky, Facebook, LinkedIn, Telegram, and Threads. Also Instagram and Pinterest if a valid image can be found in the article, and Snapchat if `useFirstImage` is `true` and an image is found — Snapchat cannot publish without media.
Reddit, TikTok, YouTube, and Google Business Profile are not available for feeds — each needs a detail a feed entry can't supply, such as a subreddit, a video file, or a location. See [why only these networks](/docs/dashboard/automated-rss-feeds#why-only-these-networks).
> [!WARNING]
>
> **X/Twitter is no longer supported for RSS feeds.** As of March 31, 2026, X requires your own API credentials for each request, which is incompatible with automated RSS posting. Passing `twitter` in `platforms` is ignored; if it is the only platform given, the request returns error `101`. Use [scheduled posts](/docs/apis/post/overview#schedule-posts) or direct API calls with your [BYO credentials](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) instead.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**url** `string` *required*
URL of the RSS feed to add.
**useFirstImage** `boolean` (default: false)
Attempt to get the first or top image in the article and add it to the post.
**autoHashtag** `boolean` (default: false)
Automatically add up to 3 hashtags to the post text based on the most relevant keywords.
**type** `string` (default: rss)
Value: `rss`, `substack`, or `youtube`.
**platforms** `array`
Social network platforms to publish the article. If not provided, defaults to all connected platforms that are available for feeds. Available platforms:
```json theme={"system"}
{
"platforms": ["bluesky", "facebook",
"instagram", "linkedin", "pinterest",
"snapchat", "telegram", "threads"]
}
```
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "https://www.nytimes.com"}' \
-X POST https://api.ayrshare.com/api/feed
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const url = "https:///www.nytimes.com";
fetch("https://api.ayrshare.com/api/feed", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({ url })
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'url': 'https://www.nytimes.com'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/feed',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
$data = array (
'url' => 'https://www.nytimes.com'
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/feed',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "4HZhptaD5",
"title": "Pulte's Money and Life Thoughts",
"websiteLink": "https://pulte.substack.com",
"feedURL": "https://pulte.substack.com/feed"
}
```
```json 400: Feed Already Exists theme={"system"}
{
"action": "create",
"status": "error",
"code": 303,
"message": "The feed https://ruthreichl.substack.com/feed already exists.",
"id": "TDW3oMDoI"
}
```
```json 400: Invalid URL theme={"system"}
{
"action": "create",
"status": "error",
"code": 130,
"message": "Something is wrong with the RSS feed URL. Please verify it."
}
```
# Delete RSS Feed
Source: https://www.ayrshare.com/docs/apis/feeds/delete-feed
DELETE /feed
Delete a previously added RSS feed by passing the id returned when the feed was added. Includes example requests plus success and error responses.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
ID of the RSS Feed returned when adding.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"id": "4HZhptaD5"}' \
-X DELETE https://api.ayrshare.com/api/feed
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "4HZhptaD5";
fetch("https://api.ayrshare.com/api/feed", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({ id }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': '4HZhptaD5'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/feed',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
$data = array (
'id' => '_3yhtyd88'
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/feed',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "RDFbD_fl5"
}
```
```json 400: Can't Find RSS ID theme={"system"}
{
"action": "delete",
"status": "error",
"code": 131,
"message": "Something is wrong deleting the RSS feed URL. Please try again and verify the id."
}
```
# Get RSS Feeds
Source: https://www.ayrshare.com/docs/apis/feeds/get-feeds
GET /feed
Return every RSS feed registered to your account, with each feed's URL, title, link, autoHashtag and useFirstImage settings, and created and updated dates.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```json 200: Success theme={"system"}
{
"status": "success",
"feeds": [
{
"autoHashtag": false,
"created": "2022-05-16T23:20:03.405Z",
"description": "Social Media APIs that enable you to send social media posts effortlessly",
"id": "_3yhtyd88",
"image": {
"height": "32",
"link": "https://www.ayrshare.com",
"title": "Ayrshare",
"width": "32",
"url": "https://www.ayrshare.com/wp-content/uploads/2020/07/cropped-ayr-icon-2FKLDFB-32x32.png"
},
"link": "https://www.ayrshare.com",
"title": "Ayrshare",
"type": "rss",
"updated": "2022-05-16T23:20:17.124Z",
"url": "https://www.ayrshare.com/feed/",
"useFirstImage": true
}
]
}
```
# RSS Feeds Overview
Source: https://www.ayrshare.com/docs/apis/feeds/overview
Manage RSS feeds for automated posting of blog articles, YouTube videos, and podcasts, with a webhook to get notified when a new RSS feed item is ready.
Manage RSS feeds for automated posting. Great for auto-posting your blog articles, YouTube videos, and podcasts.
A [webhook is available to get notified](/docs/apis/webhooks/actions#feed-action) when a new RSS feed item is available.
# Update RSS Feed
Source: https://www.ayrshare.com/docs/apis/feeds/update-feed
PUT /feed
Change settings on an existing RSS feed by its id, toggling useFirstImage to add the article's top image and autoHashtag to add up to 3 relevant hashtags.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
The RSS feed id returned from the RSS feed POST endpoint.
**useFirstImage** `boolean`
Attempt to get the first or top image in the article and add it to the post.
**autoHashtag** `boolean`
Automatically add up to 3 hashtags to the post text based on the most relevant keywords.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"id": "4HZhptaD5", "useFirstImage": true, "autoHashtag": true}' \
-X PUT https://api.ayrshare.com/api/feed
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "4HZhptaD5";
fetch("https://api.ayrshare.com/api/feed", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({ id, "useFirstImage": true, "autoHashtag": true }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.err
```
```python Python theme={"system"}
import requests
payload = {'id': '4HZhptaD5', 'useFirstImage': True, 'autoHashtag': True}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.put('https://api.ayrshare.com/api/feed',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
$data = array (
'id' => '_3yhtyd88',
'useFirstImage' => false,
'autoHashtag' => true
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/feed',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "vVYX3cgJ7"
}
```
```json 400: RSS Feed not found theme={"system"}
{
"action": "update",
"status": "error",
"code": 304,
"message": "The feed with id vVYX3cgJ does not exist."
}
```
# Generate Alt Text
Source: https://www.ayrshare.com/docs/apis/generate/image-alt-text
POST /generate/altText
Create AI-generated alt text for images by passing an image URL, with optional keywords to include and a language code for the output alt text.
Create AI-generated alt text for your images. Choose the language to write the alt text and keywords to include in the alt text.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**url** `string` *required*
Image URL of the image to create the alt text. Must start with `https://`. Supports JPEG, PNG, GIF, WEBP, and BMP.
**keywords** `array`
String array of keywords or phrases to be considered when generating the alt text. Typically only one or two of the keywords from the array will be used in the alt text.
**lang** `string`
Language to output the alt text. Use one of the available [language codes](/docs/iso-codes/language).
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "https://img.ayrshare.com/012/gb.jpg"}' \
-X POST https://api.ayrshare.com/api/generate/altText
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/generate/altText", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
url: "https://img.ayrshare.com/012/gb.jpg", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'url': 'https://img.ayrshare.com/012/gb.jpg'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/generate/altText',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$curl = curl_init();
$data = array (
"url" => "https://img.ayrshare.com/012/gb.jpg"
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/generate/altText',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY',
'Accept-Encoding: gzip'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
class Program
{
// Replace 'YOUR_API_KEY' with your actual Ayrshare API key
private const string API_KEY = "YOUR_API_KEY";
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string imageUrl = "https://img.ayrshare.com/012/gb.jpg";
try
{
var result = await GenerateAltTextAsync(imageUrl);
Console.WriteLine("Generated Alt Text:");
Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}
catch (Exception e)
{
Console.WriteLine($"An error occurred: {e.Message}");
}
}
static async Task GenerateAltTextAsync(string imageUrl)
{
var url = "https://api.ayrshare.com/api/generate/altText";
var requestData = new { url = imageUrl };
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {API_KEY}");
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"HTTP error! status: {response.StatusCode}");
}
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(responseBody);
}
}
```
```json 200: Alt Text Generated theme={"system"}
{
"status": "success",
"altText": "A ghostbusters vehicle driving through a field.",
"url": "https://img.ayrshare.com/012/gb.jpg"
}
```
```json 500: Error with the Image File theme={"system"}
{
"action": "upload",
"status": "error",
"code": 115,
"message": "An error occurred uploading your file, such as a timeout at the social network. Please try your post again with the /retryPost endpoint."
}
```
# Generate API Overview
Source: https://www.ayrshare.com/docs/apis/generate/overview
AI utilities for social media posts: generate text, rewrite posts, transcribe videos, translate text, create alt text, and monthly token limits per plan.
We offer a set of AI utilities that help with creating social media posts.
> [card] [Generate Text](/docs/apis/generate/post-text)
> [card] [Rewrite a Post](/docs/apis/generate/rewrite-post)
> [card] [Transcribe a Video](/docs/apis/generate/transcribe-video)
> [card] [Translate Post Text](/docs/apis/generate/translate-post)
> [card] [Generate Alt Text](/docs/apis/generate/image-alt-text)
> [card] [Sentiment Analysis](/docs/apis/generate/sentiment)
## Token Limits
For every API call made to AI endpoints, a certain number of "tokens" are used. These tokens are calculated based on the data you send and receive. Every month, your Max Pack and overall Ayrshare account are allocated a total of 300,000 tokens for Business plans, 100,000 for Premium Plus plans, and 50,000 for Premium plans.
This allocation refreshes on the first day of each month. To see how many tokens a specific API call used, as well as your total usage for the month, please check the HTTP response.
# Generate Post Text
Source: https://www.ayrshare.com/docs/apis/generate/post-text
POST /generate/post
Generate a new social post using AI from text or image URLs, with options to add hashtags and emojis or keep the post to 280 characters for Twitter.
Generate a new social post using AI. You can either provide text or images to create the social media post. [Token limits](/docs/apis/generate/overview#token-limits) applicable.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**text** `string` *required*
Description of what the post should be about. For example: "A shoe sale happening Monday with specials on red, blue, and yellow shoes". Not required if `mediaUrls` parameter is included.
**mediaUrls** `array`
An array of image URLs to base the post text on. Required if `text` parameter not included.
**hashtags** `boolean` (default: true)
Add hashtags to the post.
**emojis** `boolean` (default: false)
Add emojis to the post.
**twitter** `boolean` (default: false)
Construct a post with 280 or few characters.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"text": "This beautiful new shoe is on sale now. It comes in red, blue, or purple. Check it out today."}' \
-X POST https://api.ayrshare.com/api/generate/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/generate/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
text: "This beautiful new shoe is on sale now. It comes in red, blue, or purple. Check it out today.", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'text': 'This beautiful new shoe is on sale now. It comes in red, blue, or purple. Check it out today.'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/generate/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"This beautiful new shoe is on sale now. It comes in red, blue, or purple. Check it out today."
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/generate/post',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY',
'Accept-Encoding: gzip'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Text theme={"system"}
{
"status": "success",
"post": "🎉🛍️ Introducing the stunning new shoes that's now on sale! You can choose from three different colors – red, blue, or purple – and trust us, they're all amazing! Don't miss out on this beauty and grab yours today! 🙌 #newshoe #onSale #fashionable #colormebeautiful 🌈",
"usage": 98, // Tokens used for this call
"totalMonthlyUsage": 733 // Total tokens used for the month
}
```
```json 200: Image theme={"system"}
{
"status": "success",
"post": ["Ventured out on a serene walk today—a path less traveled but surrounded by the lush vibrancy of nature's finest. The perfect spot to reconnect with the earth and oneself. Where do your paths lead you?\n\n#NatureWalks #ExploreOutdoors #PeacefulPath"],
"mediaUrls": ["https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"],
"usage": 2295,
"totalMonthlyUsage": 6987
}
```
```json 400: Bad Request theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Rewrite Post
Source: https://www.ayrshare.com/docs/apis/generate/rewrite-post
POST /generate/rewrite
Generate up to five AI rewrites of a social media post, with options to add emojis or hashtags and to keep each version to 280 characters or fewer.
Generate variations of a social media post using AI. [Token limits](/docs/apis/generate/overview#token-limits) applicable.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**post** `string` *required*
The post text to be rewritten.
**emojis** `string` (default: false)
Add emojis to the post.
**hashtags** `string` (default: false)
Add hashtags to the post.
**twitter** `string` (default: false)
Construct a post 280 or fewer characters.
**rewrites** `string` (default: 5)
Number of rewrites. Min: 1 and Max: 5.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"post": "Kali melts the heart, even when the rest of the day is freezing. Happy International Polar Bear Day to the largest land carnivore and the biggest, furriest part of the Zoo’s bear community! \nOur resident polar bear Kali (pronounced “Cully”) is a wild-born bear that was born off of the northwest coast of Alaska. He was named by the people of the Native Village of Point Lay, who rescued him. \"Kali\" is the Inupiaq name for Point Lay. Eventually, the U.S. Fish and Wildlife"}' \
-X POST https://api.ayrshare.com/api/generate/rewrite
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/generate/rewrite", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
post: "Kali melts the heart, even when the rest of the day is freezing. Happy International Polar Bear Day to the largest land carnivore and the biggest, furriest part of the Zoo’s bear community! \nOur resident polar bear Kali (pronounced “Cully”) is a wild-born bear that was born off of the northwest coast of Alaska. He was named by the people of the Native Village of Point Lay, who rescued him. \"Kali\" is the Inupiaq name for Point Lay. Eventually, the U.S. Fish and Wildlife", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'post': 'Kali melts the heart, even when the rest of the day is freezing. Happy International Polar Bear Day to the largest land carnivore and the biggest, furriest part of the Zoo’s bear community! \nOur resident polar bear Kali (pronounced “Cully”) is a wild-born bear that was born off of the northwest coast of Alaska. He was named by the people of the Native Village of Point Lay, who rescued him. \"Kali\" is the Inupiaq name for Point Lay. Eventually, the U.S. Fish and Wildlife'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/generate/rewrite',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"Kali melts the heart, even when the rest of the day is freezing. Happy International Polar Bear Day to the largest land carnivore and the biggest, furriest part of the Zoo’s bear community! \nOur resident polar bear Kali (pronounced “Cully”) is a wild-born bear that was born off of the northwest coast of Alaska. He was named by the people of the Native Village of Point Lay, who rescued him. \"Kali\" is the Inupiaq name for Point Lay. Eventually, the U.S. Fish and Wildlife"
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/generate/rewrite',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY',
'Accept-Encoding: gzip'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"post": "Kali melts the heart, even when the rest of the day is freezing. Happy International Polar Bear Day to the largest land carnivore and the biggest, furriest part of the Zoo’s bear community! \nOur resident polar bear Kali (pronounced “Cully”) is a wild-born bear that was born off of the northwest coast of Alaska. He was named by the people of the Native Village of Point Lay, who rescued him. \"Kali\" is the Inupiaq name for Point Lay. Eventually, the U.S. Fish and Wildlife",
"rewrites": [
"❄️🐻 Kali, the lovable polar bear, warms our hearts on this #InternationalPolarBearDay. Fun fact: he was named after Inupiaq village, Point Lay! #FurryZooCommunity 🐾",
"Happy #InternationalPolarBearDay to Kali, the largest land carnivore of the Zoo community! Even on freezing days, Kali steals our hearts with his adorable antics. 🐻❤️",
"Meet Kali, the wild-born polar bear with a heart-melting charm. 🐾❄️ Sending love on this #InternationalPolarBearDay to the Zoo's fluffiest resident. 🐻❤️",
"On #InternationalPolarBearDay, we celebrate Kali, the furry wonder from the largest land carnivore family of the Zoo. 🐾🐻 He was named after an Inupiaq village, Point Lay!🌟",
"Kali, the Zoo's beloved polar bear, steals our hearts with his playful spirit, as we celebrate #InternationalPolarBearDay. 🐻❤️ Born off the Alaskan coast, he's a true warrior! 💪🌟"
],
"usage": 98, // Tokens used for this call
"totalMonthlyUsage": 733 // Total tokens used for the month
}
```
```json 400: Missing parameters theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Sentiment Analysis
Source: https://www.ayrshare.com/docs/apis/generate/sentiment
POST /generate/sentiment
Run sentiment analysis on a social media post or comment to learn if the text is positive, negative, or neutral, with recommendations to improve it.
Generate a sentiment analysis on a social media post or comment to understand if the text is positive, negative, or neutral and recommendations on improving the text for a more positive reaction.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**text** `string` *required*
The string of text to generate the sentiment analysis on.
### Request Examples
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"text": "These shoes aren't the best. The color has faded and they are uncomfortable to walk in."}' \
-X POST https://api.ayrshare.com/api/generate/sentiment
```
```javascript JavaScript theme={"system"}
const API_KEY = 'YOUR_API_KEY';
const analyzeTextSentiment = async (text) => {
try {
const response = await fetch('https://api.ayrshare.com/api/generate/sentiment', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ text })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Sentiment Analysis Result:', data);
return data;
} catch (error) {
console.error('Error analyzing sentiment:', error.message);
throw error;
}
};
```
```python Python theme={"system"}
import requests
import json
API_KEY = 'YOUR_API_KEY'
def analyze_text_sentiment(text):
url = 'https://api.ayrshare.com/api/generate/sentiment'
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
data = {'text': text}
try:
response = requests.post(url, headers=headers, json=data)
response.raise_for_status() # Raises a HTTPError if the status is 4xx, 5xx
result = response.json()
print('Sentiment Analysis Result:', json.dumps(result, indent=2))
return result
except requests.exceptions.RequestException as e:
print('Error analyzing sentiment:', e)
raise
```
```php PHP theme={"system"}
$text]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
echo 'Error analyzing sentiment: ' . curl_error($ch);
return null;
}
curl_close($ch);
if ($httpCode >= 200 && $httpCode < 300) {
$result = json_decode($response, true);
echo "Sentiment Analysis Result:\n";
print_r($result);
return $result;
} else {
echo "HTTP Error: " . $httpCode . "\n";
echo "Response: " . $response . "\n";
return null;
}
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
class Program
{
private const string API_KEY = "YOUR_API_KEY";
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string textToAnalyze = "These shoes aren't the best. The color has faded and they are uncomfortable to walk in.";
try
{
var result = await AnalyzeTextSentimentAsync(textToAnalyze);
Console.WriteLine("Sentiment Analysis Result:");
Console.WriteLine(JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }));
}
catch (Exception e)
{
Console.WriteLine($"An error occurred: {e.Message}");
}
}
static async Task AnalyzeTextSentimentAsync(string text)
{
var url = "https://api.ayrshare.com/api/generate/sentiment";
var requestData = new { text = text };
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {API_KEY}");
var json = JsonSerializer.Serialize(requestData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync(url, content);
if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException($"HTTP error! status: {response.StatusCode}");
}
var responseBody = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize(responseBody);
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"text": "These shoes aren't the best. The color has faded and they are uncomfortable to walk in.",
"sentimentAnalysis": {
"sentiment": "negative", // positive, negative, or neutral
"opportunities": [
"Improve color durability",
"Enhance comfort level"
],
"recommendations": [
{
"opportunity": "Improve color durability",
"recommendation": "Investigate and use higher-quality dyes or materials to prevent fading over time."
},
{
"opportunity": "Enhance comfort level",
"recommendation": "Review and possibly redesign the shoe's insole and cushioning to provide better support and comfort for walking."
}
]
}
}
```
```json 400: Error theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Transcribe a Video
Source: https://www.ayrshare.com/docs/apis/generate/transcribe-video
POST /generate/transcription
Generate a transcription and title of a video uploaded to Ayrshare using AI, then use them to create a post summary or a YouTube title. Max 10 minutes.
Generate a transcription and title of a video file using AI.
This transcription can then be used in the [/generate/post](/docs/apis/generate/post-text) to create a social media summary of the video.
For [YouTube](/docs/apis/post/social-networks/youtube), which requires a title, the generated title can be used.
> [!WARNING]
>
> The video must have been previously uploaded to Ayrshare with [/media](/docs/apis/media/overview).
> Videos not hosted by Ayrshare will be rejected.
>
> Maximum video file size is 500GB and maximum duration is 10 minutes.
### Transcribe Video Guide
[Transcribe a Video](https://www.youtube.com/embed/d1JBbHrxmls)
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**videoUrl** `string` *required*
URL encoded video URL. The video must be hosted by Ayrshare.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"videoUrl": "https://img.ayrshare.com/random/landscape5.mp4"}' \
-X POST https://api.ayrshare.com/api/generate/transcription
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/generate/transcription", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
videoUrl: "https://img.ayrshare.com/random/landscape5.mp4", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'videoUrl': 'https://img.ayrshare.com/random/landscape5.mp4'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/generate/transcription',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://img.ayrshare.com/random/landscape5.mp4"
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/generate/transcription',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY',
'Accept-Encoding: gzip'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```json 200: Success theme={"system"}
{
"status": "success",
"transcript": "This is your last chance. After this, there is no turning back. You take the blue pill the story ends you wake up in your bed and believe whatever you want to be. You take the red pill you stay in Wonderland. And I show you how deep the rabbit hole goes.",
"transcriptArray": [
"This is your last chance.",
"After this, there is no turning back.",
"You take the blue pill the story ends you wake up in your bed and believe whatever you want to be. You take the red pill you stay in Wonderland.",
"And I show you how deep the rabbit hole goes."
],
"videoTitle": "Choose Wisely: Blue Pill or Red Pill Adventure Awaits!",
"wordCount": 52
}
```
# Translate Post
Source: https://www.ayrshare.com/docs/apis/generate/translate-post
POST /generate/translate
Translate post text into over 100 languages using AI. The current language is automatically detected and translated to the language code you specify.
Translate text for a post to over 100 different [languages](/docs/iso-codes/language) using AI. The current language is automatically detected and translated to the specified language.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**text** `string` *required*
Text to be translated.
**lang** `string` *required*
[Language code](/docs/iso-codes/language) to translate the text.
```json 200: Success theme={"system"}
{
"status": "success",
"translatedText": "Nos vamos a las carreras",
"originalText": "Off we go to the races",
"language": "es"
}
```
```json 400: Bad Language Submitted theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Ads
Source: https://www.ayrshare.com/docs/apis/graphql/ads
Manage boosted ads through the Ayrshare GraphQL API with one set of operations that takes the ads platform as an argument.
The ads operations take the ads platform as an argument, so the same operation works on every ads platform Ayrshare supports. Today that is `FACEBOOK` and `INSTAGRAM`.
Each operation calls the matching [REST ads endpoint](/docs/apis/ads/overview) and returns its response unchanged. Authentication and limits are the same as REST. A failed call returns a GraphQL error: `extensions.status` carries the REST status, and `extensions.code` the Ayrshare error code when the REST error has one. See [Errors](/docs/apis/graphql/errors). Ads operations need the Ads add-on. Without it, every call returns an error with `extensions.status` `403` and `extensions.code` `399`.
## Operations
| Operation | Type | REST endpoint |
| - | - | - |
| `adAccounts` | Query | `GET /ads/{platform}/accounts` |
| `adTargetingSearch` | Query | `GET /ads/{platform}/interests`, `/regions` or `/cities` |
| `ads` | Query | `GET /ads/{platform}/ads` |
| `adHistory` | Query | `GET /ads/{platform}/history` |
| `updateAd` | Mutation | `PUT /ads/{platform}/ads` |
| `boostPost` | Mutation | `POST /ads/{platform}/boost` |
Every operation also takes an optional `profileKey` to act on a User Profile. All of them return the REST response as JSON, so they take no field selection.
## List ad accounts
Start here. Use the `accountId` values in the response wherever an operation asks for `accountId`.
```graphql theme={"system"}
query {
adAccounts(platform: FACEBOOK, limit: 10)
}
```
## Search targeting options
`kind` picks the search: `INTERESTS` for audience targeting, `REGIONS` or `CITIES` for locations. Each kind takes only its own arguments. For example, `countryCode` and `regionId` work only with `CITIES`, and `INTERESTS` needs `search`.
```graphql theme={"system"}
query {
adTargetingSearch(platform: INSTAGRAM, kind: INTERESTS, search: "running")
}
```
```graphql theme={"system"}
query {
adTargetingSearch(platform: FACEBOOK, kind: CITIES, search: "Austin", countryCode: "US")
}
```
## List ads
Pass at least one of `campaignId`, `accountId`, `adId`, `postId` or `socialPostId`. `socialPostId` is the network's own post id, for posts not published through Ayrshare.
```graphql theme={"system"}
query {
ads(platform: FACEBOOK, accountId: "act_1234567890")
}
```
## See what an ad has cost
```graphql theme={"system"}
query {
adHistory(platform: FACEBOOK, startDate: "2026-09-01", endDate: "2026-09-30")
}
```
## Pause or resume an ad
`updateAd` changes real ad spend. `PAUSED` stops spending, `ACTIVE` resumes it, and `DELETED` or `ARCHIVED` retires the ad. The same status is also set on the ad's ad set and campaign, so any other ads in that ad set or campaign change with it. A campaign or ad set the boost was attached to with `attachTo` keeps its own status, and the ad runs only while the campaign and ad set above it are active.
```graphql theme={"system"}
mutation {
updateAd(platform: FACEBOOK, adId: "120210000000000000", status: PAUSED)
}
```
## Boost a post
`boostPost` spends real money from the ad account. It turns a published post into an ad.
* Pass `postId` (the Ayrshare post id) or `socialPostId` (the network's own post id).
* Get `accountId` from `adAccounts`.
* `budget.amount` is in the ad account's currency. Leave `budget` out only when `attachTo.adSetId` adds the ad to an ad set you already run, which keeps its own budget. Leave `targeting` out then too: the ad set keeps its own. See [Boost into an Existing Campaign or Ad Set](/docs/apis/ads/facebook/boost-post#attach-to).
* `schedule`, `targeting`, `callToAction`, `attachTo` and `status` are optional. If you leave out `status` and `schedule`, the ad is **active, starts now and has no end date**, so it keeps spending until you pause it. Set `status: PAUSED` or `schedule.end` to limit it.
* Put settings that only one platform has under `platformOptions`, keyed by platform. On Facebook and Instagram, `adName` and `bidAmount` are **required** there, for example `platformOptions: { facebook: { adName: "Autumn sale", bidAmount: 2 } }`. Without them the boost returns a missing-fields error. With `attachTo.adSetId`, leave `bidAmount` out: the ad set keeps its own bid.
* On Facebook and Instagram, Meta refuses the ad until the ad account has a payment method attached and, in the EU, EEA and Switzerland, personalized ads are turned on for the account linked to the Page. See [Boost Post](/docs/apis/ads/facebook/boost-post).
```graphql theme={"system"}
mutation {
boostPost(
platform: FACEBOOK
accountId: "act_1234567890"
postId: "RNlSOkyxl9G3EDhlPuCK"
goal: "engagement"
budget: { amount: 20, type: DAILY }
schedule: { start: "2026-10-10T09:00:00Z", end: "2026-10-17T09:00:00Z" }
targeting: { ageMin: 25, ageMax: 45, locations: { countries: ["US"] } }
platformOptions: { facebook: { adName: "Autumn sale", bidAmount: 2 } }
idempotencyKey: "4f9c2a7e-1b3d-4c8e-9a6f-2d5b7e0c1a34"
)
}
```
Some arguments take more than one form, so they use the `JSON` type:
* `accountId` takes a string or a number.
* The `schedule` dates take an ISO 8601 date or date-time, such as `"2026-10-10T09:00:00Z"`. Use that form rather than a number. `schedule.end` also takes `0` for no end date.
* Location keys take one value or a list.
* `targeting.interests` takes ids as strings or numbers.
* `platformOptions` takes an object.
> [!TIP]
>
> Send a new `idempotencyKey`, such as a UUID, with each boost. If a call times out or fails, retry with the same key and the same arguments.
>
> * If the first boost finished, the retry returns its response instead of creating and paying for a second ad.
> * If the first boost is still in progress, the retry returns an error with `extensions.status` `409`. Wait, then retry with the same key. Do not switch to a new key.
> * Reusing a key with different arguments also returns `409`.
To stop the ad spending, call `updateAd` with `status: PAUSED`. `boostFacebookPost` and `instagramBoostPost` still work.
The per-platform operations, such as `facebookAdAccounts` and `instagramAds`, still work and are unchanged.
# GraphQL Errors
Source: https://www.ayrshare.com/docs/apis/graphql/errors
Learn why resolver errors return HTTP 200, how to read the errors array and extensions.status, keep partial data, and retry safely with an idempotencyKey.
## Resolver errors arrive with HTTP 200
This is the single most important thing to know before you write error handling.
Following the GraphQL over HTTP specification, a request that reaches our resolvers returns **HTTP 200 even when an operation failed**. The failure is reported inside the response body, not by the status code.
So this check does not work:
```javascript theme={"system"}
// WRONG - will treat a failed request as a success
if (response.ok) {
return "everything worked";
}
```
Check the body instead. A few transport errors, such as 415 for an unsupported `Content-Type`, have an empty body, so check that the response is JSON before parsing it:
```javascript theme={"system"}
if (!response.headers.get("content-type")?.includes("json")) {
throw new Error(`GraphQL request failed with HTTP ${response.status}`);
}
const result = await response.json();
if (result.errors) {
for (const error of result.errors) {
// Errors caught before execution have no extensions, so read it safely.
console.log(error.message, error.extensions?.status);
}
}
```
Some failures are caught before anything runs. None of them reaches a resolver or uses an API call:
A malformed request returns an error status: **HTTP 400** for invalid JSON or a missing `query`, **413** for a body over 64 KB, **405** for a method other than `POST`, and **415** for an unsupported `Content-Type`.
A query that is not valid for the schema returns **HTTP 400** only if your request sends `Accept: application/graphql-response+json`. This covers a syntax error, an unknown field, a wrong argument type, an invalid enum value, and a query over the [query limits](/docs/apis/graphql/limits#query-limits). If you send `Accept: application/json`, or no `Accept` header, the same error returns **HTTP 200**. Either way, the body has an `errors` array and no `data`.
Once execution begins, resolver-level input validation and API failures — including authentication, authorization, upstream rate limits, and upstream 5xx responses — return HTTP 200 with an `errors` array.
## The error shape
Every resolver error carries its HTTP-like API status in `extensions`:
```json theme={"system"}
{
"data": { "postHistory": null },
"errors": [
{
"message": "Rate limit exceeded",
"path": ["postHistory"],
"extensions": {
"status": 429,
"retryAfter": 7
}
}
]
}
```
`message` — a human-readable description. We remove API keys and other credentials from resolver error messages, but an error about a value you sent, such as a variable of the wrong type, can repeat that value. Treat `message` as possibly sensitive before you log it.
`path` — which field in your query failed. With several fields in one request, this is how you tell which.
`extensions.status` — the HTTP status the equivalent REST call would have returned. Switch on this.
`extensions.code` — the numeric Ayrshare error code, when the underlying endpoint supplies one.
`extensions.retryAfter` — present on upstream quota or rate-limit 429s when a retry delay is available. It is not present on the per-request dispatch-budget 429.
Use `extensions.code` with the [error code reference](/docs/errors/overview), or pass it to `explainError`.
## Partial success
If a request asks for several things and only some succeed, you get **both** `data` and `errors`:
```graphql theme={"system"}
{
history: postHistory(lastDays: 7)
analytics: socialAnalytics(platforms: [INSTAGRAM])
}
```
```json theme={"system"}
{
"data": {
"history": { "history": [{ "id": "RkQ8uXg2jT7bNd1Wq0Ya", "status": "success", "post": "Hello world" }], "refId": "9d2a7c41f0b8e6d35a1c", "count": 1, "lastUpdated": "2026-09-24T12:00:00.000Z", "nextUpdate": "2026-09-24T12:00:00.000Z" },
"analytics": null
},
"errors": [
{
"message": "Rate limit exceeded",
"path": ["analytics"],
"extensions": { "status": 429 }
}
]
}
```
`history` succeeded and its data is usable. Only `analytics` needs retrying. Do not discard the whole response because `errors` is present — that would throw away data you have already been billed for.
## Partial success on multi-network posts
A post to several networks can succeed on some and fail on others. This is **not** reported as a GraphQL error, because the operation itself worked — it did exactly what you asked and is telling you what happened.
The result separates the three outcomes:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "Hi"
platforms: [FACEBOOK, INSTAGRAM]
idempotencyKey: "multi-network-hi-001"
}) {
status
postIds { platform postUrl }
errors { platform message code }
blocked { platform message }
}
}
```
`postIds` — networks that published, with their post URLs. Successes only.
`errors` — networks that failed, with the reason.
`blocked` — networks stopped before an attempt, for example by the per-network circuit breaker after repeated deterministic failures. These are separate from provider attempts that returned errors.
`status` is `"error"` if **any** network failed, so it is not a reliable signal that nothing published. Read `postIds` to find out what did.
## Retries
**The GraphQL API never retries automatically.** If a request fails, nothing was silently attempted again on your behalf.
This matters most for mutations. `createPost` exposes `idempotencyKey`: generate a unique key before the first attempt, then reuse the same key and identical payload for every retry. A settled retry replays the original result instead of repeating the write. Do not switch to a new key after an ambiguous response.
`boostFacebookPost` and `instagramBoostPost` also take an `idempotencyKey`, and it protects real money. A retry with the same key and the same arguments returns the original result, marked `idempotentReplayed: true`, instead of creating and paying for a second ad. One key covers your whole account on both Meta platforms, so never reuse a Facebook boost's key for an Instagram boost.
When a key is refused, `extensions.status` tells you why. `createPost` returns 409 while the first attempt is still processing, so wait and retry with the same key, and 400 when the payload differs from the first attempt. The boost mutations return 409 in both cases, and also when the key was used for a boost on the other platform.
Other writes, such as `addComment`, have no idempotency key. If their response is lost, check the social network or API state before deciding whether another attempt is safe.
Two different limits both report 429, and they need different handling. The dispatch budget only rejects root fields once five API calls have already started in the same request, and its message begins `Query exceeds the per-request dispatch budget`: split those fields into requests of at most five, with no need to wait. Any other 429 is a rate limit or quota: wait `extensions.retryAfter` seconds when it is present, otherwise back off before retrying, and retry only the fields that failed. For a 5xx on a **read**, retrying is safe, with one exception: `generatePost` is a query, but a retry calls the AI generator again, counts as another API call, and returns different text. For a 5xx on a write without idempotency, reconcile whether it took effect before retrying.
## Common statuses
| `extensions.status` | Meaning | What to do |
| - | - | - |
| 400 | Invalid input our API rejected | Fix the request; retrying will not help |
| 401 | Missing or invalid API key | Check the `Authorization` header |
| 402 | Plan does not include this feature | Check your plan |
| 403 | Key valid but not permitted for this action | Check profile keys and plan |
| 404 | The thing you asked for does not exist | Verify the id |
| 409 | The idempotency key is in use: the first request is still running, or a boost key was used with different arguments | If the first request is still running, wait and retry unchanged; otherwise use a new key for the new operation |
| 429 | Rate limit, quota, or dispatch budget | If the message names the dispatch budget, split the request. Otherwise wait `retryAfter`, or back off if it is absent |
| 500 | Something went wrong on our side | Retry a read (not `generatePost`, which bills again); reuse the same idempotency key where supported, otherwise verify a write before retrying |
Errors raised by GraphQL itself before execution, such as an unknown field, a wrong argument type, or an invalid enum value, have no `extensions.status`. They return HTTP 400 or 200 depending on your `Accept` header, as described above. They mean the query itself is wrong, and no API call was made or billed.
# GraphQL Limits and Billing
Source: https://www.ayrshare.com/docs/apis/graphql/limits
How Ayrshare counts GraphQL requests against your API quota, plus the query depth, field, alias, directive, and request size limits.
## How requests are counted
**Each executed root operation field is one API call.** A GraphQL request that asks for four root fields is billed as four API calls, exactly as if you had made four REST requests. Aliases at the root count separately because each one dispatches an operation. Nested fields selected from a typed response do not add API calls.
```graphql theme={"system"}
{
history: postHistory(lastDays: 7)
accounts: user
comments: comments(id: "abc123")
analytics: socialAnalytics(platforms: [INSTAGRAM])
}
```
One HTTP request, **four API calls** against your plan's quota and rate limit.
This is intentional and it is the honest model: each root field does the same work on our side as the equivalent REST call, so it costs the same. GraphQL saves you round trips and lets you fetch exactly what you need in one place; it is not a way to get more API calls than your plan includes.
The practical consequences:
Your existing plan limits apply unchanged. See [API limits](/docs/apis/overview) for your plan's allowance.
Asking for a root operation you do not need costs the same as calling it over REST. Selecting the nested response fields you need does not add calls.
A request can be rate limited *partway through*. Root fields that completed keep their data, and only the throttled root fields return errors — see [Partial success](/docs/apis/graphql/errors#partial-success).
## Query limits
The endpoint enforces a few limits on the shape and size of a query. They exist so that a single request cannot consume an unreasonable amount of our capacity, and they are set well above what ordinary use requires — if you are hitting one, it is usually a sign of an accidentally generated query rather than a real need.
| Limit | Value | How you find out |
| - | - | - |
| API calls per request | 5 | `extensions.status` 429 on each extra root field |
| Query nesting depth | 15 | Query rejected before it runs |
| Fields per request | 300 | Query rejected before it runs |
| Aliases per request | 25 | Query rejected before it runs |
| Directives per request | 50 | Query rejected before it runs |
| Request body size | 64 KB | HTTP 413 |
A rejected query returns HTTP 400 if you send `Accept: application/graphql-response+json`, and HTTP 200 otherwise, with an `errors` array and no `data` in both cases. See [Errors](/docs/apis/graphql/errors).
### API calls per request
The important one. A single request can trigger at most **5** API calls, so the four-root-field example above is fine but a twenty-root-field one is not. Once five dispatches have started, additional root fields return a 429 explaining the limit; already-dispatched fields can still return their data.
If you need more, split the query across requests. If you find yourself doing that routinely for a legitimate workload, [get in touch](/docs/help-center/overview) — the limit is deliberately conservative for launch and raising it is straightforward, whereas lowering it later would break integrations, so it starts small.
### Depth, fields, aliases and directives
These are checked before anything runs, so exceeding one costs you no API calls at all.
They are also comfortably clear of a full introspection query, which is around 180 fields and 12 levels deep with no aliases or directives, so a client that fetches the entire schema on startup works normally.
### Request body size
64 KB, which is generous for a query and far too small for a file. This is why media cannot be uploaded through GraphQL — see [Uploading media](/docs/apis/graphql/using-the-api#uploading-media) for the supported path.
## Timeouts
A request is given up to 120 seconds overall. Root fields in a **query may run concurrently**, so its duration is generally driven by its slowest dependency. Root fields in a **mutation run serially**, so several slow mutations can accumulate toward the timeout. Either form can still place up to five concurrent or sequential dispatches' worth of load on the backend; split known-slow work when appropriate.
## What is not limited
**Reading the schema.** Introspection is unauthenticated and unmetered. Browsing what exists is free; running an operation is not.
**Malformed queries.** A query GraphQL rejects — unknown field, wrong type, invalid enum value — never reaches our API and is never billed.
These limits are reviewed against real usage. If one of them is the wrong shape for a legitimate integration, tell us — that feedback is more useful than a workaround.
# GraphQL API Overview
Source: https://www.ayrshare.com/docs/apis/graphql/overview
Query supported Ayrshare social media operations through a single GraphQL endpoint.
The GraphQL API exposes a supported subset of Ayrshare's REST capabilities through a single endpoint. Each exposed field calls the same underlying controller as its REST counterpart, so authentication, entitlements, quotas, and response data follow the REST behavior. REST remains the broader API; use the schema or Explorer to see exactly which operations GraphQL supports.
What it adds is the ability to ask for several things in one request and to discover the currently supported GraphQL surface from the schema itself, without reading documentation page by page. The schema describes every supported request field and the typed selections available on structured responses; operations that return the REST envelope as `JSON` preserve that complete payload.
```
https://api.ayrshare.com/graphql
```
Send a `POST` with a JSON body containing a `query`, exactly as with any GraphQL endpoint. `GET` and `DELETE` return `405 Method Not Allowed` — queries over `GET` are optional in the GraphQL specification and are not supported here.
## Your first query
```bash theme={"system"}
curl -X POST https://api.ayrshare.com/graphql \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ postHistory(lastDays: 7) }"}'
```
The response is the same JSON envelope the [REST history endpoint](/docs/apis/history/get-history) returns, wrapped in GraphQL's `data` field:
```json theme={"system"}
{
"data": {
"postHistory": {
"history": [
{ "id": "RkQ8uXg2jT7bNd1Wq0Ya", "status": "success", "post": "Hello world" }
],
"refId": "9d2a7c41f0b8e6d35a1c",
"count": 1,
"lastUpdated": "2026-09-24T12:00:00.000Z",
"nextUpdate": "2026-09-24T12:00:00.000Z"
}
}
}
```
## Asking for several things at once
The reason to reach for GraphQL is a request like this, which would be four REST calls:
```graphql theme={"system"}
{
history: postHistory(lastDays: 7)
accounts: user
comments: comments(id: "abc123")
analytics: socialAnalytics(platforms: [INSTAGRAM])
}
```
One round trip returns all four. Note that these are **four root operation fields and four API calls for billing purposes**, not one. Selecting nested response fields does not add calls — see [Limits and Billing](/docs/apis/graphql/limits).
## Authentication
Identical to REST. Send your API key as a bearer token:
```
Authorization: Bearer YOUR_API_KEY
```
If your account uses User Profiles, operations that expose `profileKey` can select a profile in either of two ways:
Send `Profile-Key` as the request-wide default.
Pass `profileKey` on an individual field to override that default, allowing one request to act on more than one profile.
The field argument wins when both are present. Account-level or primary-account-only fields do not expose `profileKey` and may reject a `Profile-Key` header; for example, `createProfile` must use the primary API key without that header. Check each field's schema definition for its scope, and see [Managing multiple users](/docs/multiple-users/business-plan-overview) for how profile keys work.
## Try it without writing code
The [GraphQL Explorer](https://app.ayrshare.com/graphql-explorer) is an interactive browser for the current schema. It lists every available GraphQL operation with its arguments and descriptions, autocompletes as you type, and runs queries against your account.
You do not need an API key to browse the schema — the schema is public, the same way this documentation is. You do need one to run a query, because every operation goes through the same authentication as REST.
## Should you use GraphQL or REST?
REST remains the primary interface and the one most of our documentation, [SDKs](/docs/packages-guides/overview), and integrations are built around. Reach for GraphQL when:
You need several unrelated pieces of data and want them in one round trip.
You want machine-readable operation names, arguments, input objects, enums, and typed response selections. Most responses remain `JSON` so they preserve the complete REST envelope; `createPost` currently returns a typed `PostResult`.
You are exploring the API and want to see what exists without moving between documentation pages.
Stay with REST when:
You are uploading files. Media bytes cannot travel through a GraphQL request — see [Uploading media](/docs/apis/graphql/using-the-api#uploading-media) for the supported path.
You are using one of our [SDKs or no-code integrations](/docs/packages-guides/overview), which speak REST.
You want the smallest possible dependency footprint. A REST call needs nothing but an HTTP client.
Both interfaces are supported side by side, and you can mix them freely in the same integration.
## Read next
[Using the API](/docs/apis/graphql/using-the-api) — finding operations, argument types, and uploading media.
[Errors](/docs/apis/graphql/errors) — which failures change the HTTP status, why failed operations still return HTTP 200, and how to handle partial success.
[Limits and Billing](/docs/apis/graphql/limits) — query size caps and how requests are counted.
# Using the GraphQL API
Source: https://www.ayrshare.com/docs/apis/graphql/using-the-api
Find operations, understand argument types and enums, create posts, and upload media through the Ayrshare GraphQL API.
## Finding an operation
The schema is authoritative for the supported GraphQL subset: operation roots, names, arguments, input types, and enum values. Descriptions provide usage guidance, and the [GraphQL Explorer](https://app.ayrshare.com/graphql-explorer) exposes both. For REST-only capabilities, use the [REST API reference](/docs/apis/overview).
Operation names follow the REST endpoints they reach, in camelCase. `GET /history` is `postHistory`, `GET /analytics/social` is `socialAnalytics`, `POST /post` is `createPost`.
## Queries read, mutations write
Queries are reads and validations that change nothing on our side. Mutations publish or change state, start work, or send email. Neither the REST HTTP verb nor the cost of a call decides the root:
`validatePost` checks a post without publishing it.
`validateMedia` checks that a media URL is reachable.
`generatePost` is a query because nothing on our side changes. It still counts as an API call and returns different text each time, so make sure a client cache or automatic refetch does not repeat it without you noticing.
`mediaUploadUrl` is a mutation because it creates an upload URL for your account.
`linkAnalytics` is a mutation because it can request an emailed report.
`userBatch` is a mutation because it starts an export job.
Use the Explorer or schema to confirm the root for each supported operation.
## Creating a post
`createPost` takes a single `input` argument, so the whole post is one object:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "Hello from GraphQL"
platforms: [LINKEDIN]
idempotencyKey: "post-2026-09-01-001"
}) {
status
id
}
}
```
The input mirrors the documented [REST post endpoint](/docs/apis/post/post), including per-network option objects and the REST forms that accept more than one JSON shape:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "New video"
platforms: [YOUTUBE]
mediaUrls: ["https://example.com/video.mp4"]
idempotencyKey: "youtube-video-001"
youTubeOptions: {
title: "My video"
visibility: PUBLIC
}
}) {
status
id
postIds { platform postUrl }
}
}
```
A post can succeed on one network and fail on another. That is not a request failure — see [Errors](/docs/apis/graphql/errors#partial-success-on-multi-network-posts).
## Argument types
Most arguments are ordinary strings, numbers and booleans. Three cases are worth knowing about.
### Enums
Many string arguments with a closed supported set are GraphQL enums, written **unquoted and upper case**:
```graphql theme={"system"}
{ socialAnalytics(platforms: [INSTAGRAM, TIKTOK]) }
```
Not `"instagram"`. The server maps each accepted enum to the exact value expected by the underlying REST controller — often, but not always, a lowercase string. An invalid enum is rejected during GraphQL validation before the operation runs, so a typo costs you nothing.
Some arguments share a name across operations but accept different values, because the endpoints genuinely differ. `reviews(platform:)` accepts only `GMB` and `FACEBOOK`, since those are the only networks with reviews. Your client's autocomplete will show the correct set for each operation.
### The JSON scalar
A few arguments are typed as `JSON` rather than a specific type. This happens where a value legitimately has more than one shape and no single GraphQL type could describe it honestly:
`explainError(code:)` accepts `215` or `"215"`, because customers hold error codes as both.
`createPost(input:)` uses JSON for fields such as `post` and `mediaUrls`, which can be shared values or per-platform objects.
`createAutomation(triggers:, actions:)` take arrays whose fields depend on each entry's `type`.
`boostFacebookPost(interests:)` accepts Meta interest ids as strings or numbers.
Pass the same value you would send in the corresponding REST body. A `JSON` argument is not a loophole: the resolver validates it before dispatch, so an invalid value returns a validation error and consumes no API call.
### Optional arguments and nulls
For optional arguments and optional fields inside input objects, explicit `null` is normalized to omission. Null members inside lists are preserved when the list type permits them.
## Reading responses
Most operations return a `JSON` scalar containing the complete REST response envelope, so select the root field without subfields. `createPost` currently returns a typed `PostResult`, so select its fields:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "Hi"
platforms: [LINKEDIN]
idempotencyKey: "linkedin-hi-001"
}) {
status
id
postIds { platform postUrl }
errors { platform message code }
}
}
```
Typed response types such as `PostResult` also include `raw: JSON!`, containing the complete unmodified REST response:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "Hi"
platforms: [LINKEDIN]
idempotencyKey: "linkedin-hi-001"
}) {
status
raw
}
}
```
`raw` is permanent and is there so a new field appearing in the REST response is never unreachable from GraphQL while we catch up on typing it. If you need something the typed fields do not expose, ask for `raw`.
## Uploading media
**Media bytes cannot travel through a GraphQL request.** A GraphQL request is a single JSON document under a 64 KB limit, so there is no field that accepts a file, and encoding an image as base64 inside the query would exceed that limit for anything larger than a thumbnail.
The supported path avoids the problem entirely, and is faster than uploading through an API in any case, because your bytes go straight to storage:
1. Ask for an upload URL:
```graphql theme={"system"}
mutation { mediaUploadUrl(contentType: "image/jpeg") }
```
2. Read `data.mediaUploadUrl.uploadUrl`, `accessUrl`, and `contentType` from the returned JSON.
3. `PUT` your file **directly to `uploadUrl`**, not to the Ayrshare API, setting the request's `Content-Type` header to the returned `contentType`.
4. After the upload succeeds, pass `accessUrl` in `createPost.input.mediaUrls`:
```graphql theme={"system"}
mutation {
createPost(input: {
post: "With a photo"
platforms: [INSTAGRAM]
mediaUrls: ["https://the-returned-access-url"]
idempotencyKey: "instagram-photo-001"
}) {
status
postIds { platform postUrl }
}
}
```
Treat `uploadUrl` as a short-lived write credential and do not log or expose it. `accessUrl` is the media URL used when creating the post.
You can also keep using the [REST upload endpoints](/docs/apis/media/upload-media) and reference the resulting URLs from GraphQL. The two interfaces share the same media library.
## Read next
[Errors](/docs/apis/graphql/errors) — HTTP status codes, error shapes, and partial success.
[Limits and Billing](/docs/apis/graphql/limits) — query size caps and how requests are counted.
# Auto Hashtags
Source: https://www.ayrshare.com/docs/apis/hashtags/auto-hashtags
POST /hashtags/auto
Add relevant AI-generated hashtags to your post text, embedded within the text or placed at the end, with a max hashtag count and post language code.
When creating your post, add relevant hashtags by either embedding them within the text or placing them at the end.
Our advanced AI system will analyze your content to generate appropriate hashtags based on the subject matter.
Although you can use any language for your post, writing in English typically yields the best results for our hashtag generation process.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**post** `string` *required*
Post text to add the hashtags. Max length 1,000 characters.
**position** `string`
validate
**max** `integer` (default: 2)
Integer of max number of Hashtags to add. Max value range 1 to 10. Note: Instagram allows a maximum of 5 hashtags per post.
**language** `string`
Specify the [language code](/docs/iso-codes/language) of the language of the post to assist the hashtag algorithm.
```json theme={"system"}
{
"language": "fr"
}
```
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_Key" \
-d '{"post": "Today is a great day!", "max": 3, "position": "auto"}'
-X POST https://api.ayrshare.com/api/hashtags/auto
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch(`https://api.ayrshare.com/api/hashtags/auto`, {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
post: "Today is a great day!", // required
max: 3, // optional, range 1-5
position: "auto" // optional, "auto" or "end"
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {
'post': 'Today is a great day!',
'max': 3, # optional, range 1-5
'position': 'auto' # optional, 'auto' or 'end'
}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/hashtags/auto',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/hashtags/auto',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'json' => [
'post' => 'Today is a great day!',
'max' => 3,
'position' => 'auto'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```javascript 200 Hashtagged Post Returned theme={"system"}
{
post: "Disney’s trouble with Oswald the #LuckyRabbit is a great lesson #ForStartups in a crisis"
}
```
# Check Banned Hashtags
Source: https://www.ayrshare.com/docs/apis/hashtags/check-hashtags
GET /hashtags/banned
Check whether Instagram or other social networks have banned a hashtag. Pass the hashtag with or without the # sign and get back a true or false result.
A banned hashtag checker to validate if the given hashtag has been banned by Instagram or other social networks.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**hashtag** `string` *required*
The hashtag to validate. Format: "hashtag" or "#hashtag"
```bash cURL theme={"system"}
curl --location --request GET 'https://api.ayrshare.com/api/hashtags/banned?hashtag=%23bikinibody' \
--header 'Authorization: Bearer API_KEY'
```
```javascript JavaScript theme={"system"}
const myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer API_KEY");
const urlencoded = new URLSearchParams();
const requestOptions = {
method: 'GET',
headers: myHeaders,
};
fetch("https://api.ayrshare.com/api/hashtags/banned?hashtag=%23bikinibody", requestOptions)
.then(response => response.json())
.then(result => console.log(result))
.catch(error => console.log('error', error));
```
```python Python theme={"system"}
import requests
# Your API key
API_KEY = "Bearer API_KEY"
# Headers
headers = {
"Authorization": API_KEY
}
# URL
url = "https://api.ayrshare.com/api/hashtags/banned?hashtag=%23bikinibody"
# Making the GET request
response = requests.get(url, headers=headers)
# Checking if the request was successful
if response.status_code == 200:
# Parse JSON response
result = response.json()
print(result)
else:
print("Error:", response.status_code)
```
```php PHP theme={"system"}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task Main(string[] args)
{
// Your API key
string apiKey = "Bearer API_KEY";
// URL
string url = "https://api.ayrshare.com/api/hashtags/banned?hashtag=%23bikinibody";
using (var httpClient = new HttpClient())
{
// Set the Authorization header with your API key
httpClient.DefaultRequestHeaders.Add("Authorization", apiKey);
try
{
// Make the GET request
HttpResponseMessage response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
// Read the response content as a string
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request exception: {e.Message}");
}
}
}
}
}
```
```json 200 theme={"system"}
{
"hashtag": "#bikinibody",
"banned": true
}
```
```json 400 theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Hashtags API Overview
Source: https://www.ayrshare.com/docs/apis/hashtags/overview
Hashtags API: add AI-generated hashtags to post text, get TikTok hashtag suggestions, check for banned hashtags, and search Instagram posts by hashtag.
## Hashtags API Features
The Hashtags API provides powerful tools to enhance your social media content with relevant hashtags:
**Intelligent Hashtag Generation:** Automatically create optimized hashtags based
on your content's keywords, leveraging real-time popularity data to maximize visibility and
engagement.
**Cross-Platform Support:** Generate platform-specific hashtags tailored for
Facebook, Instagram, LinkedIn, Threads, TikTok, Twitter, and YouTube to optimize performance on each
network.
**Instagram Hashtag Research:** Search and analyze the most popular or relevant
Instagram posts for any given hashtag to inform your content strategy and identify trending
topics.
**Performance Optimization:** Improve post discoverability and reach by
incorporating trending, relevant hashtags that align with your content and target audience.
# Recommend Hashtags
Source: https://www.ayrshare.com/docs/apis/hashtags/recommend-hashtags
GET /hashtags/recommend
Get recommended hashtags from TikTok based on a single keyword, with data and view counts for each suggestion. Spaces in the keyword will be removed.
Get suggestions for hashtags based on a keyword. Data and view counts are generated from TikTok.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**keyword** `string` *required*
A single keyword to get recommended hashtags from TikTok. Spaces in the keyword will be removed.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/hashtags/recommend?keyword=apple
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/hashtags/recommend?keyword=apple", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/hashtags/recommend?keyword=apple', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/hashtags/recommend?keyword=apple',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HashtagsRecommendGETRequest_csharp
{
class HashtagsRecommend
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/hashtags/recommend?keyword=apple";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"keyword": "apple",
"recommendations": [
{
"viewCount": 71950998550, // The number of views that the recommended hashtag has received.
"name": "apple" // Suggested hashtag
},
{
"viewCount": 6318280101,
"name": "applewatch"
},
{
"viewCount": 2486358394,
"name": "applepencil"
},
{
"viewCount": 2142842131,
"name": "applemusic"
},
{
"viewCount": 1506224079,
"name": "applesquad"
},
{
"viewCount": 1307287255,
"name": "apples"
},
{
"viewCount": 1008293028,
"name": "applepie"
},
{
"viewCount": 788388838,
"name": "applejuice"
},
{
"viewCount": 1024901638,
"name": "appletv"
},
{
"viewCount": 679033194,
"name": "applechallenge"
}
]
}
```
```json 400: Bad Request Missing keyword theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Search Hashtags
Source: https://www.ayrshare.com/docs/apis/hashtags/search-hashtags
GET /hashtags/search
Find public Instagram Media that has been tagged with specific hashtags
Search based on a hashtag keyword to get a list of Instagram media posts that have been tagged with that hashtag.
You can query a maximum of 30 unique hashtags on behalf of an Instagram Business or Creator
Account within a rolling, 7-day period. Once you query a hashtag, it will count against this
limit for 7 days. Subsequent queries on the same hashtag within this time frame will not count
against your limit, and will not reset its initial query 7-day timer.
You cannot comment on hashtagged media objects discovered through the API.
Hashtags on Stories are not supported.
Emojis in hashtag queries are not supported.
The API will return an error for any requests that include hashtags that we have deemed
sensitive or offensive.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**keyword** `string` *required*
The keyword to search for matching hashtags.
```bash Search for the hashtag "wisdom" theme={"system"}
https://api.ayrshare.com/api/hashtags/search?keyword=wisdom
```
**searchType** `string` (default: top)
The type of search to perform. Valid values: `top`, `recent`. Top will return the most popular
hashtags. Recent will return the most recent hashtags. Default: `top`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/hashtags/search?keyword=wisdom&searchType=top
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/hashtags/search?keyword=wisdom&searchType=top", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/hashtags/search?keyword=wisdom&searchType=top', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/hashtags/search?keyword=wisdom&searchType=top',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HashtagsSearchGETRequest_csharp
{
class HashtagsSearch
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/hashtags/search?keyword=wisdom&searchType=top";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"hashtag": {
"id": "17843798701036069",
"name": "wisdom"
},
"searchResults": [
{
"caption": "🙂96% Percent of People have no clue this method exists ... For manifesting money , there is powerful a manifestation technique it changes your beliefs on reality in the deepest level of the subconscious mind it literally changed my life give it an honest try . Click the link in our bio to see if this works for you too . (worked for me )",
"children": {
"data": [
{
"id": "18364392676136006"
},
{
"id": "18008030660493382"
},
{
"id": "17866325790184001"
}
]
},
"commentsCount": 174,
"id": "17861532840277137",
"likeCount": 1389,
"mediaType": "CAROUSEL_ALBUM",
"mediaUrl": "https://scontent-sjc3-1.cdninstagram.com/v/t51.29350-15/466382723_420103424530861_7360358065352125344_n.jpg?_nc_cat=109&ccb=1-7&_nc_sid=18de74&_nc_ohc=c-_PWScwGQAQ7kNvgFPrTeK&_nc_zt=23&_nc_ht=scontent-sjc3-1.cdninstagram.com&edm=APCawUEEAAAA&_nc_gid=AT1Ax3hZh5lt3kJskGj3Hj6&oh=00_AYBDkqi4pSdedYxDfUnAm9c93AlyXabWzJM81UySCuIpDg&oe=67406A28",
"permalink": "https://www.instagram.com/p/DCdDF6_Sa31/",
"timestamp": "2024-11-17T01:22:29+0000"
},
{
"caption": "🙂96% Percent of People have no clue this method exists ... For manifesting money , there is powerful a manifestation technique it changes your beliefs on reality in the deepest level of the subconscious mind it literally changed my life give it an honest try . Click the link in our bio to see if this works for you too . (worked for me )",
"children": {
"data": [
{
"id": "17854668297321577"
},
{
"id": "17953717721743238"
}
]
},
"commentsCount": 40,
"id": "18054468655821869",
"likeCount": 371,
"mediaType": "CAROUSEL_ALBUM",
"mediaUrl": "https://scontent-sjc3-1.cdninstagram.com/v/t51.29350-15/467316183_1100515004943506_6021956524402070992_n.jpg?_nc_cat=101&ccb=1-7&_nc_sid=18de74&_nc_ohc=LQVF-7HyOE4Q7kNvgEPZ98d&_nc_zt=23&_nc_ht=scontent-sjc3-1.cdninstagram.com&edm=APCawUEEAAAA&_nc_gid=AT1Ax3hZh5lt3kJskGj3Hj6&oh=00_AYArRIS7xth-eI3rRWlvLhMklRJLBIH5rZ66akHfuSnxjg&oe=6740726A",
"permalink": "https://www.instagram.com/p/DCdDZb5SzUj/",
"timestamp": "2024-11-17T01:25:09+0000"
},
{
"caption": "Setting intentions rather than expectations is a valuable way to keep you from overthinking, help you stay closer to reality, and move with mental clarity. \n\nYou can develop this kind of mindful skill by joining my 8-week "Slow School" live online course. It starts Dec 3rd. Check the link in my bio to learn more 🖤",
"children": {
"data": [
{
"id": "18471207271027384"
},
{
"id": "17875576494200786"
}
]
},
"commentsCount": 33,
"id": "18057421303730368",
"likeCount": 5601,
"mediaType": "CAROUSEL_ALBUM",
"mediaUrl": "https://scontent-sjc3-1.cdninstagram.com/v/t51.2885-15/467022186_992465279306045_4614070861842639887_n.jpg?_nc_cat=105&ccb=1-7&_nc_sid=18de74&_nc_ohc=pchds80KU9oQ7kNvgEaCWkZ&_nc_zt=23&_nc_ht=scontent-sjc3-1.cdninstagram.com&edm=APCawUEEAAAA&oh=00_AYDqG9VA4vJzBvXw6nzFTXc7rU0xVnsTJBOpptBbCEI9Gw&oe=67408C3D",
"permalink": "https://www.instagram.com/p/DCZT369M_07/",
"timestamp": "2024-11-15T14:32:12+0000"
},
"count": 25,
"lastUpdated": "2024-11-19T20:09:02.830Z",
"nextUpdate": "2024-11-19T20:20:02.830Z"
]
}
```
```json 400: Bad Request theme={"system"}
{
"action": "hashtag search",
"status": "error",
"code": 380,
"message": "Error searching Instagram Hashtags. Could not find a matching hashtag"
}
```
# Posts History
Source: https://www.ayrshare.com/docs/apis/history/get-history
GET /history
Retrieve posts sent through Ayrshare from newest to oldest, filtered by date range, status, social network, post type, auto repost ID, or the last n days.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get a history of posts sent via Ayrshare, in descending order (most recent to oldest).
With this endpoint you can retrieve posts from a specific date range, status, social network, or the last n days.
History `status` fields values:
`awaiting approval`: Posts are awaiting to be approved via the [approval
workflow](/docs/apis/post/overview#approval-workflow).
`deleted`: Post has been deleted. Note: deleted posts are only returned with the status query
filter. Please see below.
`error`: An error occurred with one or more social networks.
`paused`: A scheduled post that has been paused.
`pending`: The post has not yet been processed. Typically a scheduled post.
`success`: The post was successfully sent to all social networks.
Additional information:
The [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id) is returned in the response `id` field.
To retrieve posts that originated outside of Ayrshare, such as posts manually created directly
at the social network, use the [history platform](/docs/apis/history/history-platform) endpoint.
If only an individual post is required, use the [post history by
id](/docs/apis/history/get-history-id) endpoint.
The history endpoint JSON results are cached for 1 minute if the `limit` is greater than the
default value of 25.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**limit** `number` (default: 25)
Returns the last *n* number of posts. For example, if you only want the most recent post, set `limit=1`.
Default: `25` posts. Max value: `1000`.
**platforms** `array`
Filter by social network platforms. Platform values: `bluesky`, `facebook`, `gmb`, `instagram`,
`linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`,
`youtube`. Note, uses `OR` logic: `["facebook", "instagram"]` returns posts from either.
**startDate** `string`
Return posts from and including this start date. Start date for the history in ISO 8601 format.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`. Please see
[utctime](https://www.utctime.net/) for more examples.
**endDate** `string`
Return posts up to and including this end date. End date for the history in ISO 8601 format. For
example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`. Please see
[utctime](https://www.utctime.net/) for more examples.
**lastDays** `number` (default: 30)
Returns the last n days of posts by the publish date, i.e. `scheduleDate`. Default 30 days.
The `lastDays` will be ignored if `startDate` and `endDate` are provided.
If the value is zero 0 will return the entire history of posts.
If the value is greater than 0, it will return the last n days of posts.
For example, `lastDays=5` returns the last 5 days of posts and `lastDays=0` returns all posts
determined by the `limit`.
**status** `string`
Filter by current status of post. Valid values: `success`, `error`, `processing`, `pending`, `paused`, `deleted`, and `awaiting approval`.
Processing indicates the post is currently being sent. Pending indicates the post is scheduled to be posted at a future date.
> [!NOTE]
>
> Deleted posts are not returned by default. They are only returned with the status query filter set to `status=deleted`.
**type** `string`
The type of post to retrieve, either post that were sent immediately or scheduled post using the
`scheduleDate` field. Values: `immediate` or `scheduled`.
**autoRepostId** `string`
When creating an auto repost an ID is assigned to track that series of posts.
Retrieve posts by auto repost ID or use the value of `all` to get all auto reposts.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/history?startDate=2025-01-01T12:30:00Z&endDate=2025-03-01T12:30:00&limit=100
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/history?startDate=2025-01-01T12:30:00Z&endDate=2025-03-01T12:30:00&limit=100", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/history?startDate=2025-01-01T12:30:00Z&endDate=2025-03-01T12:30:00&limit=100', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistoryGETRequest_csharp
{
class History
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/history?startDate=2025-01-01T12:30:00Z&endDate=2025-03-01T12:30:00&limit=100";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"history": [
{
"errors": [],
"post": "This is the post I sent",
"platforms": [
"twitter",
"facebook"
],
"postIds": [
{
"status": "success",
"id": "1288968500063775749", // Twitter Social Post ID
"platform": "twitter"
},
{
"id": "104923907983682_108683297607743", // Facebook Social Post ID
"status": "success",
"platform": "facebook"
}
],
"urls": [],
"type": "now",
"notes": "Approved by John Smith", // Reference notes set via /post
"created": "2022-05-20T17:25:06Z",
"status": "deleted",
"scheduleDate": { // In a future release changed to "scheduleDate": "2020-11-05T12:21:29Z"
"_seconds": 1604578889,
"_nanoseconds": 211000000,
"utc": "2020-11-05T12:21:29Z"
},
"id": "rhn6u7wwz2WxGv6MZGK9" // Ayrshare Top-Level Post ID
},
{
"status": "success",
"platforms": [
"twitter",
"facebook"
],
"created": "2022-05-20T17:25:06Z",
"post": "Sometimes we need to take a break for lunch.",
"scheduleDate": { // In a future release changed to "scheduleDate": "2020-11-05T12:21:29Z"
"_seconds": 1604578889,
"_nanoseconds": 211000000,
"utc": "2020-11-05T12:21:29Z"
},
"type": "now",
"postIds": [
{
"platform": "twitter",
"id": "1288890036000983105", // Twitter Social Post ID
"status": "success"
},
{
"id": "104923907983682_108329970009742", // Facebook Social Post ID
"status": "success",
"platform": "facebook",
"isVideo": true // Video post
}
],
"errors": [],
"urls": [],
"id": "wWIY0OEirdNeYSJYm1Xa" // Ayrshare Post ID
},
{ // Awaiting approval post - Approved by user
"approved": true,
"approvedBy": "9abf1426d6ce9122ef11c7222e1",
"approvedDate": "2025-06-06T12:28:12Z",
"created": "2025-06-06T12:27:56Z",
"errors": [],
"id": "sujQsrXtroJU0NEOlY38",
"mediaUrls": [],
"platforms": [
"twitter"
],
"post": "I failed my way to success. - Thomas Edison",
"postIds": [
{
"status": "success",
"id": "193096515330813",
"postUrl": "https://twitter.com/RetiretyHQ/status/19309651533081",
"platform": "twitter"
}
],
"profileTitle": "Primary Profile",
"refId": "9abf1426d6ce9122ef11c7222e1",
"requiresApproval": true,
"scheduleDate": "2025-06-06T12:27:56Z",
"shortenLinks": false,
"status": "success",
"type": "now"
},
{ // Awaiting approval post - Rejected by user
"approved": false,
"created": "2025-06-06T12:23:48Z",
"id": "XL5xHeNK8HGTg07qxzmd",
"mediaUrls": [],
"platforms": [
"twitter"
],
"post": " Honesty is the first chapter in the book of wisdom. - Thomas Jefferson",
"profileTitle": "Primary Profile",
"refId": "9abf1426d6ce9122ef11c72bd62eddw2",
"rejectedBy": "9abf1426d6ce9122ef11c7222e1",
"rejectedDate": "2025-06-06T12:23:58Z",
"requiresApproval": true,
"scheduleDate": "2025-06-06T12:23:48Z",
"shortenLinks": false,
"status": "awaiting approval",
"type": "now"
}
],
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"count": 100,
"lastUpdated": "2025-04-05T22:44:14.209Z",
"nextUpdate": "2025-04-05T22:45:14.209Z"
}
```
```json 400: History not found theme={"system"}
{
"action": "history",
"status": "error",
"code": 221,
"message": "History not found for the past 30 days. Please see the docs on how to retrieve additional history. .../ayrshare.com/rest-api/endpoints/history#list-history-of-sent-and-scheduled-posts"
}
```
# Post History by ID
Source: https://www.ayrshare.com/docs/apis/history/get-history-id
GET /history/:id
Get the history for a specific post using its Ayrshare Post ID, with an optional searchAllPosts query parameter to search across all User Profiles.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get the history for a specific post. Replace `:id` with the [Ayrshare Post ID](/docs/apis/overview#ayrshare-post-id) returned from [post](/docs/apis/post/post).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
Ayrshare Post ID returned from /post
## Query Parameters
**searchAllPosts** `boolean` (default: false)
Search all posts across all User Profiles for the post ID. Default: `false`
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/history/TBEAAqAMMJoweA9wKHUl
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/history/TBEAAqAMMJoweA9wKHUl", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/history/TBEAAqAMMJoweA9wKHUl', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistoryGETByIDRequest_csharp
{
class HistoryGetById
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/history/TBEAAqAMMJoweA9wKHUl";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"tier": "business",
"status": "success",
"mediaUrls": [
"https://images.ayrshare.com/imgs/GhostBusters.jpg"
],
"postIds": [
{
"platform": "facebook",
"postUrl": "https://www.facebook.com/1105775157895689_361710168628052",
"status": "success",
"id": "105775157895689_361710168628052" // Facebook Social Post ID
}
],
"id": "TBEEAqAMMJoweA8wKHUp", // Ayrshare Post ID
"errors": [],
"platforms": [
"facebook"
],
"scheduleDate": { // In a future release changed to "scheduleDate": "2020-11-05T12:21:29Z"
"_seconds": 1604578889,
"_nanoseconds": 211000000,
"utc": "2020-11-05T12:21:29Z"
},
"createDate": { // deprecated, use created
"_seconds": 1653067506,
"_nanoseconds": 179000000,
"utc": "2022-05-20T17:25:06Z"
},
"created": "2022-05-20T17:25:06Z",
"shortenLinks": true,
"post": "Today is a great day",
"notes": "Approved by John Smith", // reference notes set via /post
"type": "scheduled"
}
```
```json 400: Post not found theme={"system"}
{
"action": "history",
"status": "error",
"code": 221,
"message": "History not found.",
"id": "4W3f3RPr6QSrw8S5Yo8"
}
```
# Get post history for a social platform
Source: https://www.ayrshare.com/docs/apis/history/history-platform
GET /history/:platform
Fetch posts and analytics from Bluesky, Facebook, Instagram, LinkedIn, Pinterest, Snapchat, Threads, TikTok, X, and YouTube, including non-Ayrshare posts.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
This endpoint allows you to fetch both posts and their analytics from major social networks (Bluesky, Facebook, Instagram, LinkedIn, Pinterest, Threads, TikTok, X/Twitter, and YouTube). It works for all posts on these platforms - whether they were created using Ayrshare's API or posted directly through the social network's interface.
More detailed analytics are available with the [analytics endpoint](/docs/apis/analytics/overview).
The IDs returned in from this endpoint are the social networks' native [social post ID](/docs/apis/overview#social-post-id) and not the [Ayrshare post ID](/docs/apis/overview#ayrshare-post-id).
This allows you to get details for any post on a social network, even if it was not created through Ayrshare.
For example, you can retrieve a post that was manually published on facebook.com and use the Facebook [Social Post ID](/docs/apis/overview#social-post-id) returned to [get comments](/docs/apis/comments/get-comments) or [get analytics](/docs/apis/analytics/social-by-id).
If you do not need posts that originated outside of Ayrshare, simply use the [Ayrshare post ID](/docs/apis/overview#ayrshare-post-id) returned in a post with the [comment](/docs/apis/comments/post-comment), [analytics](/docs/apis/analytics/post), or [history](/docs/apis/history/history-social-id) endpoints.
`:platform` = `bluesky`, `facebook`, `instagram`, `linkedin`, `pinterest`, `snapchat`, `threads`, `tiktok`, `twitter`, `youtube`.
Example: `https://api.ayrshare.com/api/history/instagram`
### Copyrighted Media
Instagram and TikTok will exclude all copyrighted media in their responses.
The Instagram `mediaUrl` field is omitted from response if the media contains copyrighted material or has been flagged for a copyright violation.
Examples of copyrighted material can include audio on reels. You can check in the Instagram app under Settings -> Account Status.
TikTok does not return media if the media contains copyrighted material or has been flagged for a copyright violation.
Examples of copyrighted material can include audio on reels (TikTok will mute these videos). You can check in the TikTok app under Activity -> System Notifications.
If such a video is muted, you can add audio in the TikTok app.
Go to the post and select the option to add non-copyrighted audio.
The video will then be returned in this API response.
### Limitations
#### Facebook
Expired/archived Stories (older than 24 hours) are automatically filtered out. Only active Stories are included in the response by default.
Use the `dataType` query parameter to request only `posts` or only `stories`.
Use the `since` and `until` query parameters to filter posts by date range.
#### Instagram
Responses will not include Live Video stories.
Stories are only available for 24 hours.
New stories created when a user reshares a story will not be returned. Reshared
stories include stories created using the Add Yours Templates.
New posts created when a user accepts collaboration requests will not be returned.
Use the `dataType` query parameter to request only `posts` or only `stories`.
#### Snapchat
Story and Saved Story items include a `mediaUrls` array with the downloadable media URL of each Snap in that Story. Spotlights are single-asset and do not include `mediaUrls`.
Fetching per-Snap media requires an additional request per Story, which adds latency. Set the `media` query parameter to `false` to skip it and return Story/Saved Story items without `mediaUrls` for faster, metadata-only responses. Example: `GET https://api.ayrshare.com/api/history/snapchat?media=false`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**platform** `string` *required*
The platform of the posts to retrieve. Values: `bluesky`, `facebook`,
`instagram`, `linkedin`, `pinterest`, `snapchat`, `threads`, `tiktok`,
`twitter`, `youtube`.
## Query Parameters
**limit** `number` (default: 10)
Number of history records to return. Max limit: 500\*
High limits may result in slower response times, so we recommend using a limit of no more than 100.
\*Higher limits are available on Enterprise plans. Please contact your account manager for details.
**skipAnalytics** `boolean` (default: false)
Skip gathering full analytics for Facebook Pages and Instagram. Returns only
the Social Post ID. Should be used if analytics aren't needed (only need the
Social Post ID), for a faster return, or errors occur when limit > 100.
**pagePublished** `boolean` (default: 10)
Facebook only. By default the posts returned are from the Facebook Page feed. You may also choose to only return posts published by the page.
Use feed (default) when you want a more comprehensive view of all content associated with the page, including interactions from other users. Use `pagePublished` when you only want to retrieve posts made by the page itself.
**userId** `string`
X/Twitter only. This parameter allows you to retrieve posts from a specific X/Twitter user by their numeric ID, rather than from your linked account.
For example, to get all posts from the handle `@Google`, you would use their numeric userId `20536157`.
You can find any X/Twitter user's numeric userId by using the [Brands Get User](/docs/apis/listen/brand-user) endpoint.
Note: Use only the API KEY in the header to make this request. Do not include the Profile Key.
**userName** `string`
X/Twitter only. This parameter allows you to retrieve posts from a specific X/Twitter user by their handle, rather than from your linked account.
For example, to get all posts from the handle `@Google`.
Note: Use only the API KEY in the header to make this request. Do not include the Profile Key.
**next** `string`
Cursor-based pagination for retrieving the next page of results. Pass the `next` value from the previous response's `meta.pagination` object to fetch the next set of posts.
**since** `string`
Facebook only. ISO UTC date string to filter posts created on or after this date. Use with `until` for a specific date range.
Example: `since=2026-03-17`
**until** `string`
Facebook only. ISO UTC date string to filter posts created on or before this date. Use with `since` for a specific date range.
Example: `until=2026-03-20`
**dataType** `string`
Facebook and Instagram. Filter the type of content returned. By default, both regular posts and active Stories are returned.
Values:
* `posts` — regular posts only (no Stories)
* `stories` — Stories only
Omit this parameter to return both posts and Stories (default behavior).
Note: For Facebook, expired/archived Stories (older than 24 hours) are automatically filtered out. For Instagram, Stories are only available for 24 hours.
**media** `boolean` (default: true)
Snapchat only. When `true` (default), Story and Saved Story history items include a `mediaUrls` array with each Snap's downloadable media URL. Fetching that media requires an extra request per Story, so set `media=false` to skip it for faster responses when you only need the Story metadata.
## Pagination Response
When using cursor-based pagination (with `next` query parameter), the response includes a `meta.pagination` object containing pagination state.
The `posts` array in each paginated response contains objects with the same structure as shown in the response examples below for each platform.
**meta.pagination** `object`
Pagination metadata for cursor-based navigation through results.
**properties**
**hasMore** `boolean`
Indicates whether there are more results available beyond the current page. When `true`, use the `next` cursor to fetch additional posts.
**next** `string`
An opaque cursor string to pass as the `next` query parameter to retrieve the next page of results. Only present when `hasMore` is `true` or when there are more results to fetch.
**limit** `number`
The number of results requested per page.
## Partial Results
Retrieving history from some platforms takes several requests behind the scenes. If one of those requests fails part-way through, the posts already retrieved are still returned — but the list is **incomplete**, and the response says so with `status: "partial"` instead of `"success"`.
**status** `string`
`"success"` when the full requested history was retrieved, or `"partial"` when only some of it was. A `"partial"` response is not an error: the `posts` it contains are accurate, there are simply fewer of them than exist.
> [!WARNING]
>
> Treat `"partial"` as "this page is incomplete", not as "this is everything". Counting posts, or deriving totals from a `"partial"` response, will understate the real figures. Request again to get the full history — partial responses are never cached, so a retry always re-reads from the platform.
```json Partial response theme={"system"}
{
"status": "partial",
"posts": [
{ "id": "...", "postUrl": "..." }
],
"postsCount": 12,
"lastUpdated": "2026-08-12T09:14:02.118Z",
"nextUpdate": "2026-08-12T10:14:02.118Z"
}
```
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/history/instagram
```
```bash cURL Facebook with Filters theme={"system"}
# Get only regular posts (no Stories) within a date range
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/history/facebook?limit=20&since=2026-03-17&until=2026-03-20&dataType=posts"
```
```bash cURL with Pagination theme={"system"}
# First request
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/history/twitter?limit=10"
# Next page request (use 'next' cursor from previous response)
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/history/twitter?limit=10&next=eyJ0b2tlbiI6IjE3MzkyNjg1MTQ0ODU3MTUi..."
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/history/instagram", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```javascript JavaScript with Pagination theme={"system"}
const API_KEY = "API_KEY";
// Function to fetch posts with pagination (up to maxPosts or until cutoffTime)
async function fetchPosts(platform, { limit = 25, maxPosts = 100, cutoffTime = null } = {}) {
const posts = [];
let nextCursor = null;
let pagePosts = [];
do {
const url = new URL(`https://api.ayrshare.com/api/history/${platform}`);
url.searchParams.set("limit", limit);
if (nextCursor) {
url.searchParams.set("next", nextCursor);
}
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
});
const data = await response.json();
pagePosts = data.posts || [];
// Filter by date if cutoffTime is specified
if (cutoffTime) {
pagePosts = pagePosts.filter(post => new Date(post.created) >= cutoffTime);
}
posts.push(...pagePosts);
// Get cursor for next page
nextCursor = data.meta?.pagination?.hasMore ? data.meta.pagination.next : null;
} while (nextCursor && pagePosts.length > 0 && posts.length < maxPosts);
return posts.slice(0, maxPosts);
}
// Fetch up to 100 Twitter posts
fetchPosts("twitter", { maxPosts: 100 }).then(posts => console.log(posts));
// Fetch posts from the last 30 days (up to 200)
const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
fetchPosts("twitter", { maxPosts: 200, cutoffTime: thirtyDaysAgo }).then(posts => console.log(posts));
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/history/instagram', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistoryPlatformGETRequest_csharp
{
class HistoryPlatform
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/history/TBEAAqAMMJoweA9wKHUl";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success {4, 23, 161, 216, 287, 367, 488, 672} theme={"system"}
{
"status": "success",
"posts": [
// Bluesky Response Example
{
"cid": "bafyreigh7nz7x6pl4atgsextqtcbh33mg66yqt3caeihutn7ojkmpdtnzm", // Bluesky Content ID
"created": "2025-01-06T21:35:08.951Z",
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lfb57nxgxs2e", // Bluesky Social Post ID
"indexedAt": "2025-01-06T21:35:09.156Z",
"labels": [],
"likeCount": 2,
"post": "What a wonderful data day!",
"postUrl": "https://bsky.app/profile/ayrshare.com/post/3lf43nl5jis24",
"quoteCount": 3,
"replyCount": 2,
"repostCount": 1,
"viewer": {
"threadMuted": false,
"embeddingDisabled": false
}
},
// Facebook Response Example
// Note: If the FB page has fewer than 100 likes then not all analytics data available.
{
"clicksUnique": 31,
"created": "2022-06-14T23:43:21Z",
"commentsCount": 13,
"engagedUsers": 3,
"fullPicture": "https://scontent.ford4-1.fna.fbcdn.net/v/t39.30808-6/287997502_758105082307121_n.jpg?stp=dst-jpg_s720x720&_nc_cat=101&ccb=1-7&_nc_sid=8024bb&_nc_ohc=EkktQTp_uWIAX_xtTxV&_nc_ht=scontent.ford4-1.fna&edm=AKK4YLsEAAAA&oh=00_AT-Y5Ht2Pl3MeY-qIBs6BC10SbZ-47Vfcc7DKiQrCnIUA&oe=62AEEECA",
"id": "104619420979033_758005082307127", // Facebook Social Post ID
"impressionsUnique": 0,
"isPopular": false,
"lastUpdated": "2022-06-14T23:43:38.359Z",
"likeCount": 38,
"likedBy": [ // Users who liked the post
{
"id": "7101149746568432",
"name": "John Smith"
}
],
"mediaUrls": [ // If attached photos or video
{
"media": {
"image": {
"height": 412,
"src": "https://scontent-lga3-1.xx.fbcdn.net/v/t15.5256-10/294978304_1014487762575117_6279324362130508123_n.jpg?stp=dst-jpg_s720x720&_nc_cat=101&ccb=1-7&_nc_sid=ad6a45&_nc_ohc=y3bYN-PGN0YAX9kO2qn&_nc_ht=scontent-lga3-1.xx&edm=AKK4YLsEAAAA&oh=00_AT9by7D4y4sjY4BW5RW53JO6BaG2UkpUCCJrrugW7GDW9w&oe=62E23000",
"width": 720
},
// Source if mediaType is video
"source": "https://video-lga3-1.xx.fbcdn.net/v/t39.25447-2/294718581_137662848672158_7246454806912028286_n.mp4?_nc_cat=111&vs=696d8a2ba08f208b&_nc_vs=HBkcFQAYJEdIVU1rUkdlb1RFaE5IMEFBSDdPNmVaQWxKQmtibWRqQUFBRhUAAsgBAEsGiBJwcm9ncmVzc2l2ZV9yZWNpcGUBMQ1zdWJzYW1wbGVfZnBzABB2bWFmX2VuYWJsZV9uc3ViACBtZWFzdXJlX29yaWdpbmFsX3Jlc29sdXRpb25fc3NpbQAoY29tcHV0ZV9zc2ltX29ubHlfYXRfb3JpZ2luYWxfcmVzb2x1dGlvbgARZGlzYWJsZV9wb3N0X3B2cXMAFQAlABwAACac%2FNfk2tKqARWQTigCQzMYC3Z0c19wcmV2aWV3HBdAGZmZmZmZmhhEZGFzaF9pNGxpdGViYXNpY19wYXNzdGhyb3VnaGFsaWduZWRfNDgwX2NyZl8yOF9tYWluXzMuMF9mcmFnXzJfdmlkZW8SABgYdmlkZW9zLnZ0cy5jYWxsYmFjay5wcm9kOBJWSURFT19WSUVXX1JFUVVFU1QbD4gVb2VtX3RhcmdldF9lbmNvZGVfdGFnBm9lcF9zZBNvZW1fcmVxdWVzdF90aW1lX21zATAMb2VtX2NmZ19ydWxlCnNkX3VubXV0ZWQTb2VtX3JvaV9yZWFjaF9jb3VudAI5OBFvZW1faXNfZXhwZXJpbWVudAAMb2VtX3JvaV9ub3RlC3Byb2dyZXNzaXZlEW9lbV9yb2lfdXNlcl90aWVyAB5vZW1fcm9pX3ByZWRpY3RlZF93YXRjaF90aW1lX3MBMBZvZW1fcm9pX3JlY2lwZV9iZW5lZml0BTAuMDAwJW9lbV9yb2lfc3RhdGljX2JlbmVmaXRfY29zdF9ldmFsdWF0b3ILcHJvZ3Jlc3NpdmUMb2VtX3ZpZGVvX2lkEDEyNjk2MTcwNzM3NzgxNTESb2VtX3ZpZGVvX2Fzc2V0X2lkDzc5ODQ2NjE3NDg0NzE4OBVvZW1fdmlkZW9fcmVzb3VyY2VfaWQPMzc1MjU0ODg3ODkwNzAyHG9lbV9zb3VyY2VfdmlkZW9fZW5jb2RpbmdfaWQPNDUwODU0MzgzNjE1MDc5DnZ0c19yZXF1ZXN0X2lkD2EyYThlNWU4OTBkNTRmMyUCHBwcFfDmFxsBVQACGwFVAAIcFQIAAAAWgLq3AwAlxAEbB4gBcwQ1ODkyAmNkCjIwMjItMDctMjQDcmNiATADYXBwBVdhdGNoAmN0GERJUkVDVEVEX1BPU1RfQVRUQUNITUVOVBNvcmlnaW5hbF9kdXJhdGlvbl9zBTYuNDE3AnRzFHByb2dyZXNzaXZlX29yZGVyaW5nAA%3D%3D&ccb=1-7&_nc_sid=a06cc9&_nc_ohc=bA3WO4Dk72QAX9TB9ag&_nc_ht=video-lga3-1.xx&edm=AKK4YLsEAAAA&oh=00_AT_CzNhlIwNxY_7UMWIGQMNOtD-Aw_LGcVHnDWvcQJVkAw&oe=62E2ADE1&_nc_rid=780575090331889"
},
"mediaType": "video", // "link" or "photo"
"url": "https://www.facebook.com/115237020273490/videos/1269617073778151",
"videoId": "1269617073778151"
}
],
"negativeFeedback": 0,
"negativeFeedbackUnique": 0,
"nextUpdate": "2022-06-15T00:18:38.359Z",
"parentId": "5764771300200609_742258953848744", // If a shared post, then the ID of the original post
"post": "What a nice one",
"postUrl": "https://www.facebook.com/104619420979033_758105082307121",
"properties": [ // for videos only
{
"name": "Length",
"text": "00:07"
}
],
"reactions": { // Total lifetime
"like": 1, // Like reactions - The "like" reaction counts include both "like" and "care" reactions.
"love": 1, // Love reactions
"anger": 1, // Anger reactions
"haha": 1, // Haha reactions
"wow": 1, // Wow reactions
"sorry": 1, // Sorry reactions
"total": 6 // Total number of reactions
},
"reactionsByType": 1,
"statusType": "added_video", // Status actions: added_photos, added_video, app_created_story, approved_friend, created_event, created_group, created_note, mobile_status_update, published_story, shared_story, tagged_in_photo, wall_post
"videoViewTime": 0,
"videoViews": 0,
"videoViewsUnique": 0
},
{
"clicksUnique": 45,
"created": "2022-06-14T14:53:01Z",
"engagedUsers": 2,
"fullPicture": "https://external.ford4-1.fna.fbcdn.net/emg1/v/t13/614683800928174083?url=https%3a%2f%2fwww.alphast.com%2fwp-content%2fuploads%2f2022%2f06%2fheadphones-scaled.jpg&fb_obo=1&utld=fbcdn.net&stp=dst-emg0_q75&ccb=13-1&oh=00_AT-MVM6lP-eWgsqCW6IhImKAJaGy71JIGIntjlVnZkJw&oe=62AAAC5C&_nc_sid=5f3a21",
"id": "104619420979033_757752962332339", // Facebook Social Post ID
"impressionsUnique": 1,
"isPopular": false,
"lastUpdated": "2022-06-14T23:40:48.453Z",
"mediaUrls": [
{
"mediaUrl": "link",
"url": "https://www.alphasht.com/finding-the-best-headphones/"
}
],
"negativeFeedback": 0,
"negativeFeedbackUnique": 0,
"nextUpdate": "2022-06-15T00:15:48.453Z",
"post": "Finding The Best Headphones: Finding the best headphones for you depends on several factors.",
"postUrl": "https://www.facebook.com/104619420979033_75775296233233",
"reactions": { // Total lifetime
"like": 1, // Like reactions - The "like" reaction counts include both "like" and "care" reactions.
"love": 1, // Love reactions
"anger": 1, // Anger reactions
"haha": 1, // Haha reactions
"wow": 1, // Wow reactions
"sorry": 1, // Sorry reactions
"total": 6 // Total number of reactions
},
"reactionsByType": 1,
"videoViewTime": 0,
"videoViews": 0,
"videoViewsUnique": 0
},
{ // Facebook Story
"clicksUnique": 0,
"commentsCount": 0,
"created": "2024-03-15T19:36:29.000Z",
"engagedUsers": 0,
"id": "1593984754722429",
"impressionsFanPaidUnique": 0,
"impressionsFanUnique": 0,
"impressionsOrganicUnique": 0,
"impressionsPaidUnique": 0,
"impressionsUnique": 0,
"likeCount": 0,
"mediaId": "1593984754722429",
"mediaType": "video",
"mediaUrls": [
{
"media": {
"image": {
"height": 720,
"src": "https://scontent-lga3-1.xx.fbcdn.net/v/t15.5256-10/451306699",
"width": 405
},
"source": "https://video-lga3-1.xx.fbcdn.net/o1/v/t2/f2/m69/An_VRpjrlZJxZEUVTHHMpd"
},
"mediaType": "video",
"type": "story"
}
],
"negativeFeedback": 0,
"negativeFeedbackUnique": 0,
"post": "",
"postId": "1433249613981178",
"postUrl": "https://facebook.com/stories/114251701259415/UzpfSVNDOjE0MzMyNDk2MTczMTQ1MTE=/?view_single=1",
"reactions": {},
"reactionsByType": 0,
"sharesCount": 0,
"statusType": "published",
"videoViewTime": 0,
"videoViews": 0,
"videoViewsUnique": 0
},
// Instagram Response Example
{
"mediaUrl": "https://scontent.cdninstagram.com/v/t51.2885-15/75317300_249468363049056_6683482523516619964_n.jpg",
"permalink": "https://www.instagram.com/p/B5OBT3ygpfg/",
"commentsCount": 0,
"created": "2022-05-20T17:26:03Z",
"likeCount": 0,
"mediaProductType": "FEED", // Media product type: AD, FEED, STORY or REELS
"mediaType": "IMAGE", // Media type: CAROUSEL_ALBUM, IMAGE, or VIDEO
"username": "thegoodone",
"id": "17833140557332933", // Instagram Social Post ID
"thumbnailUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.2885-15/277243581_1136333620462129_9111753545576538669_n.jpg",
"post": "The #Mandalorian is on tonight instead of Friday",
"postUrl": "https://www.instagram.com/p/B5OBT3ygpfg/" // same as permalink
},
{
"mediaUrl": "https://scontent.cdninstagram.com/v/t51.2885-15/72719989_401282024671549_6704610247561386099_n.jpg", // The media_url field is omitted from responses if the media contains copyrighted material or has been flagged for a copyright violation. Examples of copyrighted material can include audio on reels.
"permalink": "https://www.instagram.com/p/B6_EubJFlMI/",
"commentsCount": 0,
"created": "2022-05-20T17:26:03Z",
"likeCount": 0,
"mediaProductType": "REELS", // Media product type: AD, FEED, STORY or REELS
"mediaType": "VIDEO", // Media type: CAROUSEL_ALBUM, IMAGE, or VIDEO
"username": "thegoodone",
"id": "17933140557332933", // Instagram Social Post ID
"thumbnailUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.2885-15/277243581_1136333620462129_9111753545576538669_n.jpg",
"post": "And finally, number eleven of the best shows of 2019 is #Mandalorian #BabyYoda #Bestof2019",
"postUrl": "https://www.instagram.com/p/B6_EubJFlMI/" // same as permalink
},
{
"commentsCount": 3,
"created": "2024-04-29T16:10:10Z",
"id": "17998949393616",
"isPopular": false,
"lastUpdated": "2024-04-29T22:28:20.499Z",
"likeCount": 14,
"mediaProductType": "FEED",
"mediaType": "CAROUSEL_ALBUM",
"mediaUrls": [
{
"mediaUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.2885-15/441166659",
"id": "18395216701078298"
},
{
"mediaUrl": "https://scontent-lga3-2.cdninstagram.com/o1/v/t16/f1/m82/834208F324481538FE6E565772BC6B8E_video_dashinit.mp4",
"thumbnailUrl": "https://scontent-lga3-2.cdninstagram.com/v/t51.29350-15/440150892_371703612533736_7866608290099350261_n.jpg",
"id": "18041898211827161"
}
],
"nextUpdate": "2024-04-29T22:39:20.499Z",
"post": "Learn to value yourself, which means: to fight for your happiness. - Ayn Rand",
"postUrl": "https://www.instagram.com/p/C6WgGmSPn/",
"username": "johnboy"
},
// LinkedIn Response Example
{
"clickCount": 0,
"commentCount": 1,
"created": "2022-12-28T16:07:38Z",
"engagement": 0.6666666666666666,
"id": "urn:li:share:701389826709", // LinkedIn Social Post ID
"impressionCount": 3,
"lastModified": "2022-12-28T16:07:38Z",
"likeCount": 1,
"post": "left thirty pocket track flower whistle",
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:701389826709",
"publishedAt": "2022-12-28T16:07:38Z",
"reactions": { // Get reactions on a LinkedIn share
"like": 1, // "Like in the UI
"praise": 2, // "Celebrate" in the UI
"maybe": 3, // "Curious" in the UI
"empathy": 3, // "Love" in the UI
"interest": 2 // "Insightful" in the UI
"appreciation": 5 // "Support" in the UI
},
"shareCount": 0,
"status": "PUBLISHED",
"uniqueImpressionsCount": 3,
"visibility": "PUBLIC"
},
{
"clickCount": 0,
"comments": [
"urn:li:comment:(urn:li:activity:722)" // Look up using the Comments endpoint
],
"commentCount": 1,
"commentsState": "OPEN",
"created": "2022-12-28T17:05:11Z",
"engagement": 0.16, // Not available for Personal accounts
"id": "urn:li:share:70139127498", // LinkedIn Social Post ID
"impressionCount": 12, // Not available for Personal accounts
"lastModified": "2022-12-28T17:05:11Z",
"likeBy": [
"urn:li:person:Sgwlpf744" // Look up using the brands endpoint
],
"likeCount": 1,
"likedByCurrentUser": false,
"mediaUrls": [ // please see .../ayrshare.com/additional-info/upcoming-api-changes#changes-in-effect-december-1-2023
{
"id": "urn:li:video:C4E10AQHz0RMm5aAiAg",
"url": "https://media.licdn.com/dms/image/C4E10AQHz0RMm5aAiAg"
},
{
"id": "urn:li:video:C4E10AQHz0RMm5adsH2",
"url": "https://media.licdn.com/dms/image/C4E10AQHz0RMm5adsH2"
}
],
"post": "no monkey gulf organization mood choose earn",
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:70139127498",
"publishedAt": "2022-12-28T17:05:11Z",
"reactions": { // Get reactions on a LinkedIn share
"like": 1, // "Like in the UI
"praise": 2, // "Celebrate" in the UI
"maybe": 3, // "Curious" in the UI
"empathy": 3, // "Love" in the UI
"interest": 2 // "Insightful" in the UI
"appreciation": 5 // "Support" in the UI
},
"shareCount": 0, // Not available for Personal accounts
"status": "PUBLISHED",
"uniqueImpressionsCount": 9, // Not available for Personal accounts
"totalFirstLevelComments": 1,
"visibility": "PUBLIC"
},
// Pinterest Response Example
{
"altText": "",
"boardId": "955255839651522358",
"coverImageUrl": "https://i.pinimg.com/originals/7e/ea/af/7eeaaf4bce167902b06fe3370227e.jpg",
"created": "2022-08-09T22:25:12Z",
"dominantColor": "#353027", // Dominant pin color. Hex number, e.g. "#6E7874"
"duration": 6416, // Time in millisecord
"height": 1062,
"id": "7184649418547260", // Pinterest Social Post ID
"link": "",
"mediaType": "video",
"mediaUrl": "https://i.pinimg.com/originals/7e/ea/af/7eeaaf4bce167902b06fe3370227e.jpg",
"note": "What a great post",
"post": "You are what you believe yourself to be. - Paulo Coelho",
"postUrl": "https://www.pinterest.com/pin/7184649418547260",
"productTags": [],
"title": "My new post",
"username": "ayrshare",
"width": 1856
},
{
"altText": "",
"created": "2022-08-09T22:25:12Z",
"dominantColor": "#7c7c7c",
"height": 600,
"id": "7184649468512979", // Pinterest Social Post ID
"link": "",
"mediaType": "image",
"mediaUrl": "https://i.pinimg.com/originals/1b/0a/02/1b0a02123fba55042474b61bb2aa1.jpg",
"post": "Peace begins with a smile. - Mother Teresa",
"postUrl": "https://www.pinterest.com/pin/7184649468512979",
"username": "ayrshare",
"width": 600
},
{
"altText": "",
"created": "2022-10-17T12:26:05Z",
"dominantColor": "#7f805e",
"id": "36493237618990511", // Pinterest Social Post ID
"link": "",
"mediaType": "multiple_mixed",
"mediaUrls": [
{
"mediaType": "image",
"height": 442,
"width": 249,
"mediaUrl": "https://i.pinimg.com/originals/6c/e2/1f/6ce21fa3936b51cd39c34be6f60dd469.jpg"
},
{
"mediaType": "image",
"height": 589,
"width": 332,
"mediaUrl": "https://i.pinimg.com/originals/9370f0845be155b5640c0f.jpg"
},
{
"mediaType": "image",
"height": 589,
"width": 332,
"mediaUrl": "https://i.pinimg.com/originals/9370f0845be155b5640c0f.jpg"
},
{
"mediaType": "image",
"height": 589,
"width": 332,
"mediaUrl": "https://i.pinimg.com/originals/9370f0845be155b5640c0f.jpg"
},
{
"mediaType": "video",
"duration": 19600,
"height": 1280,
"width": 720,
"mediaUrl": "https://i.pinimg.com/videos/thumbnails/originals/9370f0845be155b5640c0f.jpg"
}
],
"post": " ",
"postUrl": "https://www.pinterest.com/pin/36493237618990511",
"username": "johnsmith"
},
// Snapchat Response Example
{
"id": "43d61bfe-2249-5a46-944a-d36d27c9530b",
"profileId": "43548e97-edf1-44f9-984a-0a38470133",
"thumbnailUrl": "https://cf-st.sc-cdn.net/d/fY3MDNOXp0hxLecKdr4hV",
"type": "PUBLIC_STORY", // PUBLIC_STORY, SAVED_STORY, or SPOTLIGHT
"created": "2025-05-19T19:38:36.789Z",
"ended": "2025-05-19T19:38:36.790Z",
"mediaUrls": [ // per-Snap downloadable media (returned by default; omitted when media=false). Spotlights excluded.
"https://cf-st.sc-cdn.net/d/8Yb8LJ5TWb2yVMSTuF6dg.10.IRZXSOY",
"https://cf-st.sc-cdn.net/d/AbC123dEfG456hIjK.20.IRZXSOY"
]
},
{
"id": "07c99fea-1c72-4f72-bcd9-7dacfae753e0",
"profileId": "43548e97-edf1-44f9-984a-0a3847023",
"thumbnailUrl": "https://cf-st.sc-cdn.net/d/wTf9NQ0mZ4GCDyFj7feQ8",
"type": "SAVED_STORY", // PUBLIC_STORY, SAVED_STORY, or SPOTLIGHT
"title": "A song about snaps",
"created": "2025-05-20T18:09:04.876Z",
"updated": "2025-05-20T18:09:04.876Z",
"mediaUrls": [ // per-Snap downloadable media (returned by default; omitted when media=false)
"https://cf-st.sc-cdn.net/d/wTf9NQ0mZ4GCDyFj7feQ8.30.IRZXSOY"
]
},
// TikTok Response Example
{
"audienceCities": [
{
"city_name": "US Teton County",
"percentage": 3.1
},
{
"city_name": "US Queens",
"percentage": 6.3
}
],
"audienceCountries": [
{
"country": "GB",
"percentage": 0.0029
},
{
"country": "US",
"percentage": 0.9604
}
],
"averageTimeWatched": 2.0132, // Average watch time in seconds. The average time viewers spent watching your video.
"commentsCount": 1, // Total number of lifetime comments. Available 24-48 hours after posting.
"created": "2024-02-26T18:32:00Z",
"embedUrl": "https://www.tiktok.com/static/profile-video?id=73399801580586794&",
"engagementLikes": [ // Engagement likes. The distribution of your viewers who liked your video at specific points in the video's timeline.
{
"percentage": 0,
"second": "179"
},
{
"percentage": 0,
"second": "339"
}
],
"fullVideoWatchedRate": 0.0176, // The percentage of viewers who watched the entire video.
"id": "7339980158058679594",
"impressionSources": [
{
"impression_source": "Search",
"percentage": 0
},
{
"impression_source": "Sound",
"percentage": 0
},
{
"impression_source": "other_profile_vv",
"percentage": 0
},
{
"impression_source": "Follow",
"percentage": 0
},
{
"impression_source": "For You",
"percentage": 1
},
{
"impression_source": "Hashtag",
"percentage": 0
},
{
"impression_source": "Personal Profile",
"percentage": 0
}
],
"likeCount": 2, // Total number of lifetime likes. Available 24-48 hours after posting.
"mediaType": "video",
"musicTitle": "♬ original sound - Mack", // If available
"musicUrl": "https://www.tiktok.com/music/original-sound", // If available
"name": "Mack", // If available
"post": "I have failed over and over and over again in my life and that is why I succeed. - Michael Jordan",
"postUrl": "https://www.tiktok.com/@happy/video/7339980158058679594",
"reach": 653, // The number of people who watched your published content at least once.
"shareUrl": "https://www.tiktok.com/@happy/video/7339980158058679594",
"shareCount": 2, // Total number of lifetime shares. Available 24-48 hours after posting.
"tags": [],
"thumbnailHeight": 1024, // If available
"thumbnailUrl": "https://p19-sign.tiktokcdn-us.com/obj/tos-useast5-p-85c255-tx/oIpT 9CLHAaQfuD67E3jfAFJ5eVDuF5Ibkmszg?x-expires=1709305200&x-signature=ZBTR0Q%2B4KLi8szRCir8eYEcAs1s%3D",
"thumbnailWidth": 576, // If available
"totalTimeWatched": 1373, // Total watch time in seconds. The total time viewers spent watching your video.
"url": "https://www.tiktok.com/@happy",
"videoDuration": 11.332, // Video duration in seconds.
"videoViewRetention": [ // This metric indicates how many of your viewers are still watching after a certain amount of time.
{
"percentage": 0.1,
"second": "155"
},
{
"percentage": 0.07,
"second": "234"
}
],
/* Total number of lifetime users who viewed the video. Available 24-48 hours after posting.
If the user swipes away from the ad then swipes back, it would be counted as 2 impressions.
Therefore, a new video view will be counted again with the new impression session.
*/
"videoViews": 697
},
// Twitter Response Example
// Note: Non-public and organic metrics only available last 30-days of Tweets
{
"created": "2022-05-20T20:07:53.000Z",
"entities": {
"urls": [
{
"start": 77, // Starting character position (inclusive)
"end": 96, // Ending character position (exclusive)
"url": "https://t.co/abc123", // Shortened t.co URL
"expandedUrl": "https://www.ayrshare.com/docs", // Full URL after redirect
"displayUrl": "ayrshare.com/docs", // User-friendly display version
"unwoundUrl": "https://www.ayrshare.com/docs" // Final destination URL
}
],
"hashtags": [
{
"start": 97, // Starting character position (inclusive)
"end": 112, // Ending character position (exclusive)
"tag": "SocialMediaAPI" // Hashtag text without #
}
],
"mentions": [
{
"start": 113, // Starting character position (inclusive)
"end": 122, // Ending character position (exclusive)
"username": "ayrshare" // Username without @
}
],
"cashtags": [
{
"start": 123, // Starting character position (inclusive)
"end": 128, // Ending character position (exclusive)
"tag": "META" // Stock symbol without $
}
],
"annotations": [
{
"start": 46, // Starting character position (inclusive)
"end": 54, // Ending character position (exclusive)
"probability": 0.9456, // Confidence score (0.0 to 1.0)
"type": "Product", // Entity type: Person, Place, Product, Organization, Other
"normalizedText": "Ayrshare" // Standardized entity name
}
]
},
"id": "1187026023991603", // Twitter Social Post ID
"media": [ // Attached media
{
"mediaKey": "7_1555577959943217152",
"previewImageUrl": "https://pbs.twimg.com/ext_tw_video_thumb/1555577959943217152/pu/img/f4M9veIozFjQA_XD.jpg",
"durationMs": 2168, // Duration in milliseconds, videos only
"type": "video" // or "photo", "animated_gif"
}
],
"nonPublicMetrics": { // Use this to determine the total number of impressions generated for the Tweet. Only available for Tweets created the last 30 days.
"impressionCount": 1, // Number of times the Tweet has been viewed
"userProfileClicks": 0
},
"organicMetrics": { // Use this to measure organic engagement for the Tweet. Only available for Tweets created the last 30 days.
"userProfileClicks": 2,
"impressionCount": 4, // Number of times the Tweet has been viewed organically.
"replyCount": 3,
"retweetCount": 1,
"likeCount": 234
},
"possiblySensitive": false, // Tweet contains possibly sensitive information
"post": "Just launched our social media campaign using Ayrshare! Check out the API at https://t.co/abc123 #SocialMediaAPI @ayrshare $META",
"postUrl": "https://twitter.com/myhandle/status/1187026023991603",
"publicMetrics": { // Use this to measure Tweet engagement. Only available for Tweets created the last 30 days.
"retweetCount": 2,
"replyCount": 2,
"likeCount": 5,
"quoteCount": 1
},
"referencedTweets": [ // If a quoted or reply tweet
{
"type": "quoted", // or "replied_to"
"id": "1556718165828276224", // Quoted or replied Tweet ID
"url": "https://www.twitter.com/1556718165828276224" // Quoted or Replied Tweet URL
}
],
"source": "Ayrshare", // Determine if a Twitter user posted from the web, mobile device, or other app.
"text": "To effectively communicate, we must realize that we are all different", // Deprecated, use "post" field
"urls": [ // Available for long Tweets only
{
"start": 866,
"end": 889,
"url": "https://t.co/3r6xz4hBnM",
"expandedUrl": "https://www.cnn.com",
"displayUrl": "cnn.com"
}
],
},
{
"authorId": "1194338779881472",
"created": "2023-12-27T03:24:23.000Z",
"createdAt": "2023-12-27T03:24:23.000Z",
"editHistoryTweetIds": [
"17398682514485715"
],
"id": "17398682514485715",
"media": [ // available for videos and gifs
{
"nonPublicMetrics": {
"playback100Count": 2,
"playback0Count": 55,
"playback50Count": 4,
"playback25Count": 11,
"playback75Count": 2
},
"publicMetrics": {
"viewCount": 27
},
"durationMs": 28240,
"organicMetrics": {
"playback100Count": 2,
"playback0Count": 55,
"playback25Count": 11,
"playback75Count": 2,
"playback50Count": 4,
"viewCount": 27
},
"mediaKey": "7_1739849636389814272",
"previewImageUrl": "https://pbs.twimg.com/ext_tw_video_thumb/1739849636389814272/pu/img/bCkAdkD0R00-ZlkY.jpg",
"width": 1920,
"type": "video",
"height": 1080,
"mediaUrls": [
{
"bitRate": 256000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/480x270/4RoZCqynednVMFgy.mp4?tag=12"
},
{
"bitRate": 832000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/640x360/RYy5iHTIxu7_iyZm.mp4?tag=12"
},
{
"bitRate": 2176000,
"contentType": "video/mp4",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/vid/avc1/1280x720/9UpR90ekFA-9Ucxo.mp4?tag=12"
},
{
"contentType": "application/x-mpegURL",
"mediaUrl": "https://video.twimg.com/ext_tw_video/1739849636389814272/pu/pl/0AVy2xIOLQcsrEZG.m3u8?tag=12&container=fmp4"
}
]
}
],
"nonPublicMetrics": {
"impressionCount": 54,
"userProfileClicks": 0
},
"organicMetrics": {
"userProfileClicks": 0,
"likeCount": 0,
"replyCount": 0,
"impressionCount": 54,
"retweetCount": 0
},
"possiblySensitive": false,
"post": "Family is the most important thing in the world. - Diana, Princess of Wales https://t.co/MgYxnJDer6",
"postUrl": "https://twitter.com/superme/status/17398682514485715",
"publicMetrics": {
"retweetCount": 0,
"replyCount": 0,
"likeCount": 0,
"quoteCount": 0,
"bookmarkCount": 0,
"impressionCount": 54
},
"urls": [ // Available for long Tweets only
{
"start": 866,
"end": 889,
"url": "https://t.co/3r6xz4hBnM",
"expandedUrl": "https://www.cnn.com",
"displayUrl": "cnn.com"
}
],
},
// YouTube Response Example
{
"created": "2022-08-15T19:14:44Z",
"description": "Doubt is not a pleasant condition, but certainty is absurd. - Voltaire", // Deprecated, use "post" field
"id": "Q-4OuHCALVa", // YouTube Social Post ID
"position": 0,
"post": "Doubt is not a pleasant condition, but certainty is absurd. - Voltaire",
"postUrl": "https://www.youtube.com/watch?v=Q-4OuHCALVa",
"privacyStatus": "public",
"license": "youtube", // "youtube" or "creativeCommon"
"embeddable": true, // whether the video can be embedded on external sites
"publicStatsViewable": true, // whether the extended stats panel is publicly viewable
"published": "2025-05-07T16:27:12Z",
"thumbnailUrl": "https://i.ytimg.com/vi/Q-4OuHCALVa/default.jpg",
"title": "Yo time"
},
{
"created": "2022-08-08T22:24:42Z",
"description": "Little author little, one travels far. - J.R.R. Tolkien", // Deprecated, use "post" field
"id": "Btg4ysKnHea", // YouTube Social Post ID
"position": 1,
"post": "Little author little, one travels far. - J.R.R. Tolkien",
"postUrl": "https://www.youtube.com/watch?v=Btg4ysKnHea",
"privacyStatus": "unlisted",
"license": "creativeCommon",
"embeddable": false,
"publicStatsViewable": false,
"published": "2025-05-07T16:27:12Z",
"thumbnailUrl": "https://i.ytimg.com/vi/Btg4ysKnHea/default.jpg",
"title": "Nice one 2"
}
],
"lastUpdated": "2022-11-14T16:04:51.994Z",
"nextUpdate": "2022-11-14T16:37:21.994Z",
"meta": {
"pagination": {
"hasMore": true, // More results available
"next": "eyJ0b2tlbiI6IjE3MzkyNjg1MTQ0ODU3MTUiLCJzb3VyY2UiOiJwdWJsaWMiLCJwbGF0Zm9ybSI6InR3aXR0ZXIifQ==", // Cursor for next page
"limit": 10 // Requested page size
}
}
}
```
```json 400: Error theme={"system"}
{
"status": "error",
"code": 196,
"message": "Instagram is not connected"
}
```
```json 400: Bad Request theme={"system"}
{
// When some of the data is not available from the social networks.
// All available data is still returned
"status": "error",
"posts": [
{... valid data },
{
"action": "analytics",
"code": 187,
"commentsCount": 0,
"created": "2019-03-22T13:31:02Z",
"createdTime": "2019-03-22T13:31:02+0000",
"from": {
"name": "Ayrshare",
"id": "746346376697"
},
"fullPicture": "https://external.fphl1-1.fna.fbcdn.net/emg1/v/t13/",
"id": "746346376697_3993223", // Facebook Social Post ID
"isPopular": false,
"likeCount": 2,
"mediaUrls": [
{
"description": "Dirk Cotton's approach to retirement planning.",
"media": {
"image": {
"height": 720,
"src": "https://external.fphl1-1.fna.fbcdn.net/emg1/v/t13/608115669879219",
"width": 720
}
},
"mediaType": "link",
"title": "The Retirement Planning Regime",
"url": "https://bit.ly/2RwaH"
}
],
"message": "Error getting analytics.",
"post": "Error getting analytics.",
"status": "error",
"statusType": "shared_story"
},
{... valid data }
]
}
```
# Posts History by Social ID
Source: https://www.ayrshare.com/docs/apis/history/history-social-id
GET /history/:socialId
Retrieve history for posts that did not originate via Ayrshare using the Social Post ID, with the searchPlatformId and platform query parameters.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Retrieve history for posts that did not originate via Ayrshare by providing the low-level Social post ID. This [Social Post ID ](/docs/apis/overview#social-post-id)is returned in the `postIds` field of the [post endpoint](/docs/apis/post/overview) or the `id` field from the [get all history endpoint](/docs/apis/history/get-history) or the ID from a post URL, such as from this [Facebook post](https://www.facebook.com/Ayrshare/posts/pfbid02ktEFyG8EydxNGyiytsiC4m3X8kQAfTC6inCCzZJZD9KpAZAiWPpSK5C5HS7cgD54l).
The linked account must be the owner of the post to retrieve the history.
Supported platforms: Facebook, Instagram, LinkedIn, Threads, TikTok, Twitter, and YouTube with the following `platform` values: `facebook`, `instagram`, `linkedin`, `threads`, `tiktok`, `twitter`, `youtube`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**socialId** `string` *required*
The [Social Post ID](/docs/apis/overview#id-types) of the post.
> [!NOTE]
>
> Please see the [/post endpoint](/docs/apis/post/post) Response 200 for another example of the Facebook
> Social Post ID `104923907983682_108329000309742`
## Query Parameters
**searchPlatformId** `boolean` *required*
Always equals `true`
**platform** `string` *required*
Values: `facebook`, `instagram`, `linkedin`, `threads`, `tiktok`, `twitter`, `youtube`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook
```
```javascript JavaScript theme={"system"}
const url = "https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook";
const response = await fetch(url, {
headers: {
'Authorization': 'Bearer API_KEY'
}
});
```
```python Python theme={"system"}
import requests
url = 'https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook'
headers = {'Authorization': 'Bearer API_KEY'}
response = requests.get(url, headers=headers)
```
```php PHP theme={"system"}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer API_KEY'));
$response = curl_exec($ch);
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace HistorySocialIdGETRequest_csharp
{
class HistorySocialId
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```go Go theme={"system"}
client := &http.Client{}
req, err := http.NewRequest("GET", "https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook", nil)
req.Header.Add("Authorization", "Bearer API_KEY")
resp, err := client.Do(req)
```
```ruby Ruby theme={"system"}
require 'net/http'
require 'uri'
uri = URI.parse("https://api.ayrshare.com/api/history/104923907983682_108329000309742?searchPlatformId=true&platform=facebook")
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer API_KEY'
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
http.request(request)
end
```
```json 200: Success theme={"system"}
[
{
"commentsCount": 0,
"created": "2024-01-25T23:01:23Z",
"from": {
"name": "Ayrshare",
"id": "18930271191"
},
"id": "18930271191_39392923",
"mediaUrls": [
{
"media": {
"image": {
"height": 720,
"src": "https://scontent-lga3-2.xx.fbcdn.net/v/...",
"width": 405
},
"source": "https://video-lga3-2.xx.fbcdn.net/o1/..."
},
"title": "🔥 Sneaker time!"
}
],
"messageTags": [
{
"id": "434786466522",
"name": "#sneakerswap365",
"offset": 242,
"length": 15
}
],
"post": "🔥 Sneaker Time for all!",
"postUrl": "https://www.facebook.com/reel/72681340799/",
"lastUpdated": "2024-01-25T23:29:58.014Z",
"nextUpdate": "2024-01-25T23:40:58.014Z"
}
]
```
```json 400: Post Not Found theme={"system"}
{
"action": "get",
"status": "error",
"code": 221,
"message": "The ID was not found. Please verify the id, API or Profile Keys, and required parameters.",
"id": "7268134079",
"lastUpdated": "2024-01-25T23:45:14.311Z",
"nextUpdate": "2024-01-25T23:56:14.311Z"
}
```
# History API Overview
Source: https://www.ayrshare.com/docs/apis/history/overview
Retrieve your Ayrshare post history, or use the platform and social ID history endpoints to fetch posts made directly on the social networks.
The Ayrshare history endpoint allows you to retrieve posts from your connected social media accounts, whether they were published through Ayrshare's platform or posted directly on the social networks themselves.
This means you can access your social media history through a single endpoint, making it easier to analyze and track all your social content regardless of how it was originally posted.
# Create Short Link
Source: https://www.ayrshare.com/docs/apis/links/create-short-link
POST /links
Provide a URL to the /links endpoint and get a shortened link back, with optional UTM parameters such as utmSource and click analytics on browser type.
Provide a URL and a shortened link will be returned. Analytics can then be gathered on the clicks, browser type, etc.
Submitting the same URL for shortening will always result in the same shortened link. To generate a unique shortened link for the same URL, you can add extra query parameters to it. For example, appending a unique identifier like [https://ayrshare.com?uniqueId=123](https://ayrshare.com?uniqueId=123) will create a distinct shortened link.
You may also include UTM parameters, which will be embedded in the shortened URL link.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**url** `string` *required*
URL to be shortened. Must be a valid URL starting with https\://
**utmId** `string`
Used to identify which Google Analytics ads campaign this referral references.
**utmSource** `string`
Used to identify a search engine, newsletter name, or other source.
**utmMedium** `string`
Used to identify a medium such as email or cost-per-click.
**utmCampaign** `string`
Used for keyword analysis. Used to identify a specific product promotion or strategic campaign.
**utmTerm** `string`
Used for paid search. Used to note the keywords for this ad.
**utmContent** `string`
Used for A/B testing and content-targeted ads. Used to differentiate ads or links that point to the same URL.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "https://www.ayrshare.com", "utmSource": "google_ads"}' \
-X POST https://api.ayrshare.com/api/links
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/links", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
url: "https://www.ayrshare.com", // required
utmSource: "google_ads", // optional
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'url': 'https://www.ayrshare.com',
'utmSource': 'google_ads"}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/links',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
require 'vendor/autoload.php';// Composer auto-loader using Guzzle. See .../guzzlephp.org/en/stable/overview.html
$client = new GuzzleHttp\Client();
$res = $client->request(
'POST',
'https://api.ayrshare.com/api/links',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'json' => [
'url' => 'https://www.ayrshare.com',
'utmSource' => 'google_ads', // optional
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"url": "https://www.ayrshare.com",
"utmSource": "google_ads",
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/links",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CreateShortLinkPOSTRequest_csharp
{
class CreateShortLink
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/analytics/links";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"url\" : \"https://www.ayrshare.com\"," +
"\"utmSource\" :\"google_ads\"";
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Success theme={"system"}
{
"created": "2023-07-17T15:20:51.118Z",
"id": "yC0fTl",
"originalUrl": "https://www.ayrshare.com/?utm_source=looking",
"shortUrl": "https://ayrs.io/yC0fTl",
"status": "success",
"utmSource": "looking"
}
```
```json 400: Error Shortening Link theme={"system"}
{
"action": "request",
"status": "error",
"code": 126,
"message": "Shorten URL failed. Please verify the URL is properly formatted and the correct UTM parameters are used."
}
```
# Link Analytics
Source: https://www.ayrshare.com/docs/apis/links/link-analytics
GET /links/:id
Return analytics for all shortened links or a single link ID, with optional fromCreatedDate, toCreatedDate, fromClickDate, and toClickDate UTC filters.
Return analytics for all shortened links or a single link for a given link ID. For example:
`https://api.ayrshare.com/api/links/yC0fTl` returns analytics for ID `yC0fTl`
`https://api.ayrshare.com/api/links` returns all link analytics.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Path Parameters
**id** `string`
Provide the shortened link ID returned from the POST /links request as a path parameter. For
example: `https://api.ayrshare.com/api/links/yC0fTl` If no link ID is provided, all links are
returned.
## Query Parameters
**fromCreatedDate** `string`
Get history of links shortened after this date. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
**toCreatedDate** `string`
Get history of links shortened before this date. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
**fromClickDate** `string`
Get history of links clicked after this date. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
**toClickDate** `string`
Get history of links clicked before this date. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_Key" \
-H 'Content-Type: application/json' \
-X GET https://api.ayrshare.com/api/links/yC0fTl
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch(`https://api.ayrshare.com/api/links/yC0fTl`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/links/yC0fTl',
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/links/yC0fTl',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace LinksGETRequest_csharp
{
class Links
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/links/yC0fTl";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200: Analytics on a Link theme={"system"}
{
"status": "success",
"analytics": {
"browserCounts": {
"chrome": 15
},
"created": "2023-06-29T00:57:12.220Z",
"id": "yC0fTl",
"originalUrl": "https://www.ayrshare.com/?utm_source=google_ads",
"refererCounts": {},
"shortUrl": "https://ayrs.io/yC0fTl",
"socialClicks": {},
"status": "success",
"totalClicks": 15,
"utmSource": "google_ads"
}
}
```
```json 200: Analytics on all Links theme={"system"}
{
"status": "success",
"analytics": [
{
"browserCounts": {
"chrome": 3
},
"created": "2023-04-07T22:33:41.126Z",
"id": "pHXlbv9JlRxrXj6rstdMu",
"originalUrl": "https://www.ayrshare.com/",
"refererCounts": {},
"shortUrl": "https://ayrs.io/pHXlbv9JlRxrXj6rstdMu",
"socialClicks": {},
"totalClicks": 3
},
{
"browserCounts": {
"edge": 13
},
"created": "2023-07-17T15:20:51.118Z",
"id": "yC0fTl",
"originalUrl": "https://www.ayrshare.com/?utm_source=looking",
"refererCounts": {},
"shortUrl": "https://ayrs.io/yC0fTl",
"socialClicks": {},
"totalClicks": 13,
"utmSource": "looking"
}
]
}
```
```json 400: Bad Request theme={"system"}
{
"status": "error",
"message": "Unable to find link ID: yC0fTl",
"code": 187
}
```
# Links API Overview
Source: https://www.ayrshare.com/docs/apis/links/overview
Shorten links to include in social posts with the /links endpoint, track link performance, and use a custom link domain on Business and Enterprise plans.
With the /links endpoint you can shorten links to include in social posts.
The Ayrshare link shortener offers several valuable benefits for social publishing.
Condenses long and complex URLs into more visually appealing, concise, and memorable links.
Analytics and tracking capabilities, allowing you to monitor link performance, engagement, and audience insights.
Save valuable character space, especially on platforms like Twitter where character limits are stringent.
Add a custom link domain with your own url.
## Custom Link Domain
A custom link domain allows you to personalize and brand the domain name used for link shortening and redirection.
Instead of using a generic link shortener domain (e.g., bit.ly, ayr.app), a custom link domain allows you to use own domain name to create shortened links.
Please contact us to set up your custom domain. Available on Business and Enterprise plans.
# Update Short Link
Source: https://www.ayrshare.com/docs/apis/links/update-short-link
PUT /links/:id
Update the destination URL of a short link by the ID returned from POST /links, and see how social media preview caching affects Open Graph preview cards.
Update the destination URL of an existing short link. The short link ID is returned when you first create a short link via `POST /links`.
> [!NOTE]
>
> **Social Media Preview Caching:** Social media platforms cache Open Graph preview cards from the original destination URL. After updating a short link, the preview card shown on social platforms may not match the new destination until the cache expires.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Path Parameters
**id** `string` *required*
The short link ID returned from `POST /links` (e.g., "TkuWEy").
## Body Parameters
**url** `string` *required*
The new destination URL. Must be a valid URL starting with https\://
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url": "https://www.new-destination.com"}' \
-X PUT https://api.ayrshare.com/api/links/TkuWEy
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/links/TkuWEy", {
method: "PUT",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
url: "https://www.new-destination.com", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'url': 'https://www.new-destination.com'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.put('https://api.ayrshare.com/api/links/TkuWEy',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
require 'vendor/autoload.php';// Composer auto-loader using Guzzle. See .../guzzlephp.org/en/stable/overview.html
$client = new GuzzleHttp\Client();
$res = $client->request(
'PUT',
'https://api.ayrshare.com/api/links/TkuWEy',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'json' => [
'url' => 'https://www.new-destination.com',
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "TkuWEy",
"originalUrl": "https://www.new-destination.com",
"shortUrl": "https://ayrs.io/TkuWEy",
"created": "2023-07-17T15:20:51.118Z",
"updated": "2024-01-10T12:30:00.000Z"
}
```
```json 400: Invalid URL Format theme={"system"}
{
"action": "request",
"status": "error",
"code": 126,
"message": "Shorten URL failed. Please verify the URL is properly formatted and the correct UTM parameters are used."
}
```
```json 404: Link Not Found theme={"system"}
{
"action": "link",
"status": "error",
"code": 418,
"message": "Link not found or not owned by user."
}
```
# Get Brand Data
Source: https://www.ayrshare.com/docs/apis/listen/brand-user
GET /brand/byUser
Retrieve public account details from Bluesky, Facebook Pages, Instagram Business or Creator accounts, LinkedIn, X, or YouTube, even if not linked to Ayrshare.
This endpoint allows you to retrieve information about any public social media profile, even if that
profile is not linked to your Ayrshare account. This feature is available for Bluesky, Facebook, Instagram, LinkedIn,
X, and YouTube.
For profiles that are linked to your Ayrshare account, we recommend using the [/analytics](/docs/apis/analytics/social) endpoint instead, as it provides more detailed analytics.
Important: To search for a profile on a specific social platform, your Ayrshare account must have that social network linked.
For example, to search for any Instagram profile, e.g. `@taylorswift`, you must have Instagram linked to your Ayrshare account.
> [!WARNING]
>
> The searched account must be public to access the data. Private social accounts are not available.
>
> Location data is only available for business with public locations.
> Tagging a location in Facebook or Instagram requires an available location.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**platforms** `array` *required*
String array of platforms: `bluesky` ,`instagram`, `facebook`, `linkedin`, `twitter`, or
`youtube`.
**blueskyUser** `string`
Bluesky handle URL encoded. For example: "@ayrshare" or "ayrshare". Required if "bluesky" in
`platforms` array.
**facebookUser** `string`
Facebook Page name URL encoded. For example "@newyorkgiants" or "newyorkgiants".
Facebook personal accounts are not permitted by Facebook.
Required if "facebook" in `platforms` array.
**instagramUser** `string`
Instagram handle URL encoded. For example: "@nygiants" or "nygiants".
Note: Only Instagram Business and Creator accounts can be returned.
Required if "instagram" in `platforms` array.
**linkedinUser** `string`
LinkedIn company (organization) vanity name such as `Linkedin` or `linkedin-marketing-solutions`.
You can also look up a person using their LinkedIn person ID retrieved from the history endpoint, i.e. people who liked
a post. An example ID: `urn:li:person:Z_yXaxh_Et`
Required if "linkedin" in `platforms` array.
**twitterUser** `string`
Twitter handle URL encoded. For example: "@ayrshare" or "ayrshare".
Required if "twitter" in `platforms` array.
**youtubeUser** `string`
YouTube username URL encoded, channel ID, or playlist ID. Note, channel IDs typically begin with
"UC" and playlist IDs begin with "PL". For example, send the username "MelissaEtheridgeVEVO" or
the channel ID as "UCpSUQewzOXg1F0zLmieKCqQ".
You may also use the YouTube handle found in the YouTube URL.
For example, the handle for [https://www.youtube.com/@mkbhd](https://www.youtube.com/@mkbhd) is "@mkbhd".
Be sure to keep the `@` symbol to designate the handle.
Required if "youtube" in `platforms` array.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/brand/byUser?platforms[0]=instagram&platforms[1]=twitter&twitterUser=ayrshare&instagramUser=nygiants
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch(
"https://api.ayrshare.com/api/brand/byUser?platforms[0]=instagram&platforms[1]=twitter&twitterUser=ayrshare&instagramUser=nygiants",
{
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`
}
}
)
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/brand/byUser?platforms[0]=instagram&platforms[1]=twitter&twitterUser=ayrshare&instagramUser=nygiants', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$apiUrl = 'https://api.ayrshare.com/api/brand/byUser?platforms[0]=instagram&platforms[1]=twitter&twitterUser=ayrshare&instagramUser=nygiants';
$apiKey = 'API_KEY'; // Replace 'API_KEY' with your actual API key
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
];
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace BrandByUserGETRequest_csharp
{
class BrandByUser
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/brand/byUser?platforms[0]=instagram&platforms[1]=twitter&twitterUser=ayrshare&instagramUser=nygiants";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var response = await client.GetStringAsync(url);
Console.WriteLine(response);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"bluesky": {
"avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:62musrcyanhro2lydyhlw7ci/bafkreiegtpqhpwgu6tww2ejsdil4ew3blmt6jh3wlq6zhanxo3wj2ib4du@jpeg",
"description": "Ayrshare's Social APIs provide the core infrastructure for social media posting, management, and analytics.",
"displayName": "Ayrshare",
"handle": "ayrshare.com",
"id": "did:plc:62musrcyanhro2lydyhlw7ci",
"indexedAt": "2024-11-29T21:13:18.046Z"
},
"facebook": {
"about": "We invite you to wonder. ",
"description": "Frank Lloyd Wright's architectural masterpiece home to a world-renowned collection of modern and contemporary art.",
"fanCount": 845048,
"followersCount": 874337,
"id": "7640348500",
"isUnclaimed": false, // whether a Facebook Page that was automatically generated has been claimed by the business it represents.
"link": "https://www.facebook.com/7640348500",
"location": {
"city": "New York",
"country": "United States",
"latitude": 40.782910059774,
"longitude": -73.959075808525,
"state": "NY",
"street": "1071 5th Ave",
"zip": "10128"
},
"name": "Solomon R. Guggenheim Museum",
"picture": {
"data": {
"height": 50,
"isSilhouette": false,
"url": "https://scontent.ford4-1.fna.fbcdn.net/v/t39.30808-1/352122020_2000703126929355_7618417261219676343_n.jpg?stp=cp0_dst-jpg_p50x50&_nc_cat=104&ccb=1-7&_nc_sid=4da83f&_nc_ohc=Y1AlQn-HyvwAX8NzExq&_nc_ht=scontent.ford4-1.fna&edm=AJdBtusEAAAA&oh=00_AfD7ms2Nv0b5x1jr_uJKZsXnngQP3dmDrjQDNz_4aaBrCg&oe=65D3ACAE",
"width": 50
}
},
"username": "guggenheimmuseum",
"verificationStatus": "blue_verified", // The verification status of the Facebook Page that represents a business, blue_verified or not_verified.
"website": "http://www.guggenheim.org/"
},
"instagram": {
"biography": "4x Super Bowl Champions #TogetherBlue",
"followersCount": 2278171,
"followsCount": 228,
"id": "17841400118294090", // Instagram Id
"igId": 261763943,
"mediaCount": 8965,
"name": "New York Giants",
"profilePictureUrl": "https://scontent-lga3-2.xx.fbcdn.net/v/t51.2885-15/209249968_563247608171409_1254577321735891919_n.jpg?_nc_cat=1&ccb=1-5&_nc_sid=86c713&_nc_ohc=T673IyEiuasAX_Jj7xu&_nc_ht=scontent-lga3-2.xx&edm=AL-3X8kEAAAA&oh=00_AT8GNdOos4riN7NhrI06a6TkVKgOf5p_RUlsOQUwPRW3VQ&oe=6247F2F0",
"username": "nygiants",
"website": "http://nygnt.co/vgle2"
},
// LinkedIn using the username or handle
"linkedin": {
"localizedName": "LinkedIn",
"name": {
"localized": {
"it_IT": "LinkedIn",
"ru_RU": "LinkedIn",
"pl_PL": "LinkedIn",
"ro_RO": "LinkedIn",
"sv_SE": "LinkedIn"
},
"preferredLocale": {
"country": "US",
"language": "en"
}
},
"id": 1337,
"vanityName": "linkedin",
"organizationType": "PUBLIC_COMPANY",
"locations": [
{
"locationType": "HEADQUARTERS",
"address": {
"geographicArea": "CA",
"country": "US",
"city": "Sunnyvale",
"line1": "1000 W Maude",
"postalCode": "94085"
},
"streetAddressFieldState": "UNSET_OPT_OUT",
"geoLocation": "urn:li:geo:106316449",
"staffCountRange": "SIZE_1"
},
{
"locationType": "OTHER",
"address": {
"geographicArea": "Community of Madrid",
"country": "ES",
"city": "Madrid",
"postalCode": "28046"
},
"streetAddressFieldState": "OPT_OUT",
"geoLocation": "urn:li:geo:106809575",
"staffCountRange": "SIZE_1"
},
{
"locationType": "OTHER",
"address": {
"geographicArea": "ON",
"country": "CA",
"city": "Toronto",
"postalCode": "M5J 2Z2"
},
"streetAddressFieldState": "OPT_OUT",
"geoLocation": "urn:li:geo:108528311",
"staffCountRange": "SIZE_1"
}
],
"specialties": [
{
"locale": {
"country": "US",
"language": "en"
},
"tags": [
"Online Professional Network",
"Jobs",
"People Search",
"Company Search",
"Address Book",
"Advertising",
"Professional Identity",
"Group Collaboration",
"Recruiting"
]
}
],
"website": "https://careers.linkedin.com",
"description": "Founded in 2003, LinkedIn connects the world's professionals to make them more productive and successful. With more than 1 billion members worldwide, including executives from every Fortune 500 company, LinkedIn is the world's largest professional network. The company has a diversified business model with revenue coming from Talent Solutions, Marketing Solutions, Sales Solutions and Premium Subscriptions products. Headquartered in Silicon Valley, LinkedIn has offices across the globe..",
"media": {
"mediaUrl": "https://media.licdn.com/dms/image/C560BAQHaVYd13rRz3A/company-logo_400_400/0/1638831590218/linkedin_logo?e=1723680000&v=beta&t=gOk8XZWklJyh3O7qcgWRluAgbt8whoV8Kr9B0E74xYI",
"id": "urn:li:digitalmediaAsset:C560BAQHaVYd13rRz3A",
"mediaExpiresSeconds": 1723680000000
},
"lastUpdated": "2024-05-15T20:42:09.417Z",
"nextUpdate": "2024-05-15T20:53:09.417Z"
},
// LinkedIn using a person ID.
"linkedin": {
"from": {
"name": "John Doe",
"id": "Z_yXaxh",
"url": "https://www.linkedin.com/in/johndoe",
"description": "Founder"
},
"media": {
"id": "urn:li:image:C5103AQHORT70jVfKVA",
"mediaExpiresSeconds": 1728518400000,
"url": "https://media.licdn.com/dms/image/v2/C5103AQHORT70jVfKVA/profile"
},
"platform": "linkedin",
"profileImageUrl": "https://media.licdn.com/dms/image/v2/C5103AQHORT70jVfKVA/profile",
"userName": "johndoe"
},
"twitter": {
"createdAt": "2017-10-04T15:26:17.000Z",
"description": "Ayrshare's APIs provide the core infrastructure for social media posting, management, and analytics. https://t.co/UlcRGcg1X9",
"id": "92839209304423",
"location": "New York, NY",
"name": "Ayrshare",
"profileImageUrl": "https://pbs.twimg.com/profile_images/1423334467389767680/ochnivwr_normal.jpg",
"publicMetrics": {
"followersCount": 5361,
"followingCount": 39,
"tweetCount": 844,
"listedCount": 5
},
"url": "https://t.co/UlcRGcg1X9",
"username": "Ayrshare"
},
"youtube": {
"created": "2009-05-12T05:28:43Z",
"description": "",
"hiddenSubscriberCount": false,
"isLinked": true,
"longUploadsStatus": "longUploadsUnspecified",
"madeForKids": false,
"playlistId": "UU-0dv2mN6SeXwkEKtfKXJnQ",
"privacyStatus": "public",
"subscriberCount": "28200",
"thumbnailUrl": "https://yt3.ggpht.com/OwargtIRcXzB7jlHWgCmitNo-6JX2wbZGdOMg5K7rd5BnX4bSJX1WPaD2Bi4RN3X9SJi4D4h4g=s88-c-k-c0x00ffffff-no-nd-rj",
"title": "MelissaEtheridgeVEVO",
"url": "https://www.youtube.com/c/@MelissaEtheridgeVEVO",
"videoCount": "43",
"viewCount": "29872467"
}
}
```
```json 400: Bad Request Error theme={"system"}
{
"status": "error",
"code": 187,
"instagram": {
"action": "analytics",
"status": "error",
"code": 294,
"message": "Error getting analytics. The user with username: whoblhablah cannot be found.",
"platform": "instagram"
},
"twitter": {
"action": "analytics",
"status": "error",
"code": 294,
"message": "Error getting analytics. Could not find user with username: [NoDonkdfjkd]."
},
"linkedin": {
"action": "analytics",
"status": "error",
"code": 294,
"message": "Error getting analytics.",
"details": "No LinkedIn organization found for linkedinfdfd"
}
}
```
# Search Tweets by Keyword
Source: https://www.ayrshare.com/docs/apis/listen/keyword/search-tweets
GET /listen/keyword
Search X/Twitter for tweets matching keywords, hashtags, and search operators, returning user info and engagement metrics. Requires BYOK for X/Twitter.
Search X/Twitter for tweets matching keywords, hashtags, and advanced search operators. Returns normalized tweet data including user info, engagement metrics, and entities.
This endpoint requires [Bring Your Own Keys (BYOK)](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) for X/Twitter.
> [!WARNING]
>
> **Important Limitations**
>
> * Only tweets from the last \~7 days are available (X API limitation).
> * Default daily limit of 25 calls.
> * BYOK keys for X/Twitter are required.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**query** `string` *required*
Keyword search query. Supports X/Twitter search operators (see table below).
Examples: `ayrshare`, `#socialmedia`, `ayrshare OR #socialmedia`, `from:ayrshare`.
**platform** `string` *required*
Must be `twitter`.
**limit** `integer` (default: 15)
Maximum number of tweets to return. Must be between 10 and 100.
**sinceId** `string`
Returns tweets with an ID greater than (newer than) this value. Useful for fetching only new tweets since a previous request.
**untilId** `string`
Returns tweets with an ID less than (older than) this value. Useful for paginating backwards through results.
**next** `string`
Pagination token from a previous response's `meta.pagination.next`. Use this to fetch the next page of results.
## Search Operators
The `query` parameter supports the following X/Twitter search operators:
| Operator | Description | Example |
| - | - | - |
| `AND` | Both terms must appear (default behavior) | `social AND media` |
| `OR` | Either term must appear | `ayrshare OR socialmedia` |
| `from:` | Tweets from a specific user | `from:ayrshare` |
| `to:` | Tweets directed at a specific user | `to:ayrshare` |
| `#` | Match a hashtag | `#socialmedia` |
| `@` | Match a mention | `@ayrshare` |
| `-` | Exclude a term | `social -spam` |
| `lang:` | Filter by language | `ayrshare lang:en` |
| `filter:` | Filter by content type | `ayrshare filter:links` |
| `url:` | Match a URL | `url:ayrshare.com` |
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "x-twitter-oauth1-api-key: YOUR_TWITTER_API_KEY" \
-H "x-twitter-oauth1-api-secret: YOUR_TWITTER_API_SECRET" \
-X GET "https://api.ayrshare.com/api/listen/keyword?query=ayrshare%20OR%20%23socialmedia&platform=twitter&limit=15"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch(
"https://api.ayrshare.com/api/listen/keyword?query=ayrshare%20OR%20%23socialmedia&platform=twitter&limit=15",
{
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`,
"x-twitter-oauth1-api-key": "YOUR_TWITTER_API_KEY",
"x-twitter-oauth1-api-secret": "YOUR_TWITTER_API_SECRET"
}
}
)
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {
'Authorization': 'Bearer API_KEY',
'x-twitter-oauth1-api-key': 'YOUR_TWITTER_API_KEY',
'x-twitter-oauth1-api-secret': 'YOUR_TWITTER_API_SECRET'
}
params = {
'query': 'ayrshare OR #socialmedia',
'platform': 'twitter',
'limit': 15
}
r = requests.get('https://api.ayrshare.com/api/listen/keyword', headers=headers, params=params)
print(r.json())
```
```json 200: Success theme={"system"}
{
"status": "success",
"platform": "twitter",
"query": "ayrshare OR #socialmedia",
"tweets": [
{
"id": "1234567890",
"text": "Just discovered @ayrshare for managing social media APIs!",
"createdAt": "2026-03-22T14:30:00.000Z",
"user": {
"id": "987654321",
"name": "Jane Doe",
"screenName": "janedoe",
"profileImageUrl": "https://pbs.twimg.com/profile_images/..."
},
"metrics": {
"retweetCount": 5,
"favoriteCount": 12
},
"entities": {
"hashtags": ["socialmedia"],
"mentions": ["ayrshare"],
"urls": []
},
"inReplyToStatusId": null,
"isRetweet": false,
"lang": "en"
}
],
"meta": {
"pagination": {
"hasMore": true,
"next": "b26v89c19zqg8o3fpds7h...",
"limit": 15
}
}
}
```
```json 400: Missing Query theme={"system"}
{
"action": "listen",
"status": "error",
"code": 428,
"message": "Missing required `query` parameter for keyword search"
}
```
```json 400: Unsupported Platform theme={"system"}
{
"action": "listen",
"status": "error",
"code": 429,
"message": "Missing or unsupported `platform` parameter. Currently only `twitter` is supported"
}
```
```json 403: Plan Required theme={"system"}
{
"action": "listen",
"status": "error",
"code": 430,
"message": "Keyword search requires Premium, Launch, Business, or Enterprise tier"
}
```
```json 403: BYOK Keys Required theme={"system"}
{
"action": "listen",
"status": "error",
"code": 431,
"message": "Keyword search requires BYOK (Bring Your Own Keys) for X/Twitter"
}
```
```json 429: Daily Limit Reached theme={"system"}
{
"action": "listen",
"status": "error",
"code": 432,
"message": "Daily keyword search limit exceeded. Limit resets at midnight UTC"
}
```
# Listen API: Social Media Monitoring & Mentions
Source: https://www.ayrshare.com/docs/apis/listen/overview
The Ayrshare Listen API monitors social media for keywords, hashtags, and mentions in real time to track brand conversations, sentiment, and trends.
Look up users' or companies' social media public information, such as followers, profile image, and websites for competitive analysis.
Although you still need to have a linked social account, these users and companies that you look up do not need to be a linked Ayrshare user.
Also search for Facebook Page and LinkedIn Member Profiles, often used for typeahead mention completion.
[Keyword search](/docs/apis/listen/keyword/search-tweets) on X/Twitter is also available on the Premium plan.
If your users have linked their social accounts, use the [/analytics](/docs/apis/analytics/social) endpoint for more detailed information.
# Search Facebook Pages
Source: https://www.ayrshare.com/docs/apis/listen/search/fb-page-search
GET /brand/search/facebook
Search for Facebook Pages with a search query, often used for typeahead mention completion. Returns the id, link, name, and public location of each Page.
Search for Facebook Pages based on a search query.
Often used for typeahead mention completion.
The response is an array of objects with the id, link, name, and location of the Page.
The `location` field will only be returned for Pages with public locations.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string` *required*
Search query to find Facebook pages by name.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/brand/search/facebook?search=facebook
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/brand/search/facebook?search=facebook", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/brand/search/facebook?search=facebook', headers=headers)
print(r.json())
```
```json 200: Success theme={"system"}
{
"facebook": [
{
"id": "7640348500",
"isUnclaimed": false,
"link": "https://www.facebook.com/7640348500",
"location": {
"city": "New York",
"country": "United States",
"latitude": 40.782910059774,
"longitude": -73.959075808525,
"state": "NY",
"street": "1071 5th Ave",
"zip": "10128"
},
"name": "Solomon R. Guggenheim Museum",
"verificationStatus": "blue_verified"
}
]
}
```
# Search LinkedIn
Source: https://www.ayrshare.com/docs/apis/listen/search/linkedin-search
GET /brand/search/linkedin
Search LinkedIn companies by exact vanity name, or people who follow your company page by name, for typeahead @mentions. Needs a linked LinkedIn company page.
Search for LinkedIn companies or people based on a search query.
This endpoint is commonly used for typeahead mention completion in social media posts.
**The linked account must be a LinkedIn company page to search.** Personal LinkedIn accounts can not be used to perform searches.
1. **For mentions in posts**: When implementing @mentions, use this endpoint with typeahead functionality.
2. **Company search**: The exact company vanity name is required.
3. **Person search**: Start with at least 3 characters of a name for best results.
4. **Rate limiting**: This endpoint follows standard API rate limits.
> [!NOTE]
>
> **Search Limitations**
>
>
>
**Companies**: You can search for any LinkedIn company page
>
>
> **People**: You can only search for people who are followers of your LinkedIn account and the
> Ayrshare linked account must be a LinkedIn company page to perform the search. If a person has
> their LinkedIn visibility set to private, they will not be found in the search results.
>
>
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**search** `string` *required*
Search query to find LinkedIn companies or people.
**Requirements:**
Minimum length: 3 characters for people and 1 character for companies.
Maximum length: 100 characters for both people and companies.
**Search behavior:**
**For companies**: Use the company's vanity name (found in the LinkedIn URL). Only **exact vanity name matches** will be returned.
Example: For `linkedin.com/company/ayrshare`, search for "ayrshare".
Searching for partial names like "ayrsh" will NOT return results.
**For people**: Use first name and/or last name. Partial name matches are supported.
Example: "John Smith" will find people named John Smith. Be sure to URL encode the space.
Partial matches like "Joh" or "Smi" will also return results.
Remember: Only your LinkedIn followers can be found and the Ayrshare linked account must be a LinkedIn company page to perform the search
**personOnly** `boolean` (default: false)
Controls the search scope: - `false` (default): Searches for companies first, then people if no
companies are found. - `true`: Searches only for people (skips company search entirely).
## Examples
```bash Company Search theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/brand/search/linkedin?search=ayrshare"
```
```bash Person Search Only theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/brand/search/linkedin?search=John%20Smith&personOnly=true"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
// Search for company or person
fetch("https://api.ayrshare.com/api/brand/search/linkedin?search=ayrshare", {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
// Search for person only
fetch("https://api.ayrshare.com/api/brand/search/linkedin?search=John%20Smith&personOnly=true", {
method: "GET",
headers: {
Authorization: `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
# Search for company or person
r = requests.get('https://api.ayrshare.com/api/brand/search/linkedin?search=ayrshare', headers=headers)
print(r.json())
# Search for person only
r = requests.get('https://api.ayrshare.com/api/brand/search/linkedin?search=john&personOnly=true', headers=headers)
print(r.json())
```
```json Company Search Result theme={"system"}
{
"linkedin": [
{
"vanityName": "ayrshare",
"website": "https://www.ayrshare.com",
"groups": [],
"description": "Ayrshare's APIs provide the core infrastructure for social media posting, management, and analytics.\n\nThe Ayrshare API takes care of the social media infrastructure so you don't have to. Your team can focus on building your product instead of stitching together and maintaining multiple social media platforms.\n\nPost to Facebook, Twitter, Instagram, LinkedIn, Reddit, Telegram, TikTok, Google My Business, and YouTube.\n",
"defaultLocale": {
"country": "US",
"language": "en"
},
"organizationType": "PARTNERSHIP",
"alternativeNames": [],
"specialties": [
"social media",
"api",
"saas",
"social networks",
"tiktok",
"facebook",
"instagram"
],
"staffCountRange": "SIZE_10_TO_100",
"name": "Ayrshare",
"primaryOrganizationType": "NONE",
"locations": [
{
"description": {
"localized": {
"en_US": "Headquarters"
},
"preferredLocale": {
"country": "US",
"language": "en"
}
},
"locationType": "HEADQUARTERS",
"address": {
"geographicArea": "New York",
"country": "US",
"city": "New York",
"line1": "142 W 57th St",
"postalCode": "10019"
},
"localizedDescription": "Headquarters",
"streetAddressFieldState": "UNSET_OPT_OUT",
"geoLocation": "urn:li:geo:103963738"
}
],
"id": 66755333
}
]
}
```
```json Person Search Result theme={"system"}
{
"linkedin": [
{
"lastName": "Smith",
"firstName": "John",
"headline": "CTO at Ayrshare",
"id": "urn:li:person:WBwF1C23L"
}
]
}
```
```json 400: Search query too short theme={"system"}
{
"linkedin": {
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "The search must be a string between 3 and 100 characters."
}
}
```
```json 404: No results found theme={"system"}
{
"linkedin": []
}
```
# Ayrshare API Overview
Source: https://www.ayrshare.com/docs/apis/overview
Powerful Social APIs that enable you to send social media posts and get analytics effortlessly. For developers and businesses of all sizes.
The Social Media REST API provides developers with programmatic access to multiple social networks through a single unified interface.
Through Ayrshare's social API, you can manage social media activities including creating and deleting posts, retrieving analytics, engaging with comments and reviews, managing direct messages, creating Facebook ads, and performing other social media actions across platforms.
The API currently supports 14 major social networks: Bluesky, Facebook, Google Business Profile, Instagram, LinkedIn, Pinterest, Reddit, Snapchat, Telegram, Threads, TikTok, WhatsApp, X (formerly Twitter), and YouTube.
By integrating with this API, developers can automate social media management tasks across all these platforms simultaneously.
API calls request and response data are in JSON format, which allows for easy parsing and processing in most programming languages.
The same API is also available over [GraphQL](/docs/apis/graphql/overview), which lets you request several things in one call and gives you a typed, self-describing schema you can explore in the [GraphQL Explorer](https://app.ayrshare.com/graphql-explorer). REST remains the primary interface; both are supported side by side and can be mixed in the same integration.
If you are on the Launch, Business, or Enterprise Plan, see the [Business Plan Overview](/docs/multiple-users/business-plan-overview), [Launch Plan Overview](/docs/multiple-users/business-launch-overview), and [/profiles API endpoint](/docs/apis/profiles/overview).
## Key Functionality
[14 social networks supported](/docs/introduction#which-social-networks-are-supported).
Secure API access using your unique API Key.
Scheduled posting to connected social media platforms.
Automated posting based on predefined schedules.
Support for image and video content, including Reels, Stories, and Spotlight.
Delete posts on linked social networks.
Comprehensive post engagement analytics (likes, shares, etc.).
Social account metrics, including follower count and demographic data.
Comment management: view, add, and delete post comments.
Optional link shortening for all or specific URLs in posts.
Unsplash integration: add specific images or randomly select based on keywords.
Automatic hashtag generation option using relevant keywords.
Post history tracking, including non-Ayrshare posts.
Review management: retrieve, reply to, and delete review responses.
RSS feed integration for automated content posting.
Media library: upload and store photos and videos for use in posts.
[Social Post Verification System](/docs/testing/post-verification) to keep your social accounts safe.
## Business Plan and Launch Plan
[Business Plan](/docs/multiple-users/business-plan-overview) and [Launch Plan](/docs/multiple-users/business-launch-overview) features for managing multiple users and clients:
Enable users to link their own social media accounts to your platform.
Secure single sign-on using OAuth for quick account linking.
Create and remove user profiles programmatically through the API.
Access advanced user analytics.
Webhook support for real-time updates.
Direct message management across supported platforms.
Create Facebook ads from existing posts.
Contact us to learn more about the [Business Plan](https://www.ayrshare.com/business-plan-for-multiple-users/).
## Ads API
The [Ads API](/docs/apis/ads/overview) allows you to create Facebook ads from existing posts.
Boost posts to reach more people.
Manage ads and track performance.
Analyze ad spend and optimize campaigns.
> [card] [Ads API](/docs/apis/ads/overview)
>
> Explore the Ads API
## Messages API
A unified [Messaging API](/docs/apis/messages/overview) to engage users in conversations across the major social media channels: Facebook, Instagram, and X.
Sending text, image, and video messages.
Retrieving complete conversation histories.
Setting up automated message responses.
Receiving real-time updates via webhooks for messages received, message reactions, read
receipts.
This API simplifies user engagement by centralizing messaging operations for multiple social media channels. Learn more...
> [card] [Messages API](/docs/apis/messages/overview)
>
> Explore the unified messaging API
## Max Pack
Get even more capabilities with the [Max Pack add-on](/docs/additional/maxpack):
> [card] [Max Pack](/docs/additional/maxpack)
>
> Unlock advanced features like AI-powered content generation, enhanced analytics, and expanded
> platform support with our Max Pack add-on.
## Watch How to Use the Social API
If you're building in Node.js, check out this video on how to connect and publish posts to X and Facebook.
[How to Use Ayrshare's Social API](https://www.youtube.com/embed/OMtj2h6sW6U)
## Social API Demo
If you use Node.js see the social API demo code to get started building your own social API integration.
> [card] [Social API Demo](https://github.com/ayrshare/social-api-demo)
>
> Explore our Node.js demo code to jumpstart your social API integration
## Base URL
The base URL for the Ayrshare API is the same for all endpoints.
`https://api.ayrshare.com/api`
## Authorization
Ayrshare authenticates API requests via an Authorization token passed in the HTTP header. Please be sure to send `Bearer` with the API Key. The API Key can be found in the Ayrshare Dashboard by switching to your Primary Profile.
If you are a Business or Enterprise user, you can create User Profiles with Profile Keys to manage multiple clients. The Profile Key is used in the header of your requests.
The API Key must also be used in the header of your requests for User Profiles.
> [!TIP]
>
> Premium plans should only use the API Key in the header of their requests. Business and Enterprise
> plans should use the Profile Key in the header of their requests when interacting on behalf of a
> User Profile.
### API Key Format
`Authorization: Bearer API_KEY` replacing API\_KEY with your Primary Profile API Key, which can be [found in the Ayrshare Dashboard](/docs/quickstart#get-your-api-key).
```bash cURL theme={"system"}
curl -H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api
```
```javascript JavaScript theme={"system"}
headers: {"Authorization": "Bearer API_KEY"}
```
```python Python theme={"system"}
headers = {"Authorization": "Bearer API_KEY"}
```
```php PHP theme={"system"}
$headers = ["Authorization" => "Bearer API_KEY"];
```
```go Go theme={"system"}
headers := map[string]string{"Authorization": "Bearer API_KEY"}
```
```ruby Ruby theme={"system"}
headers = {"Authorization": "Bearer API_KEY"}
```
```java Java theme={"system"}
headers = {"Authorization": "Bearer API_KEY"}
```
```csharp C# theme={"system"}
headers = {"Authorization": "Bearer API_KEY"}
```
```rust Rust theme={"system"}
headers = {"Authorization": "Bearer API_KEY"}
```
> [!NOTE]
>
> Obtain your secret API Key in the Ayrshare dashboard under the API Key page found in the left navigation panel.
>
> For example, if your API Key is 2MPXPKQ-S03M5LS-GR5RX5G-AZCK8EA
>
> Your header should include:
>
> `Authorization: Bearer 2MPXPKQ-S03M5LS-GR5RX5G-AZCK8EA`
### Profile Key Format
A Profile Key is used to interact on behalf of a User Profile.
This is only available for Business or Enterprise plans.
`Profile-Key: PROFILE_KEY` replacing PROFILE\_KEY with a user's [Profile Key](/docs/multiple-users/manage-user-profiles#get-the-profile-key).
**Including a Profile Key in the header is required to interact on behalf of a User Profile**.
A missing Primary Profile API Key or using the Profile Key in place of the API Key in the header will result in an error.
Here's how to structure the headers for API requests:
```bash cURL theme={"system"}
curl -H "Authorization: Bearer API_KEY" \
-H "Profile-Key: PROFILE_KEY" \
-X GET https://api.ayrshare.com/api
```
```javascript JavaScript theme={"system"}
headers: {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```python Python theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```php PHP theme={"system"}
$headers = [
"Authorization" => "Bearer API_KEY",
"Profile-Key" => "PROFILE_KEY"
];
```
```go Go theme={"system"}
headers := map[string]string{
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```ruby Ruby theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```java Java theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```csharp C# theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
```rust Rust theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY"
}
```
> [!NOTE]
>
> [Find the Profile Key](/docs/multiple-users/manage-user-profiles#get-the-profile-key) in the Ayrshare Dashboard under the Profile Key page found in the left navigation panel.
> Switch to the Profile you want to use in the User Profile page.
> Additionally, the Profile Key is returned in the response of the [Create a User Profile](/docs/apis/profiles/create-profile) endpoint.
>
> You can then use the Profile Key in the header of your requests:
>
> For example, if your Profile Key is `AX1XGG-9jK3M5LS-GR5RX5G-LLCK8EA`
>
> Your header should include both the API and Profile Key:
>
> `Authorization: Bearer 2MPXPKQ-S03M5LS-GR5RX5G-AZCK8EA`
>
> `Profile-Key: AX1XGG-9jK3M5LS-GR5RX5G-LLCK8EA`
### X/Twitter BYO Credentials
Starting March 31, 2026, all X/Twitter operations through Ayrshare require your own OAuth 1.0a credentials. After linking your X account via OAuth, pass these 2 headers alongside your `Authorization` (and optional `Profile-Key`) headers for any request that targets X/Twitter:
| Header | Description |
| - | - |
| `X-Twitter-OAuth1-Api-Key` | Your OAuth 1.0a API Key (Consumer Key) |
| `X-Twitter-OAuth1-Api-Secret` | Your OAuth 1.0a API Key Secret (Consumer Secret) |
These headers are required after March 31, 2026. Requests to X/Twitter endpoints without valid BYO credentials will be rejected.
> [!NOTE]
>
> **One key pair, used for every request.** You create one X Developer App per Ayrshare account and use the same `X-Twitter-OAuth1-Api-Key` and `X-Twitter-OAuth1-Api-Secret` values on every X-targeting request, regardless of which sub-profile (`Profile-Key`) the request is for. You do not generate or rotate keys per customer or per end-user.
See the [X BYO Key Setup Guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) for step-by-step instructions, code examples, and troubleshooting.
The same two headers apply when driving X/Twitter through the [MCP Server](/docs/additional/mcp-action-server) — see [Connect & Setup](/docs/additional/mcp-action-connect).
## Content Type
The Content Type should always be set as `Content-Type: "application/json"` unless the endpoint specifically specifies otherwise.
```bash cURL theme={"system"}
curl -H "Authorization: Bearer API_KEY" \
-H "Profile-Key: PROFILE_KEY" \ # Optional
-H "Content-Type: application/json" \
-X GET https://api.ayrshare.com/api
```
```javascript JavaScript theme={"system"}
headers: {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json"
}
```
```python Python theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", # Optional
"Content-Type": "application/json"
}
```
```php PHP theme={"system"}
$headers = [
"Authorization" => "Bearer API_KEY",
"Profile-Key" => "PROFILE_KEY", # Optional
"Content-Type" => "application/json"
];
```
```go Go theme={"system"}
headers := map[string]string{
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json"
}
```
```ruby Ruby theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", # Optional
"Content-Type": "application/json"
}
```
```java Java theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json"
}
```
```csharp C# theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json"
}
```
```rust Rust theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json"
}
```
## Compression
Ayrshare supports compression for all API requests.
To enable compression, set the `Accept-Encoding` header to `deflate, gzip, br`.
```bash theme={"system"}
Accept-Encoding: "deflate, gzip, br"
```
This is recommended for calling larger responses such as the [/history endpoint](/docs/apis/history/overview).
Only responses over 1024 bytes (1KB) are compressed.
The order of compression used: Brotli (br) first, then gzip, then deflate. Brotli is the most
efficient compression algorithm.
The response header contains the content encoding used. For example: `content-encoding: br` if
Brotli was used.
Learn more about compression and the benefits of using compression in the [Ayrshare
Blog](https://www.ayrshare.com/blog/http-compression-in-node-js-a-dive-into-gzip-deflate-and-brotli/).
> [!TIP]
>
> Don't forget to properly uncompress the response using the content encoding.
```bash cURL theme={"system"}
curl -H "Authorization: Bearer API_KEY" \
-H "Profile-Key: PROFILE_KEY" \ # Optional
-H "Content-Type: application/json" \
-H "Accept-Encoding: deflate, gzip, br" \
-X GET https://api.ayrshare.com/api
```
```javascript JavaScript theme={"system"}
headers: {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```python Python theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", # Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```php PHP theme={"system"}
$headers = [
"Authorization" => "Bearer API_KEY",
"Profile-Key" => "PROFILE_KEY", # Optional
"Content-Type" => "application/json",
"Accept-Encoding" => "deflate, gzip, br"
];
```
```go Go theme={"system"}
headers := map[string]string{
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```ruby Ruby theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", # Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```java Java theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```csharp C# theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
```rust Rust theme={"system"}
headers = {
"Authorization": "Bearer API_KEY",
"Profile-Key": "PROFILE_KEY", // Optional
"Content-Type": "application/json",
"Accept-Encoding": "deflate, gzip, br"
}
```
## ID Types
There are several types of IDs returned, which can be used in various endpoints:
### Ayrshare Post ID
This ID is generated by Ayrshare and returned from the
[/post endpoint](/docs/apis/post/post) in the `id` field. This ID makes it easy
to get analytics on the post across social networks, add comments to the
post, delete the post, etc. This is the ID you will use most often.
### Social Post ID
Each social network assigns their own unique ID to posts and comments.
These IDs are returned in the `postIds` field of the /post or /comments endpoints.
You can use these IDs, or ones you get directly from the social networks, to retrieve data, such as with the [analytics social post ID](/docs/apis/analytics/social-by-id).
### Ayrshare Comment ID
This ID is generated by Ayrshare and returned from the
[/comments endpoint](/docs/apis/comments/post-comment) in the `id` field. This ID makes it easy
to get analytics on the comment across social networks, add replies to the
comment, delete the comment, etc. This is the ID you will use most often.
This is often used if you want to get details on a particular comment published via Ayrshare.
### Social Comment ID
Each social network assigns their own unique ID to comments.
These IDs are returned in the `commentId` field of the [GET /comments endpoint](/docs/apis/comments/get-comments).
This is often used if you want to get details on a particular comment published outside of Ayrshare.
## Error Codes
Errors will return with [standard HTTP status codes](https://tools.ietf.org/html/rfc2616#section-10).
For more information:
> [card] [HTTP Status Codes](/docs/errors/errors-http)
>
> Learn more about HTTP status codes
Detailed Errors are in the REST API response specific for each type of call.
For more information:
> [card] [Ayrshare Error Codes](/docs/errors/errors-ayrshare)
>
> Learn more about Ayrshare-specific error codes
## Timestamp Format
Ayrshare uses Zulu Time, also known as UTC (Coordinated Universal Time) or an ISO 8601 formatted date string, for precise and unambiguous time references across different time zones.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
You can convert the UTC format to your local time in the programming language of your choice. For example in JavaScript:
```javascript theme={"system"}
const convertToLocalTime = (isoString) => {
// Create a new Date object from the ISO string
const date = new Date(isoString);
// Extract local time components
const localDate = date.toLocaleDateString();
const localTime = date.toLocaleTimeString();
// Combine the local date and time
const localDateTime = `${localDate} ${localTime}`;
return localDateTime;
};
```
## Postman
You can use Postman to test your REST API calls.
> [card] [Postman](/docs/testing/postman)
>
> Learn how to use Postman with our API
## Random Posts, Images, and Videos
Check out the [quick start guide](/docs/quickstart#publish-test-posts) on how to send random posts, images, or videos when testing.
## Packages
We have both Node.js & Python packages, Bubble.io, Airtable, and Make guides available to make the RESTful calls easier.
> [card] [Node NPM Package](/docs/packages-guides/nodejs)
>
> Integrate using our NodeJS package
> [card] [Python PyPI Package](/docs/packages-guides/python)
>
> Integrate using our Python package
> [card] [Airtable Guide](/docs/packages-guides/airtable)
>
> Learn how to use Ayrshare with Airtable
> [card] [Bubble Plugin & Guide](/docs/packages-guides/bubble)
>
> Learn how to use Ayrshare with Bubble.io
> [card] [Make Guide](/docs/packages-guides/make)
>
> Learn how to use Ayrshare with Make
> [card] [Notion Guide](/docs/packages-guides/notion)
>
> Learn how to use Ayrshare with Notion
> [card] [FlutterFlow Guide](/docs/packages-guides/flutterflow)
>
> Learn how to use Ayrshare with FlutterFlow
> [card] [Retool Guide](/docs/packages-guides/retool)
>
> Learn how to use Ayrshare with Retool
# Ayrshare Social Media API
Source: https://www.ayrshare.com/docs/introduction
One Ayrshare API connects 14 social networks to schedule posts, get analytics, manage comments, send direct messages, and run ads. Features vary by network.

Ayrshare is a unified API that lets you manage your users' social media presence across all major platforms with a single integration.
Instead of dealing with 14 different social media APIs, you use just one to schedule posts, get analytics, manage comments, send direct messages, create ads, and more.
## What is Ayrshare?
Managing multiple social media platforms is complex. Each network has its own API, approval process, error handling, and data formats. Keeping up with constant API changes across platforms is a full-time job.
Ayrshare solves this by providing a single, [unified API](/docs/apis/overview) that handles all the complexity for you. We maintain direct integrations with each social network, so you can focus on building your product instead of managing infrastructure.
With Ayrshare, you can:
* Schedule and publish posts across multiple platforms
* Retrieve analytics and engagement metrics
* Manage comments and conversations
* Send and receive direct messages
* Create and manage advertising campaigns
And with our [Launch](/docs/multiple-users/business-launch-overview) and [Business](/docs/multiple-users/business-plan-overview) plans, you can manage social media for all your users.
## See It In Action
Here's how simple it is to post to multiple platforms on behalf of your user:
```json theme={"system"}
// One API call posts to multiple platforms
POST /post
{
"post": "Check out our new product launch! 🚀",
"platforms": ["x", "facebook", "instagram", "linkedin", "tiktok"],
"mediaUrls": ["https://example.com/video.mp4"],
"scheduleDate": "2025-12-01T10:00:00Z"
}
```
That's it! And you can then make a single call to [get the analytics](/docs/apis/analytics/overview) or [add a comment](/docs/apis/comments/overview) on all of these posts.
See below for [how to get started](/docs/introduction#how-to-get-started-with-ayrshare).
## Which Social Networks Are Supported?
Ayrshare seamlessly integrates with 14 major social networks:
> [card] Bluesky
> [card] Facebook
> [card] Google Business
> [card] Instagram
> [card] LinkedIn
> [card] Pinterest
> [card] Reddit
> [card] Snapchat
> [card] Telegram
> [card] Threads
> [card] TikTok
> [card] WhatsApp
> [card] X (Twitter)
> [card] YouTube
Ayrshare integrates directly with each social network's official APIs and partnership programs. This ensures our platform delivers the most reliable, secure, and current social media management features available, all while maintaining compliance with each network's policies and best practices.
> [!NOTE]
>
> The messaging API supports Instagram, Facebook, WhatsApp, and X. The Ads API supports Facebook.
## API-First Platform
At Ayrshare, everything is designed with developers in mind, allowing you to quickly integrate social media features into your platform or application.
Our API-first approach means every feature is accessible programmatically, with comprehensive documentation, SDKs in multiple languages, and predictable behavior.
## How It Works
The API follows REST principles, making it intuitive for developers:
* **GET** requests retrieve information (analytics, post history, user data)
* **POST** requests create new resources (posts, comments, messages)
* **PUT** requests update existing resources
* **DELETE** requests remove resources
All data is exchanged in JSON format, ensuring easy parsing and manipulation in any programming language.
## How to Get Started with Ayrshare
Getting up and running takes just a few minutes:
**1. Create Your Account**
Sign up for Ayrshare and find your API key in the dashboard.
**2. Connect Social Accounts**
Link the social media accounts you want to manage.
> [card] [Connect Social Media Accounts](/docs/dashboard/connect-social-accounts/overview)
**3. Make Your First API Call**
Test the API with our interactive documentation.
> [card] [API Overview](/docs/apis/overview)
**4. Follow the Quick Start**
Get posting in minutes with our step-by-step guide.
> [card] [Quick Start Guide](/docs/quickstart)
**5. Scale to Multiple Users**
Learn how to manage social media for all your users.
> [card] [Business Plans for Multiple Users](/docs/multiple-users/business-plan-overview)
**6. Get Support**
Have questions? Our team is here to help via chat or email.
# Quick Start Guide
Source: https://www.ayrshare.com/docs/quickstart
Connect social accounts, find your API Key, and publish a first post with /post, then follow best practices for error codes, key security, and test posts.
The guide is for setting up a single company. For Business and Enterprise Plan subscriptions with multiple users, a personalized integration guide is provided.
[Ayrshare's Social API Quick Start Guide](https://www.youtube.com/embed/HaWh_QOib_Y)
## Connect Your Social Media Accounts
Log in or sign up for an [Ayrshare account](https://app.ayrshare.com/).
On the Social Account page, click the social networks you want to connect.
Please be sure to grant all permissions.

## Get Your API Key
An API Key is required to authorize access to the API endpoints.
This key is used in the header of your requests, and should be preceded by the `Bearer` keyword.
Your API Key is located within the [Ayrshare Dashboard](https://app.ayrshare.com/), on the **API Key** page. If necessary, switch to your 'Primary Profile' via the **User Profiles** page.
From there, you can find the **API Key** page in the left-hand side panel.

Every API call must include the API Key in the Header using a `Bearer` Token. Also include the `Content-Type` of `application/json`.
```json Header theme={"system"}
"Authorization": "Bearer API_KEY",
"Content-Type": "application/json"
```
See the [Authorization](/docs/apis/overview#authorization) for more information.
## Send Your First API Post
### Publishing the Post
Below are code examples showing how to make a post using our API in different programming languages.
For an even easier integration, you can use one of our pre-built [SDK packages](/docs/packages-guides/overview).
To make a post, you'll need:
Your [API Key](/docs/apis/overview#authorization) for authentication.
The text content of your post.
The social media platforms you want to post to (only include platforms you've linked).
Optional: URLs of any images or videos you want to include.
All examples use the [/post](/docs/apis/post/post) endpoint. Let's look at some sample code:
> [!NOTE]
>
> **Posting to X/Twitter?** X requires your own API credentials. Add 2 BYO headers (`X-Twitter-OAuth1-Api-Key` and `X-Twitter-OAuth1-Api-Secret`) to your request. This is a **one-time setup per Ayrshare account**, and the same key pair works for every sub-profile / end-user. See the [setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) for details.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"post": "Today is a great day!",
"platforms": ["facebook", "instagram", "linkedin"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"]
}' \
-X POST https://api.ayrshare.com/api/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
post: "Today is a great day!", // required
platforms: ["bluesky", "facebook", "instagram", "linkedin"], // required
mediaUrls: ["https://img.ayrshare.com/012/gb.jpg"] //optional
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'post': 'Today is a great day!',
'platforms': ['bluesky', 'facebook', 'instagram', 'linkedin'],
'mediaUrls': ['https://img.ayrshare.com/012/gb.jpg']}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"Today is a great day!",
"platforms" => ["bluesky", "facebook", "instagram", "linkedin", "pinterest"],
"mediaUrls" => ["https://img.ayrshare.com/012/gb.jpg"]
];
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.ayrshare.com/api/post',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer API_KEY', // Replace 'API_KEY' with your actual API key
'Content-Type: application/json'
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"post": "Today is a great day!",
"platforms": []string{"bluesky", "facebook", "instagram", "linkedin"},
"mediaUrls": []string{"https://img.ayrshare.com/012/gb.jpg"}
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/post",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostPOSTRequest_csharp
{
class Post
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post";
// Set up request headers
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
// Prepare JSON content
string json = "{\"post\" : \"Today is a great day!\","
+ "\"platforms\" : [ \"bluesky\", \"facebook\", \"instagram\", \"linkedin\" ],"
+ "\"mediaUrls\" : [ \"https://img.ayrshare.com/012/gb.jpg\" ]}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// Send POST request
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
// Read response
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```ruby Ruby theme={"system"}
require 'httparty' # gem install httparty
res = HTTParty.post("https://api.ayrshare.com/api/post",
headers: {Authorization: "Bearer API_KEY"},
body: {
post: "Today is a great day!",
platforms: ['bluesky', 'facebook', 'instagram', 'linkedin'],
mediaUrls: ["https://img.ayrshare.com/012/gb.jpg"]
}).body
puts res
```
Congratulations! You just sent your first post.
### Post Response
You will receive a response with a `status` of "success" when our API successfully processes your post. The response includes:
`status`: Overall status of the API call ("success" or "error")
`errors`: Array of any errors encountered (empty if successful)
`postIds`: Array of objects containing platform-specific details:
Platform name
Post ID on that platform
URL to view the post
Additional platform-specific information
Each platform in the `postIds` array will have its own status, allowing you to verify which platforms successfully received your post.
Check your social media pages to ensure that the post was successfully processed and is live. Note that some platforms may have a slight delay before posts appear publicly.
Here is a sample response:
```json Post Response theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"id": "738681876342836_1530521371228093",
"postUrl": "https://www.facebook.com/738681876342836/posts/1530521371228093",
"platform": "facebook"
},
{
"status": "success",
"id": "18008340650526653",
"postUrl": "https://www.instagram.com/p/DGf4hgZObR0/",
"usedQuota": 1,
"platform": "instagram"
},
{
"status": "success",
"id": "urn:li:share:7300150903578275840",
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:7300150903578275840",
"owner": "urn:li:organization:66755239",
"platform": "linkedin"
}
],
"id": "K0DmYRgugS5P694it",
"refId": "d93ca2784c9bf7bf83e0bf081d16c91598",
"post": "Today is a great day!"
}
```
See more code examples of calling the social media API in Node.js, Python, PHP, Golang, C#, and Ruby, plus other great capabilities:
> [card] [Post](/docs/apis/post/post)
### Diving a Little Deeper
Let's break down the process of what just happened when we made this API call.
**1. Set the Header**
We set the Header Authorization bearer token (API Key) and content type of
json.
**2. Create a Body Object**
Next, we created a body object:
Post containing the text "Today is a great day!".
Social network platforms of Facebook, Instagram, and LinkedIn.
You should only include the platforms you linked in the Social Linkage
Page.
Added an image in the mediaUrls.
**3. Call the /post Endpoint**
Sent everything as an HTTP POST to the /post endpoint.
## Before You Go, Some Best Practices
### Read the Docs
We maintain extensive and up-to-date documentation. Since you are already here, take some time to check out the sections of the docs in the left navigation.
### Error Codes Are Important
You need to handle them so your users have a great experience.
> [card] [Errors](/docs/errors/overview)
### Keep Your API Key Secure
We take security very seriously, and so should you. You have full control over the connection between Ayrshare and your social media accounts. Once the accounts are connected, the Ayrshare API key becomes the way you authenticate. Please keep this key secret and secure.
### Publish Test Posts
You can publish test posts with a random quote, image, or video using the `randomPost`, `randomMediaUrl`, and `isLandscapeVideo` or `isPortraitVideo` parameters in the [/post endpoint](/docs/apis/post/post). If you use the `randomPost`, the `post` parameter is not required.
[Post Random Content](https://www.youtube.com/embed/hMI0eK-5qBk?si=y5dkygosZ1B2DtlV)
> [!WARNING]
>
> These posts are live on your social media pages, so if appropriate, delete them after testing.
#### Random Quote
Send a random quote for testing publishing a post by adding `randomPost: true`.
Used with the [/post](/docs/apis/post/post) endpoint.
```json theme={"system"}
"randomPost": true
```
#### Random Image
Send a random image by adding `randomMediaUrl: true`.
Used with the [/post](/docs/apis/post/post) endpoint.
```json theme={"system"}
"randomMediaUrl": true
```
#### Random Video
Send a random landscape video by adding `isVideo: true` and `isLandscapeVideo: true`.
The video will be standard landscape size.
Used with the [/post](/docs/apis/post/post) endpoint.
```json theme={"system"}
"randomMediaUrl": true,
"isLandscapeVideo": true
```
Send a random portrait video, required for TikTok or Facebook/Instagram Reels, by adding `isVideo: true` and `isPortraitVideo: true`.
Used with the [/post](/docs/apis/post/post) endpoint.
```json theme={"system"}
"randomMediaUrl": true,
"isPortraitVideo": true
```
#### Random Comment
Send a random comment for testing publishing a comment by adding `randomComment: true`.
Used with the [/comment](/docs/apis/comments/post-comment) endpoint.
```json theme={"system"}
"randomComment": true
```
#### Example cURL Call
Send a random quote and portrait video.
```bash theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H "Content-Type: application/json" \
-X POST \
-d '{
"randomPost": true,
"platforms": ["facebook", "instagram", "linkedin"],
"randomMediaUrl": true,
"isPortraitVideo": true
}' \
https://api.ayrshare.com/api/post
```
### **Use Postman to Test Your API Calls**
[Postman](https://www.postman.com/) is an amazing tool for testing API calls. You can even generate code in over a dozen languages.
### Demo Code
If you are looking for sample demo project in React and NodeJS, please see this GitHub repository:
> [card] [Github Social Posting Demo App](https://github.com/ayrshare/social-api-demo)
### **Common Troubleshooting**
Sometimes things don't go right, so here is a little help in our help center.
> [card] [Troubleshooting](/docs/help-center/overview)
### **Reach Out To Us for Help**
We are here to help, so please use the chat in the bottom right corner or [email us](mailto:support@ayrshare.com).
# Get Instagram audio metadata
Source: https://www.ayrshare.com/docs/apis/media/get-instagram-audio
GET /media/instagram/audio/{audioId}
Retrieve one Instagram audio track by audioId, with title, artist, duration, and artwork. Needs Instagram linked via Facebook Login, else code 514.
Retrieve the details of one audio track by its `audioId`. Use this to confirm a stored id is still valid, or to show a track's title and artwork before publishing.
> [!WARNING]
>
> **Requires Instagram linked through Facebook Login.** Instagram's Audio API is only available to accounts connected via **Facebook Login**, which needs a Facebook Page connected to the Instagram account. Accounts connected with **Instagram Login** (the Ayrshare default) cannot search, look up, or attach audio, and every audio request returns `code: 514`.
>
> This is a Meta restriction, not an Ayrshare one. Meta's documentation states the Audio API "is only available on the Instagram API with Facebook Login. It is not supported on the Instagram API with Instagram Login." No permission or plan upgrade changes it.
>
> **Three things are required:**
>
> 1. An Instagram **Business** or **Creator** account
>
> 2. A **Facebook Page connected** to that Instagram account
>
> 3. Instagram linked to Ayrshare with **Instagram Login disabled**, so the Facebook Login flow is used
>
> See the [step-by-step setup guide](/docs/dashboard/connect-social-accounts/instagram#music-attach-eligibility-facebook-login-required).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**audioId** `string` *required*
The track id, as returned by [audio search](/docs/apis/media/search-instagram-audio). An id Instagram
does not recognize returns `code: 517`.
## Response Fields
The track's fields are returned flat on the response object rather than nested. Instagram does not return every field for every track, so treat all fields except `audioId` as optional.
**status** `string`
`success` when the track was found.
**audioId** `string`
The track id, echoed back.
**title** `string`
Track title.
**displayArtist** `string`
Artist name as Instagram displays it.
**durationMs** `integer`
Track length in milliseconds.
**thumbnailUrl** `string`
Cover artwork thumbnail URL.
**audioType** `string`
Either `music` or `original_sound`.
**isAdsEligible** `boolean`
Whether the track may be used in an ad.
```json 200: Success theme={"system"}
{
"status": "success",
"audioId": "17901234567890123",
"title": "Summer Nights",
"displayArtist": "The Example Band",
"durationMs": 32000,
"thumbnailUrl": "https://scontent.example.com/cover.jpg",
"audioType": "music",
"isAdsEligible": true
}
```
```json 400: Track unavailable theme={"system"}
{
"status": "error",
"code": 517,
"action": "post",
"message": "The selected audio track is unavailable. It may be an invalid audio id, restricted in your region, or not licensed for this use. Please choose a different track using the Instagram audio search endpoint and try again.",
"platform": "instagram"
}
```
> [!NOTE]
>
> **Availability is region-dependent.** Meta returns the same error for an id that does not exist,
> one that is not licensed in the account's region, and one that is not licensed for this use. All
> three report `517`, so a track that resolves for one account can legitimately fail for another.
> Look up an id rather than assuming a stored one still works.
## Errors
| Code | Meaning |
| - | - |
| 156 | Instagram is not linked to this profile. |
| 161 | Instagram's authorization expired or was revoked. Relink the account. |
| 239 | Instagram rejected the request for an account-level reason. |
| 258 | Instagram returned a malformed response or an unmapped failure. |
| 425 | The connection is missing a required Instagram permission. Relink and grant all permissions. |
| 435 | Instagram's rate limit was reached. Wait before retrying. |
| 514 | Instagram is linked with Instagram Login. The Audio API requires Facebook Login. |
| 516 | The `audioId` is missing or empty. |
| 517 | Instagram does not recognize the id, or the track is unavailable to this account. |
Codes 514 to 518 are described in [Instagram Reels audio errors](/docs/errors/errors-ayrshare#instagram-reels-audio-errors); the rest are in the [Ayrshare error reference](/docs/errors/errors-ayrshare).
## Next Steps
> [card] [Search for a track](/docs/apis/media/search-instagram-audio)
> [card] [Attach the track to a Reel](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel)
# Get All Media in Gallery
Source: https://www.ayrshare.com/docs/apis/media/get-media-in-gallery
GET /media
Retrieve all images and videos uploaded to your media gallery, including resized images, with each file's id, URL, size, file name, description, and expiration.
Retrieve all the images uploaded to the image gallery including resized images.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer [API Key]" \
-X GET https://api.ayrshare.com/api/media
```
```javascript JavaScript theme={"system"}
const API_KEY = "Your API Key";
fetch("https://api.ayrshare.com/api/media", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer [API_KEY]'}
r = requests.get('https://api.ayrshare.com/api/media', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/media',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace MediaGETRequest_csharp
{
class Media
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/media";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```javascript 200: Array of images in the image gallery theme={"system"}
[
{
"id": "5553a26a-cffb-4a31-bd89-dee296d35648-jpeg",
"timeCreated": "2025-07-30T20:23:49.774Z",
"size": "223340",
"url": "https://images.ayrshare.com/LW8kx9Q5smXeJl5CujF7DxFAF1q1/5553a26a-cffb-4a31-bd89-dee296d35648.jpeg",
"fileName": "test1.jpeg",
"description": "Test1",
"expiresAt": "2025-09-05T22:00:48.000Z" // The date and time the media will expire
},
{
"id": "93383cc7-7a15-4d16-a5fa-fdda1f9bebe2-jpeg",
"timeCreated": "2025-07-30T20:24:57.421Z",
"size": "223340",
"url": "https://images.ayrshare.com/LW8kx9Q5smXeJl5CujF7DxFAF1q1/93383cc7-7a15-4d16-a5fa-fdda1f9bebe2.jpeg",
"fileName": "test2.jpeg",
"description": "Test2",
"expiresAt": "2025-09-05T22:00:48.000Z" // The date and time the media will expire
}
]
```
# Metadata on a media file
Source: https://www.ayrshare.com/docs/apis/media/get-media-metadata
GET /media/meta
Retrieve the metadata on an externally accessible media file URL, such as its format, codec, width, height, duration in seconds, size in bytes, and type.
Retrieve the metadata on a media file URL to check format, dimensions, and other details.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**url** `string` *required*
Encoded URI of the externally accessible media file URL.
```json 200: Success theme={"system"}
{
"codec": "h264",
"duration": 28.24, // In seconds
"filename": "https://img.ayrshare.com/random/landscape16.mp4",
"format": "QuickTime / MOV",
"height": 1080,
"size": 16275959, // In bytes
"type": "video",
"width": 1920
}
```
```json 400: Bad media URL theme={"system"}
{
"action": "post",
"status": "error",
"code": 229,
"message": "Error validating video. Please check the video or URL."
}
```
```json 503: Media probe temporarily unavailable theme={"system"}
{
"action": "post",
"status": "error",
"code": 499,
"message": "Ayrshare's media probe service is temporarily unavailable. Please retry shortly.",
"retryAvailable": true
}
```
# Media API Overview
Source: https://www.ayrshare.com/docs/apis/media/overview
Manage your image and video gallery by uploading and retrieving media, verifying media URLs, or skipping uploads by passing external URLs in mediaUrls.
Manage your image and video gallery by uploading and retrieving images and videos. Please be sure to verify the media URL before using it to post.
> [!TIP]
>
> If you already have your media accessible by an external URL, such as an S3 bucket, you can skip uploading the files to Ayrshare. Just POST to the `/post` endpoint with your externally accessible URL in the `mediaUrls` body parameter and your file will automatically be uploaded.
# Resize an Image
Source: https://www.ayrshare.com/docs/apis/media/resize
POST /media/resize
Use the resize endpoint to fit images to social network sizes, add watermarks, change backgrounds, apply effects, or crop using platform or dimensions.
The social networks have [specific requirements](/docs/media-guidelines/overview) for social media images. The resize endpoint allows you to choose a social network compatible image size, add watermarks, change backgrounds, add effects, crop, and more.
By default resizing will change the dimensions of an image, but not crop the image. You may instead crop the image. See below for details.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**mediaUrl** `string`
URL of image to be resized. Must begin with `https://`. Required if `file` not present.
`imageUrl` also works. If both are sent, `imageUrl` is used.
**platform** `string`
One platform to resize for, for example `"instagram"`. Required if `dimensions` not present, except for watermark-only or format-only (`convertToJpg`, `convertToWebP`) requests. See [platform
options](/docs/apis/media/resize#platform-options) for details.
**file** `object`
Send the media file as a multipart form-data object. Required if `mediaUrl` not present.
**watermark** `object`
URL and optional position of watermark to be applied to resized image. The watermark will appear
in the lower right corner of the image by default. See [watermark](/docs/apis/media/resize#watermark)
for details.
**effects** `object`
Change opacity, colors, etc. See [effects options](/docs/apis/media/resize#effects-options) for
details.
**dimensions** `object`
Object specifying `width` and `height` for resizing. If cropping, you may optionally specify the center `x` and `y` coordinates.
Default is the center of the image.
```json Dimensions theme={"system"}
{
"width": 500,
"height": 500,
"xCoordinate": 35, // optional for crop mode
"yCoordinate": 50 // optional for crop mode
}
```
> [!NOTE]
>
> Width and height required if platform is not specified.
**mode** `string` (default: resize)
Value: `resize`, `blur`, or `crop`. See [mode](/docs/apis/media/resize#mode) for details.
**convertToJpg** `boolean`
Automatically convert to a JPG file, such as from a PNG to a JPG file. 75% quality will be used.
See [convert to a JPG](/docs/apis/media/resize#convert-to-a-jpg-or-webp) for details.
**convertToWebP** `boolean`
Automatically convert to a WebP file, such as from a PNG to a WebP file. 75% quality will be used.
See [convert to a WebP](/docs/apis/media/resize#convert-to-a-jpg-or-webp) for details.
### Platform Options
Specify a platform as a String to use predefined dimensions of the image, or you may specify your own with the `dimensions` field.
For example `"platform": "facebook"` will set the dimension of the image as width 1200px and height 630px.
Note, that resize to these dimensions will not crop the image.
If you want to crop the image, you can use the `mode` parameter set to `crop` with the `dimensions` and `xCoordinate` and `yCoordinate` fields.
### Mode
#### Resize
Resize is the default mode that will change the dimensions of an image while maintaining its aspect ratio.
Resize the image to the specified dimensions without cropping any content.
Example JSON:
```json Resize theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"platform": "instagram",
"mode": "resize"
}
```
You can also specify custom dimensions using the `dimensions` field:
```json Resize with Dimensions theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"mode": "resize",
"dimensions": {
"width": 800,
"height": 600
}
}
```
Either the `platform` or the dimensions field `width` and `height` must be specified.
#### Crop
Crop will cut off "crop" the image to the specified dimensions. By default the center coordinate will be the center of the image. You may also specify your own x/y coordinates.
Example JSON:
```json Crop theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"platform": "instagram",
"mode": "crop"
}
```
You can also specify custom dimensions and optional crop coordinates using the `dimensions` field:
```json Crop with Dimensions theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"mode": "crop",
"dimensions": {
"width": 1080,
"height": 1080,
"xCoordinate": 35,
"yCoordinate": 50
}
}
```
Either the `platform` or the dimensions field `width` and `height` must be specified.
For square crops, if `width` or `height` are less than the dimensions of the provided image, the small of the `width` or `height` will be used. For example, if the image is 1200x800 and the crop requested is 1080x1080, the image returned will be 800x800.
#### Blur
Blur effect will duplicate the image as a background and blur the image.
Example blur JSON:
```json Blur theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"platform": "instagram",
"mode": "blur"
}
```
Example blur image:

### Watermark
#### Watermark Overview
You may add a watermark to the image by providing a URL, which must begin with `https://` and an optional position.
The watermark will by default appear in the bottom right corner of the image - `southeast`.
We recommend a PNG with a transparent background.
Example watermark JSON:
```json Watermark theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/random/photo-13.jpg",
"platform": "instagram",
"watermark": {
"url": "https://img.ayrshare.com/012/100-percent.png",
"position": "northeast" // optional
}
}
```
Example watermark image in southeast position:

#### Watermark Position
The position of the watermark can be one of the following:
`north`
`northeast`
`east`
`southeast`
`south`
`southwest`
`west`
`northwest`
`center`
### Effects Options
#### Color Hexadecimal
Hexadecimal value for color of background for blur. Only applicable if `"mode": "blur"`. String value, e.g. `"#A020F0"`
Example color background JSON:
```json Color Background theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg",
"platform": "instagram",
"mode": "blur",
"effects": {
"color": "#A020F0"
}
}
```
Example color background image:

#### Color: Grayscale, Sepia, Invert
You made change the primary image color by specifying `grayscale`, `sepia`, or `invert`. The field `"blur": true` is not required and should not be used if you do not want a background.
Example grayscale JSON:
```json Grayscale theme={"system"}
{
"mediaUrl": "https://img.ayrshare.com/random/photo-13.jpg",
"platform": "instagram",
"effects": {
"color": "grayscale"
}
}
```
Example grayscale image:

#### Opacity
Set the opacity of the image. Number value range: 0 - 1.
Example opacity JSON:
```json Opacity theme={"system"}
{
"effects": {
"opacity": 0.2
}
}
```
#### Quality
For JPG or JEPG images specify the quality, or amount of compression, of the image.
The lower the number the more compressed, but lower the image quality.
The higher the number the less compressed, but the higher the image quality. Number value range: 0 - 100.
Example quality JSON:
```json Quality theme={"system"}
{
"effects": {
"quality": 20
}
}
```
### Convert to a JPG or WebP
The `convertToJpg` and `convertToWebP` options allow you to transform images from their original format (such as PNG) to JPG or WebP format respectively.
By default, the converted images will have a quality setting of 75%.
You can customize the compression level by using the [quality](/docs/apis/media/resize#quality) parameter in the effects object.
Note that if your source image is already a JPG and you use `convertToJpg`, the API will simply resize the image to your specified dimensions without changing the format.
Example convert to JPG:
```json Convert to JPG theme={"system"}
{
"convertToJpg": true
}
```
Example convert to WebP:
```json Convert to WebP theme={"system"}
{
"convertToWebP": true
}
```
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"mediaUrl": "https://img.ayrshare.com/012/gb.jpg", "platform": "instagram"}' \
-X POST https://api.ayrshare.com/api/media/resize
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/media/resize", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
mediaUrl: "https://img.ayrshare.com/012/gb.jpg", // required
platform: "instagram"
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'mediaUrl': 'https://img.ayrshare.com/012/gb.jpg',
'platform': 'instagram'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/media/resize',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"https://img.ayrshare.com/012/gb.jpg",
"platform" => "instagram"
);
curl_setopt_array($curl, array(
CURLOPT_URL => 'https://api.ayrshare.com/api/media/resize',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => '',
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 0,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => http_build_query($data),
CURLOPT_HTTPHEADER => array(
'Authorization: Bearer API_KEY',
'Accept-Encoding: gzip'
),
));
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class AyrshareApiClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private const string BaseUrl = "https://api.ayrshare.com/api";
public AyrshareApiClient(string apiKey)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_apiKey}");
}
public async Task ResizeMediaAsync(string mediaUrl, string platform)
{
try
{
var requestData = new
{
mediaUrl = mediaUrl,
platform = platform
};
var content = new StringContent(
JsonSerializer.Serialize(requestData),
Encoding.UTF8,
"application/json"
);
var response = await _httpClient.PostAsync($"{BaseUrl}/media/resize", content);
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
return jsonResponse;
}
catch (HttpRequestException ex)
{
throw new Exception($"Failed to resize media: {ex.Message}", ex);
}
}
public void Dispose()
{
_httpClient.Dispose();
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"url": "https://media.ayrshare.com/9abf1426d6ce9122ef11c72bd62e59807c5cc083/8UbyBjHTxgHkAC1I37e6O.jpg",
"platform": "instagram",
"mode": "blur",
"effects": {
"color": "#A020F0"
}
}
```
```json 400: Failed Resize theme={"system"}
{
"action": "resize",
"status": "error",
"code": 312,
"message": "Invalid extension type. Extension: null. Please verify the extension is one of the following: png, jpg, jpeg and the file is accessible."
}
```
# Search Instagram audio
Source: https://www.ayrshare.com/docs/apis/media/search-instagram-audio
GET /media/instagram/audio/search
Search Instagram's licensed audio catalog for a track to attach to a Reel
Search Instagram's audio catalog by keyword. Use the returned `audioId` with [`instagramOptions.audioConfiguration`](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel) to attach the track when you publish a Reel.
> [!WARNING]
>
> **Requires Instagram linked through Facebook Login.** Instagram's Audio API is only available to accounts connected via **Facebook Login**, which needs a Facebook Page connected to the Instagram account. Accounts connected with **Instagram Login** (the Ayrshare default) cannot search, look up, or attach audio, and every audio request returns `code: 514`.
>
> This is a Meta restriction, not an Ayrshare one. Meta's documentation states the Audio API "is only available on the Instagram API with Facebook Login. It is not supported on the Instagram API with Instagram Login." No permission or plan upgrade changes it.
>
> **Three things are required:**
>
> 1. An Instagram **Business** or **Creator** account
>
> 2. A **Facebook Page connected** to that Instagram account
>
> 3. Instagram linked to Ayrshare with **Instagram Login disabled**, so the Facebook Login flow is used
>
> See the [step-by-step setup guide](/docs/dashboard/connect-social-accounts/instagram#music-attach-eligibility-facebook-login-required).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**query** `string` *required*
Keyword to search for, such as an artist or track name. An empty or whitespace-only value returns
`code: 101`.
**audioType** `string` (default: music)
Which catalog to search. Either `music` for licensed commercial tracks or `original_sound` for
audio taken from other creators' Reels. Any other value returns `code: 101`.
**limit** `integer`
Maximum number of tracks to return, from `1` to `100`. Non-integer or out-of-range values return
`code: 101`. When omitted, Instagram's own default applies.
## Response Fields
Instagram does not return every field for every track, so treat all track fields except `audioId` as optional.
**status** `string`
`success` when the search completed. An empty catalog match is still a success.
**query** `string`
The keyword that was searched, echoed back.
**audio** `array`
Matching tracks. An empty array means the search ran and matched nothing.
**Track fields**
**audioId** `string`
The id to pass as `audioConfiguration.audioId` when publishing a Reel.
**title** `string`
Track title.
**displayArtist** `string`
Artist name as Instagram displays it.
**durationMs** `integer`
Track length in milliseconds.
**thumbnailUrl** `string`
Cover artwork thumbnail URL.
**audioType** `string`
Either `music` or `original_sound`.
**isAdsEligible** `boolean`
Whether the track may be used in an ad. Licensed music is frequently unavailable for ads even
when it is available for an organic Reel.
```json 200: Success theme={"system"}
{
"status": "success",
"query": "summer",
"audio": [
{
"audioId": "17901234567890123",
"title": "Summer Nights",
"displayArtist": "The Example Band",
"durationMs": 32000,
"thumbnailUrl": "https://scontent.example.com/cover.jpg",
"audioType": "music",
"isAdsEligible": true
}
]
}
```
```json 200: No matches theme={"system"}
{
"status": "success",
"query": "asdfghjkl",
"audio": []
}
```
```json 400: Instagram Login account theme={"system"}
{
"status": "error",
"code": 514,
"action": "post",
"message": "Adding music to a Reel requires an Instagram account connected via Facebook Login. This profile is connected with Instagram Login, which Meta does not permit for music attach, search, or lookup. Please relink Instagram using Facebook Login and grant the instagram_content_publish permission, then try again.",
"platform": "instagram"
}
```
## Errors
| Code | Meaning |
| - | - |
| 101 | Missing or empty `query`, an unrecognized `audioType`, or a `limit` outside 1 to 100. |
| 156 | Instagram is not linked to this profile. |
| 161 | Instagram's authorization expired or was revoked. Relink the account. |
| 239 | Instagram rejected the request for an account-level reason, including an unavailable track. On search this reports 239 rather than the 517 the lookup endpoint returns. |
| 258 | Instagram returned a malformed response or an unmapped failure. |
| 425 | The connection is missing a required Instagram permission. Relink and grant all permissions. |
| 435 | Instagram's rate limit was reached. Wait before retrying. |
| 514 | Instagram is linked with Instagram Login. The Audio API requires Facebook Login. |
Codes 514 to 518 are described in [Instagram Reels audio errors](/docs/errors/errors-ayrshare#instagram-reels-audio-errors); the rest are in the [Ayrshare error reference](/docs/errors/errors-ayrshare).
## Next Steps
> [card] [Attach the track to a Reel](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel)
> [card] [Look up a single track](/docs/apis/media/get-instagram-audio)
# Upload Large Media Files
Source: https://www.ayrshare.com/docs/apis/media/upload-large-media
GET /media/uploadUrl
For file uploads greater than 10 MB, obtain a presigned URL to upload a file
For file uploads greater than 10 MB, obtain a presigned URL to upload a file.
Maximum file upload size 5 GB.
Upload presigned URL valid for 1 hour after being generated.
Access URL available for 30 days after uploaded. All published posts are unaffected at the
social networks. Scheduled posts beyond that time frame will result in errors at time of
publishing.
> [!TIP]
>
> If you already have your media accessible by an external URL, such as an S3 bucket, you can skip uploading the files to Ayrshare. Just POST to the `/post` endpoint with your externally accessible URL in the `mediaUrls` body parameter and your file will automatically be uploaded.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**fileName** `string`
If the contentType is not present, then a full file name with extension is required.
Name of the file to be uploaded. Must include an extension such as .png, .jpg, .mov, .mp4, etc.
**contentType** `string`
The content-type of the media being uploaded. Valid formats include: `mp4`, `mov`, `png`, `jpg`, or `jpeg`.
For example, if the file is a Quicktime .mov file, then the contentType should be `mov`.
If not present, the type is taken from the `fileName` extension. If neither gives a supported type, the request returns error `301`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer [API Key]" \
-X GET https://api.ayrshare.com/api/media/uploadUrl?fileName=test.mov&contentType=mov
```
```javascript JavaScript theme={"system"}
const API_KEY = "Your API Key";
fetch("https://api.ayrshare.com/api/media/uploadUrl?fileName=test.mov&contentType=mov", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer [API_KEY]'}
r = requests.get('https://api.ayrshare.com/api/media/uploadUrl?fileName=test.mov&contentType=mov', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/media/uploadUrl?fileName=test.mov&contentType=mov',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
namespace MediaUploadUrlGETRequest_csharp
{
class MediaUploadUrl
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string baseUrl = "https://api.ayrshare.com/api/media/uploadUrl";
// Build URL with query parameters
var uriBuilder = new UriBuilder(baseUrl);
var query = HttpUtility.ParseQueryString(string.Empty);
query["fileName"] = "test.mov";
query["contentType"] = "mov";
uriBuilder.Query = query.ToString();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(uriBuilder.Uri);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
## Response Details
`accessUrl` is the URL to access the media file after upload.
`contentType` is the content-type set for the media being upload. Use this in the *Content-Type*
header when uploading the media.
`uploadUrl` is the URL used to *PUT* the media file. Please see below.
### Additional Endpoint Examples
The process to upload larger files:
Obtain an `uploadURL` and `accessURL` via the `/media/uploadUrl` endpoint. Please see above.
Upload the file via a *PUT* with *Content-Type* set to the returned `contentType`.
Upload the media by using the `--upload-file` with a media file and the `uploadUrl`.
On a successful upload, a `200` response will be returned.
After uploading the media file, POST to the `/post` endpoint with the `accessUrl` in the
`mediaUrls` body parameter.
**The presigned upload URL may only be uploaded to once**. If you sent a bad file you must
create a new upload URL. No error response will occur if the file is not successfully uploaded.
See below of [verifying the URL exists](/docs/apis/media/verify-media-url).
```bash cURL theme={"system"}
curl -X PUT \
-H 'Content-Type: video/mp4' \
--upload-file LOCAL_FILE_PATH uploadUrl
```
```javascript JavaScript theme={"system"}
const fs = require("fs").promises;
const uploadFileToSignedUrl = async (signedUrl, filePath) => {
try {
const fileBuffer = await fs.readFile(filePath);
const response = await fetch(signedUrl, {
method: "PUT",
body: fileBuffer,
headers: {
"Content-Type": "video/mp4"
}
});
if (response.ok) {
console.log("File upload successful:", response.status);
} else {
console.error("File upload failed:", response.status);
}
} catch (error) {
console.error("Error uploading file:", error);
}
};
// Use the signed URL generated from the previous step
const signedUrl = "SIGNED_URL";
const filePath = "LOCAL_FILE_PATH";
uploadFileToSignedUrl(signedUrl, filePath);
```
```python Python theme={"system"}
import requests
def upload_file_to_signed_url(signed_url, file_path):
try:
with open(file_path, 'rb') as file:
response = requests.put(signed_url, data=file, headers={'Content-Type': 'video/mp4'})
if response.ok:
print("File upload successful:", response.status_code)
else:
print("File upload failed:", response.status_code)
except Exception as error:
print("Error uploading file:", error)
# Use the signed URL generated from the previous step
signed_url = "SIGNED_URL"
file_path = "LOCAL_FILE_PATH"
upload_file_to_signed_url(signed_url, file_path)
```
```php PHP theme={"system"}
getMessage();
}
}
// Use the signed URL generated from the previous step
$signedUrl = "SIGNED_URL";
$filePath = "LOCAL_FILE_PATH";
uploadFileToSignedUrl($signedUrl, $filePath);
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.IO;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string signedUrl = "SIGNED_URL"; // Replace with your signed URL
string filePath = "LOCAL_FILE_PATH"; // Replace with your file path
try
{
await UploadFileToSignedUrl(signedUrl, filePath);
}
catch (Exception ex)
{
Console.WriteLine("Error uploading file: " + ex.Message);
}
}
static async Task UploadFileToSignedUrl(string signedUrl, string filePath)
{
using (var client = new HttpClient())
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
using (var content = new StreamContent(fileStream))
{
content.Headers.Add("Content-Type", "video/mp4");
var response = await client.PutAsync(signedUrl, content);
if (response.IsSuccessStatusCode)
{
Console.WriteLine("File upload successful: " + response.StatusCode);
}
else
{
Console.WriteLine("File upload failed: " + response.StatusCode);
}
}
}
}
```
```java Java theme={"system"}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import java.nio.file.Path;
import java.io.IOException;
import java.nio.file.Files;
public class FileUploader {
public static void main(String[] args) {
String signedUrl = "SIGNED_URL"; // Replace with your signed URL
String filePath = "LOCAL_FILE_PATH"; // Replace with your file path
try {
uploadFileToSignedUrl(signedUrl, filePath);
} catch (IOException | InterruptedException e) {
System.out.println("Error uploading file: " + e.getMessage());
}
}
private static void uploadFileToSignedUrl(String signedUrl, String filePath) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(signedUrl))
.header("Content-Type", "image/jpg")
.PUT(BodyPublishers.ofFile(Path.of(filePath)))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
System.out.println("File upload successful: " + response.statusCode());
} else {
System.out.println("File upload failed: " + response.statusCode());
}
}
}
```
Please be sure that the `contentType` set when creating the `uploadUrl` matches the Content-Type and the file type when PUTing the file.
For example, if you set the `contentType` to "image/png" when creating the `uploadUrl`, be sure to set the `Content-Type: image/png` and the uploaded file ends in `.png`.
On a successful upload, a `200` response will be returned.
### Example Upload File Binary in Node.js
Here is an example of uploading a binary media file using Node with JavaScript:
```javascript theme={"system"}
const fs = require("fs");
const request = require("request");
const API_KEY = "Your API Key";
const fileName = "test.png";
const endpoint = `https://api.ayrshare.com/api/media/uploadUrl?fileName=${fileName}&contentType=png`;
const run = async () => {
request.get(
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
url: endpoint
},
(err, res, body) => {
if (err) {
return console.error(err);
}
const json = JSON.parse(body);
console.log("Upload URL:", json);
return fs.createReadStream(`./${fileName}`).pipe(
request.put(
json.uploadUrl,
{
headers: {
"Content-Type": json.contentType
}
},
(err, httpsResponse, body) => {
if (err) {
console.error("err", err);
} else {
console.log(body);
}
}
)
);
}
);
};
run();
```
### Example Upload Binary File in Postman
You may also use Postman to upload the binary file to the `uploadUrl`.
In Postman:
1. Select the HTTP Method `PUT`.
2. Paste your `uploadUrl` in the url field. Note, the url will expire after an hour and may only be used once. If you make a call and it fails, you must regenerate the `uploadUrl`.
3. In *Headers* set the `Content-Type` to be the content type returned in the /uploadUrl endpoint. For example: `Content-Type: image/png`.
4. Select *Body -> binary* and select the file to upload.
5. Press **Send**.
6. **Important**: No return response will occur, so you should check if the upload was successful by opening the `accessUrl` returned from the /uploadUrl endpoint in a browser. You may also use the [verify URL endpoint](/docs/apis/media/verify-media-url).

> [card] [Postman Sample JSON File]()
```javascript 200: An upload URL and access URL theme={"system"}
{
"accessUrl": "https://media.ayrshare.com/Aswmfs3dIEbwLSdhTlV2/test.mp4",
"contentType": "video/mp4",
"uploadUrl": "https://storage.googleapis.com/..."
}
```
```json 400: Bad Request Error getting signed URL theme={"system"}
{
"action": "upload",
"status": "error",
"code": 301,
"message": "The provided content-type 'movd' is not recognized."
}
```
# Upload Image or Video
Source: https://www.ayrshare.com/docs/apis/media/upload-media
POST /media/upload
Upload an image or small video as multipart form data or Base64 encoded file to get a URL for the /post endpoint, with a 30 MB limit and 90 day storage.
This endpoint allows you to upload a file or an image or small video to include in your post. Returned will be the URL to the image that can be used in the /post endpoint.
You can pass the file either as a ***multipart form data*** as a form parameter or a **Base64 encoded file** as a body parameter.
Important notes about media uploads:
1. For best performance, we recommend
Hosting media files on your own server (e.g. AWS S3).
Passing the media URL directly in the `mediaUrls` parameter of the [/post](/docs/apis/post/overview) endpoint.
This approach is faster than uploading files through this endpoint.
2. Media file retention
Uploaded files are stored for 90 days.
After 90 days:
Published posts on social networks are unaffected.
Scheduled posts will fail to publish if they reference expired media.
3. File size limits
Maximum file size: 30 MB.
For larger files, see our guide on [handling large media uploads](/docs/apis/media/upload-large-media).
> [!TIP]
>
> If you already have your media accessible by an external URL, such as an S3 bucket, you can skip uploading the files to Ayrshare. Just POST to the `/post` endpoint with your externally accessible URL in the `mediaUrls` body parameter and your file will automatically be uploaded.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Content-Type** `string` *required*
Use `multipart/form-data` if sending a multipart form data - see below. Otherwise, send the standard `application/json`.
## Body Parameters
**file** `string or object` *required*
Max 30 MB file size.
We recommend sending as a multipart form-data object instead of Base64 encoding.
**fileName** `string`
The name of the file to be uploaded.
**description** `string`
A description of the file.
### Send as Multipart Form-Data
Send the media file as a multipart form-data object. Please be sure to specify the `Content-Type` as mentioned above.
### Send as Base64
Send the media file as a Base64 encoded string as a Data URI string. The string should begin with `data:content/type;base64`
Example encoding with Output Format Data URI:
Note: The /post endpoint accepts larger files via an external URL with the `mediaUrls` parameter.
```bash cURL theme={"system"}
# Send as Multipart Form-Data
curl \
-H "Authorization: Bearer API_KEY" \
-F "file=@test.png" \
-F "fileName=test.png" \
-F "description=best image" \
-X POST https://api.ayrshare.com/api/media/upload
# Send as Base64
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"file": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...", "fileName": "test.png", "description": "best image"}' \
-X POST https://api.ayrshare.com/api/media/upload
```
```javascript JavaScript theme={"system"}
// Send as Multipart Form-Data
const FormData = require('form-data');
const fs = require('fs');
const API_KEY = "API_KEY";
const imagePath = './test.png';
const form = new FormData();
form.append('file', fs.createReadStream(imagePath));
form.append('fileName', 'test.png');
form.append('description', 'best image');
fetch("https://api.ayrshare.com/api/media/upload", {
method: "POST",
headers: {
"Authorization": `Bearer ${API_KEY}`
// Don't set Content-Type header - FormData will set it automatically with boundary
},
body: form
})
.then(res => res.json())
.then(json => console.log(json))
.catch(console.error);
// Send as Base64
const API_KEY = "API_KEY";
const base64 = "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...";
fetch("https://api.ayrshare.com/api/media/upload", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
file: base64,
fileName: "test.png",
description: "best image"
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
# Send as Multipart Form-Data
import requests
# For a local file:
files = {
'file': ('test.png', open('test.png', 'rb')),
}
# Form data
data = {
'fileName': 'test.png',
'description': 'best image'
}
headers = {
'Authorization': 'Bearer API_KEY'
}
r = requests.post(
'https://api.ayrshare.com/api/media/upload',
files=files,
data=data,
headers=headers
)
print(r.json())
# Send as Base64
import requests
payload = {'file': 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...',
'fileName': "test.png",
'description': "best image"}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/media/upload',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
# Send as Multipart Form-Data
[
'method' => 'POST',
'header' => "Authorization: Bearer API_KEY\r\n" .
"Content-Type: multipart/form-data; boundary=" . $boundary . "\r\n" .
"Content-Length: " . strlen($data) . "\r\n",
'content' => $data
]
];
// Send the request
$context = stream_context_create($options);
$result = file_get_contents('https://api.ayrshare.com/api/media/upload', false, $context);
// Print the response
echo $result;
# Send as Base64
'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...',
'fileName' => "test.png",
'description' => "best image"
];
$ch = curl_init('https://api.ayrshare.com/api/media/upload');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Authorization: Bearer API_KEY'
]);
$response = curl_exec($ch);
curl_close($ch);
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
```
```javascript 200: Success theme={"system"}
{
"id": "1167335b-6c37-4fc6-ab8a-044e0005d335-jpeg",
"url": "https://images.ayrshare.com/q3Ls85VTsrbODnGIJHpy7PaHWwA3/1167335b-6c37-4fc6-ab8a-044ed885d.jpeg",
"fileName": "fun.jpg",
"description": "good times"
}
```
```json 400: Bad Request Error in upload theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Verify Media URL Exists
Source: https://www.ayrshare.com/docs/apis/media/verify-media-url
POST /media/urlExists
Verify that a media file URL exists with a HEAD request and get its contentType. Use it with /media/uploadUrl; it checks reachability, not file contents.
Verify that the media file exists when uploaded. It should be used in conjunction with the /media/uploadUrl endpoint.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Verify Media URL Exists
`POST` `https://api.ayrshare.com/api/media/urlExists`
Verify that the media file exists when uploaded. It should be used in conjunction with the /media/uploadUrl endpoint.
You will also receive the `contentType` of the media file.
A `HEAD` request is made to the media URL to verify it exists.
Please be sure the hosting provider is not blocking the `HEAD` request.
This endpoint checks reachability only. It does not run the deeper media probe used before publishing, validate the file's contents or platform requirements, or prove that a social network's crawler can download the media. A successful response can therefore still be followed by a media-validation error during `/post`.
## Body Parameters
**mediaUrl** `string` *required*
URL of the media to verify exists.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"mediaUrl": "https://img.ayrshare.com/012/vid.mp4"}' \
-X POST https://api.ayrshare.com/api/media/urlExists
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/media/urlExists", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
mediaUrl: "https://img.ayrshare.com/012/vid.mp4"
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'mediaUrl': 'https://img.ayrshare.com/012/vid.mp4'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/media/urlExists',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/media/urlExists',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'mediaUrl' => 'https://img.ayrshare.com/012/vid.mp4'
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```json 200: OK Media URL is valid theme={"system"}
{
"status": "success",
"statusCode": 200,
"contentType": "image/jpeg"
}
```
```json 404: Not Found Media file not found theme={"system"}
{
"status": "error",
"statusCode": 404,
"statusText": "Not Found"
}
```
# Get Auto Response
Source: https://www.ayrshare.com/docs/apis/messages/get-auto-response
GET /messages/autoresponse
Get the auto response setting, including whether it is active, the message text, and wait seconds. If no settings exist, only a status is returned.
Get the auto response setting. If the auto response settings do not exist, no data, except a `status`, will be returned.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```json 200: Success theme={"system"}
{
"status": "success",
"autoResponseActive": true,
"autoResponseMessage": "Howdy Buckaroo!",
"autoResponseWaitSeconds": 30
}
```
```json 200: No Settings theme={"system"}
{
"status": "success"
}
```
# Get Messages
Source: https://www.ayrshare.com/docs/apis/messages/get-messages
GET /messages/:platform
Get messages or conversations for Facebook, Instagram, X/Twitter, or WhatsApp, with filters for status, conversationId, and limit, plus caching details.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get messages or conversations for a messaging platform.
Retrieval times differ on each social network. On Facebook, Instagram, and WhatsApp, messages are available via Ayrshare in real time. On X/Twitter, there is a delay of up to 3 minutes to see new message updates. Please contact support to learn more about the Enterprise Plan if you need real-time X/Twitter message access.
> [!NOTE]
>
> **Response caching:** For **Facebook** and **Instagram**, responses are cached for **60
> seconds**. For **X/Twitter**, responses are cached for **15 seconds** to better support polling.
> WhatsApp reads messages already received through Meta webhooks, so a newly received message can
> appear as soon as Ayrshare processes its webhook. The response still includes `lastUpdated` and
> `nextUpdate` metadata.
> [!NOTE]
>
> Initial message history retrieval for Facebook and Instagram is limited to the
> last 20 messages. Please see the [Message History Retrieval for Facebook and
> Instagram](/docs/apis/messages/overview#message-history-retrieval-for-facebook-and-instagram)
> section for more information.
> [!NOTE]
>
> WhatsApp conversations are identified by the correspondent's phone number
> (digits only, E.164 without the leading `+`). A stored outbound message may include a `status`
> value of `sent`, `delivered`, `read`, or `failed`.
> [!NOTE]
>
> WhatsApp messages sent through [Send Message](/docs/apis/messages/send-message) are not currently
> added to Get Messages history. Incoming WhatsApp messages received through webhooks are stored
> and returned here.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**platform** `string` *required*
The platform to get the message: `facebook`, `instagram`, `twitter`, `whatsapp`
## Query Parameters
**status** `string` *required* (default: active)
Return active conversations or archived conversations. Values: `active` or `archived`.
**conversationId** `string`
Only return the specific conversation.
**action** `string`
Only return messages in one direction. Values: `sent` or `received`. Omit to
return both.
**conversationsOnly** `boolean` (default: false)
Return conversations instead of messages. Returns one page of conversations — follow `meta.pagination.next` for the rest. If `true`, `conversationId` is ignored.
> [!NOTE]
>
> When `conversationsOnly=true`, conversation details are returned in
> `converstationsDetails`. This spelling is part of the current API response.
**limit** `integer`
Page size for messages and for conversation lists: an integer from 1 to 100,
with a default and maximum of **100**.
On X/Twitter `limit` also chooses which set of messages you read. Omit it to
page your stored messages, which is what every other platform does and what
applies `status`, `action` and `conversationsOnly`. Supply it to page X's live
timeline through X's own cursor instead, which applies none of those three
filters.
That choice is made by the **first** request only. Once you are following a
cursor, the cursor decides: a stored cursor keeps returning stored messages
even if you send `limit`, and an X cursor keeps returning the live timeline
even if you omit it. You cannot switch between the two mid-pagination.
**next** `string`
Opaque cursor from `meta.pagination.next`. Pass it as `next` with the same
profile, platform and filters. Continue while `meta.pagination.hasMore` is
`true`, even if a filtered page comes back short or empty. `limit` may be
omitted on subsequent requests and defaults to 100; on X/Twitter the cursor
already records which set of messages it came from, so sending or omitting
`limit` alongside it will not switch you to the other one.
### Fetching the next page
Start with `GET /api/messages/instagram?limit=100`. To continue, URL-encode the
returned `meta.pagination.next` value and send
`GET /api/messages/instagram?limit=100&next=`. The parameter is
`next`, not `cursor`. Keep the same Profile-Key and query filters throughout.
Check `meta.pagination.hasMore`, not the number of results, to decide when to
stop. A filtered request may return fewer than `limit` results, or an empty page
with a continuation, because each request scans a bounded portion of history.
Messages in stored history are ordered newest first, with document ID breaking
ties. Conversation lists are ordered by document ID. Paging is not a frozen
snapshot: new messages appear when you restart from the first page, and changing
conversation statuses can change filtered results.
Existing message fields and the `converstationsDetails` spelling are unchanged.
Integrations that previously expected a complete list from one request — whether
of messages or of conversations, on any platform — must follow the continuation
cursor; increasing `limit` above 100 is not supported.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/messages/facebook
```
```javascript JavaScript theme={"system"}
const apiKey = 'API_KEY';
const url = 'https://api.ayrshare.com/api/messages/facebook';
const headers = {
'Authorization': `Bearer ${apiKey}`,
};
fetch(url, {
method: 'GET',
headers: headers,
})
.then(response => {
if (response.ok) {
return response.json();
} else {
throw new Error(`Request failed. Status code: ${response.status}`);
}
})
.then(data => {
console.log('Response:', data);
})
.catch(error => {
console.error('Error:', error.message);
});
```
```python Python theme={"system"}
import requests
api_key = 'API_KEY'
url = 'https://api.ayrshare.com/api/messages/facebook'
headers = {
'Authorization': f'Bearer {api_key}',
}
response = requests.get(url, headers=headers)
if response.status_code == 200:
data = response.json()
print('Response:', data)
else:
print(f'Request failed. Status code: {response.status_code}')
```
```php PHP theme={"system"}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using System.Text.Json;
class Program
{
static async Task Main()
{
string apiKey = "API_KEY";
string url = "https://api.ayrshare.com/api/messages/facebook";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
try
{
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadAsStringAsync();
var responseData = JsonSerializer.Deserialize(responseBody);
Console.WriteLine("Response:");
Console.WriteLine(responseData);
}
else
{
Console.WriteLine($"Request failed. Status code: {response.StatusCode}");
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) {
String apiKey = "API_KEY";
String url = "https://api.ayrshare.com/api/messages/facebook";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.GET()
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String responseBody = response.body();
System.out.println("Response:");
System.out.println(responseBody);
} else {
System.out.println("Request failed. Status code: " + response.statusCode());
}
} catch (IOException | InterruptedException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"messages": [
{
"senderId": "106638148652444",
"senderDetails": {
"name": "Ayrshare"
},
"conversationId": "t_10161117434308444",
"created": "2024-06-06T00:54:32.455Z",
"action": "sent",
"recipientId": "7101149746568444",
"id": "m_JH6o-yS83JoxWmQaLrmgSaHwGtfTgQ",
"message": "Howdy!",
"platform": "facebook",
"reactions": {
"7101149746568522": "😆". // Reaction by the customer on the Howdy! message
}
},
{
"recipientDetails": {
"name": "Sara Smith",
"id": "736532028017333",
"picture": "https://img.ayrshare.com/333/messages/facebook-eTZzhE2b.jpeg"
},
"senderId": "106638148652329",
"attachments": [
{
"type": "image",
"url": "https://scontent.xx.fbcdn.net/v/t1.15752-9/490986808_1193328359195158"
}
],
"conversationId": "t_3567590438533",
"created": "2024-06-06T00:54:32.455Z",
"action": "sent",
"recipientId": "736532028017333",
"id": "m_WJlfgzopxfdRM1wFTKYHKv7zh75P",
"updated": "2024-06-06T00:54:32.455Z",
"platform": "facebook",
"senderDetails": {
"name": "Ayrshare"
}
},
{
"senderId": "7101149746568444",
"senderDetails": {
"name": "John Smith",
"profileImage": "https://platform-lookaside.fbsbx.com/platform/profilepic/"
},
"conversationId": "t_10161117434308444",
"created": "2024-06-06T00:54:28.102Z",
"action": "received",
"recipientId": "106638148652329",
"id": "m_HGbotYJUmf4AzyPlJ-2uZqHwGtfTgQihX",
"message": "Look up!",
"platform": "facebook"
},
{
"senderId": "7101149746568444",
"senderDetails": {
"name": "John Smith",
"profileImage": "https://platform-lookaside.fbsbx.com/platform/profilepic/"
},
"conversationId": "t_10161117434308444",
"created": "2024-06-06T00:49:11.679Z",
"action": "received",
"recipientId": "106638148652444",
"id": "m_jXoYQIwTXaq2u06PG6Z8vaHwGtfTgQ",
"message": "How is the weather?",
"platform": "facebook"
}
],
"lastUpdated": "2024-06-09T21:46:04.233Z",
"nextUpdate": "2024-06-09T21:47:04.233Z"
}
```
```json 200: Paginated (X/Twitter) theme={"system"}
{
"status": "success",
"messages": [
{
"id": "1893410668991234567",
"conversationId": "1234567890-9876543210",
"senderId": "9876543210",
"created": "2024-06-09T21:30:00.000Z",
"message": "Hey, how are you?",
"action": "received",
"senderDetails": {
"name": "Jane Doe",
"username": "janedoe"
}
},
{
"id": "1893410668991234566",
"conversationId": "1234567890-9876543210",
"senderId": "1234567890",
"created": "2024-06-09T21:28:00.000Z",
"message": "Hello!",
"action": "sent",
"senderDetails": {
"name": "My Account",
"username": "myaccount"
}
}
],
"messagesCount": 2,
"lastUpdated": "2024-06-09T21:46:04.233Z",
"nextUpdate": "2024-06-09T21:46:19.233Z",
"meta": {
"pagination": {
"hasMore": true,
"limit": 5,
"next": "eyJwYWdpbmF0aW9uVG9rZW4iOiIxODkzNDEwNjY4OTkxMjM0NTY1In0="
}
}
}
```
```json 200: WhatsApp theme={"system"}
{
"status": "success",
"messages": [
{
"id": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI3MEM3Q0NDQjlGRTUzMjJBNEUA",
"conversationId": "14155551234",
"senderId": "14155551234",
"recipientId": "123456789012345",
"created": "2026-05-18T17:10:02.000Z",
"message": "When is my order shipping?",
"action": "received",
"platform": "whatsapp"
},
{
"id": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI4OUYxRkExNzE0M0EwQTYwM0EA",
"conversationId": "14155551234",
"senderId": "14155551234",
"recipientId": "123456789012345",
"created": "2026-05-18T17:08:41.000Z",
"attachments": [
{
"type": "image",
"url": "https://img.ayrshare.com/abc123/whatsapp/whatsapp_image_x9aB2pK7.jpg"
}
],
"action": "received",
"platform": "whatsapp"
}
],
"lastUpdated": "2026-05-18T17:13:00.000Z",
"nextUpdate": "2026-05-18T17:14:00.000Z"
}
```
```json 200: WhatsApp Conversations theme={"system"}
{
"status": "success",
"conversationIds": ["14155551234", "447700900123"],
"converstationsDetails": [
{
"id": "14155551234",
"participant": {
"id": "14155551234",
"name": "Jane Customer"
},
"status": "active",
"unread": 2
},
{
"id": "447700900123",
"participant": {
"id": "447700900123"
},
"status": "active",
"unread": 0
}
],
"lastUpdated": "2026-05-18T17:13:00.000Z",
"nextUpdate": "2026-05-18T17:14:00.000Z"
}
```
```json 200: Conversations theme={"system"}
{
"status": "success",
"conversationIds": ["t_10161117434308444", "t_356759043857444"],
"converstationsDetails": [
{
"id": "t_10161117434308444",
"participant": {
"name": "John Smith",
"id": "7101149746568444",
"picture": "https://platform-lookaside.fbsbx.com/platform/profilepic/"
},
"status": "active",
"watermark": 1717889607444
},
{
"id": "t_356759043857444",
"participant": {
"name": "Sara Johnson",
"id": "7365320280173444",
"picture": "https://platform-lookaside.fbsbx.com/platform/profilepic/"
},
"status": "active"
}
],
"lastUpdated": "2024-06-09T21:46:04.233Z",
"nextUpdate": "2024-06-09T21:47:04.233Z"
}
```
```json 403: Messaging Not Enabled theme={"system"}
{
"action": "messages",
"status": "error",
"code": 361,
"message": "Messaging is not enabled for this User Profile. Please subscribe to Messaging and activate Messaging for this User Profile."
}
```
```json 400: Social Account Needs Relinking theme={"system"}
{
"action": "messages",
"status": "error",
"code": 362,
"message": "The social network needs to be relinked to access Messaging. Please unlink, relink, and try again.",
"resolution": {
"relink": true,
"platform": "facebook"
}
}
```
```json 403: X App Missing Direct Message Permission theme={"system"}
{
"action": "messages",
"status": "error",
"code": 520,
"message": "X rejected this request because the linked X app does not have Direct Message permission. In the X Developer Portal, set the User authentication settings for the app to \"Read and write and Direct messages\", then unlink and relink the X account. X tokens keep the permissions they were created with, so changing the app setting alone is not enough.",
"resolution": {
"relink": true,
"platform": "twitter"
}
}
```
# Messages API Overview
Source: https://www.ayrshare.com/docs/apis/messages/overview
Send and receive direct messages on Facebook, Instagram, X/Twitter, and WhatsApp
The Messaging API allows you to manage direct messages (DM) to correspondents who contact your User Profiles.
A correspondent is the person with whom your user (User Profile) is communicating with.
A conversation is a series of messages between your user (User Profile) and their correspondent.
The Messaging API is included with all Ayrshare Business Plans and as a paid add-on for Premium
plans.
For both Business and Premium plans, you must activate Messaging in the Account page of the
Ayrshare dashboard.
Messaging is available for Facebook Messenger, Instagram Direct Messenger,
X Direct Messages, and WhatsApp (Private Beta).
> [!NOTE]
>
> **WhatsApp is in Private Beta.** Send and receive WhatsApp messages through
> the Messaging API. If you'd like early access, email
> [lotty@ayrshare.com](mailto:lotty@ayrshare.com).
> [card] [Learn more about the Messaging API](https://www.ayrshare.com/ayrshare-messaging-api-for-facebook-instagram-and-x-twitter/)
## Key Messaging Features
Manage your users' conversations with correspondents.
Sending text, image, video, and emoji messages on behalf of your users.
Retrieving complete conversation histories.
Setting up automated message responses.
Receiving real-time updates via webhooks for messages received, message reactions, read
receipts.
## Enable Messaging
### Enable Your Ayrshare Account and User Profiles
You must first enable messaging for your overall Ayrshare account to manage DMs. In the Ayrshare dashboard go to the [Account page](https://app.ayrshare.com/account) and click the "Learn More" button and then "Enable". At this point you have enabled messaging for your overall account, *but have not activated messaging for individual User Profiles*.
You can activate messaging for individual User Profiles either in the [User Profiles page](https://app.ayrshare.com/manage-profiles) by clicking "Messaging Active" checkbox for each profile or using the create or update [profiles endpoint](/docs/apis/profiles/overview).
Facebook and Instagram grant the messaging permissions when an account is linked, so activate messaging before you link accounts. This works the same whether you activate messaging in the dashboard or with the profiles endpoint:
* **Accounts linked after messaging was activated** need no relink.
* **Accounts linked before messaging was activated** need a relink if their "Messaging" badge shows as a warning instead of the normal badge.
* **Accounts linked through Ayrshare Connect** (the [widget](/docs/multiple-users/connect-widget) or [direct mode](/docs/multiple-users/connect-direct-mode)) never get the messaging permissions. To use messaging, relink them on the User Profile's social linking page.
Relink accounts on the [Social Accounts page](https://app.ayrshare.com/social-accounts) or on the User Profile's [social linking page](/docs/multiple-users/user-integration). To skip the relink, set `messagingActive: true` when you [create the User Profile](/docs/apis/profiles/create-profile), before linking any accounts.
Accounts that are ready show the normal "Messaging" badge.
> [!NOTE]
>
> The Messages endpoints report these setup steps with distinct error codes, so you can tell them apart
> programmatically:
>
> * **`403` / code `361`** — Messaging is not enabled. Either the account is not subscribed to Messaging, or this
> User Profile has not been activated. Complete the steps above; relinking a social account will not help.
> * **`400` / code `362`** — Messaging is active for the User Profile, but this **Facebook or Instagram** account
> still needs to be relinked. This response includes `resolution: { "relink": true }`.

### Enable Messaging for WhatsApp
> [!NOTE]
>
> **WhatsApp is in Private Beta.** The setup flow and Messaging API endpoints are available to
> approved beta accounts. To request access, email
> [lotty@ayrshare.com](mailto:lotty@ayrshare.com).
Link a WhatsApp Business account through Meta's **embedded signup** flow directly from the
Ayrshare dashboard:
You need a **Meta Business Manager** account, a **WhatsApp Business Account (WABA)**, and an
eligible phone number. Meta verifies the account and phone number during embedded signup.
On the Social Accounts page, click **Link** on the WhatsApp tile. Ayrshare launches Meta's
embedded signup pop-up which walks the customer through selecting/creating their Business
Manager, WABA, and phone number, and accepting Meta's terms.
When signup finishes, Ayrshare verifies ownership of the selected WABA and phone number,
completes registration, and subscribes the account to WhatsApp webhooks. You do not enter a
separate registration PIN in the Ayrshare dashboard.
### Important WhatsApp-Specific Behavior
**Recipients are phone numbers, not IDs.** WhatsApp identifies correspondents by E.164
phone number (digits only, no `+`). Conversation IDs returned from Ayrshare are also the
correspondent's phone number — there are no opaque conversation IDs the way Facebook and
Instagram use.
**24-hour customer service window.** WhatsApp Business only allows free-form outbound
messages within 24 hours of the most recent message from the correspondent. Outside that
window Meta rejects the send. Templated messages outside the 24-hour window are not currently
supported through Ayrshare.
**Address a recipient directly with the API.** Pass the recipient's phone number to
[Send Message](/docs/apis/messages/send-message). Meta only delivers a free-form message when that
recipient is inside the 24-hour customer service window. Approved templates are required for
cold outreach, and Ayrshare does not currently expose template sending.
**Delivery status uses one field.** When available on a stored outbound message,
[Get Messages](/docs/apis/messages/get-messages) returns `status` as `sent`, `delivered`, `read`, or
`failed`. Delivery-status updates and WhatsApp reactions are not currently pushed to your
registered webhook URL.
**Media types and sizes** follow Meta's published WhatsApp Cloud API limits — images up to
5 MB, audio and video up to 16 MB, and documents up to 100 MB at the time of writing.
**Incoming messages are marked as read.** Ayrshare marks each incoming WhatsApp message as read as soon as it arrives, so the sender sees it as read right away.
**Keep Messaging on for the receiving profile.** Incoming WhatsApp messages for a profile that does not have Messaging turned on are not stored or sent to your webhook. Turn it on from the Profiles page of the Ayrshare dashboard, or set `messagingActive` to `true` with [Update Profile](/docs/apis/profiles/update-profile).
### Enable Messaging in the Instagram App
You may need to enable messaging for your Instagram account:
Go to your profile in the Instagram mobile app and tap the menu icon ≡ in the upper right corner
to go to the **Settings and activity** page.
Scroll down to the **How others can interact with you** section.
Click **Messages and story replies**.
Tap **Message requests**.
Under **Connected tool** toggle on the **Allow access to messages** switch.
> [!TIP]
>
> The **Message requests** setting shown here also governs whether a *recipient* can be reached. If you use [comment-triggered automations](/docs/apis/automations/overview) to DM people who comment on your posts, delivery depends on each recipient's Message requests setting — a message can be accepted by Instagram (`sent`) and then silently dropped. See [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered).
## Important Information on Messaging
> [!NOTE]
>
> **For Facebook and Instagram, a conversation must be initiated by the correspondent.** Once a
> conversation is established, you may then send messages, receive messages, get reactions (e.g.
> thumbs ups), or get read receipts on behalf of your users. WhatsApp follows the 24-hour customer
> service window described above.
> [!NOTE]
>
> **Your user must respond to an Instagram conversation within 7 days of the last message the
> correspondent sent.** If the correspondent has not sent a message in 7 days the conversation is
> considered inactive and cannot be responded to.
> [!NOTE]
>
> **Comment-triggered automations use a different 7-day window.** A [`comment_keyword` automation](/docs/apis/automations/overview) replies to a *comment* via a private reply, whose 7-day window is anchored to the comment (not to a prior message), so no existing conversation is required. Delivery is still subject to the recipient's Message requests setting — see [Automation DM Sent but Not Delivered](/docs/help-center/technical-support/automation-dm-sent-but-not-delivered).
### Message History Retrieval for Facebook and Instagram
When you first enable messaging for a user profile with a linked Facebook or
Instagram, there are important limitations on accessing conversation history:
#### Initial Connection Limitations
Facebook and Instagram only allow retrieval of the **last 20 messages** from existing
conversations when you first link an account for messaging
If a conversation has more than 20 messages, only the most recent 20 will be accessible
initially
#### After Initial Setup
Once messaging is enabled, **all new messages** sent and received will be fully accessible
Ayrshare maintains complete conversation history going forward
For example: If you initially retrieve 20 messages, then 100 new messages are exchanged, you'll
be able to access all 120 messages total
This limitation only applies to historical messages that existed before enabling
messaging - all future conversation activity for Facebook and Instagram will be
fully preserved and accessible.
### Conversation Limit & Pricing
Each Ayrshare User Profile can have up to **1,000 active conversations per
billing cycle**. The active conversation count is the sum across all social
networks. Each User Profile has its own count, including messages sent
through the API with that profile's `Profile-Key`.
**Premium plan**: Messaging is a flat **\$49 per month** add-on per account.
**Launch and Business plans**: Messaging is billed at **\$0.09 per active
conversation** via Stripe metered billing. Volume-based discounts are
available; contact your Ayrshare account representative for details.
A conversation is counted as active for the billing cycle when you send a
message to a recipient and the social network accepts it. Inbound messages
from a recipient do not, on their own, mark a conversation as active.
Messages Ayrshare sends for you automatically count the same as messages
you send yourself. This includes [auto responses](/docs/apis/messages/set-auto-response)
and the `send_dm` action in [Automations](/docs/apis/automations/overview).
If the 1,000 conversation limit has been reached, you can still receive
messages and keep replying in conversations that are already active this
billing cycle, but you cannot message a new recipient until the start of
the next billing cycle. This applies to auto responses and automation DMs
too, with one exception: an automation DM triggered by a comment is still
sent, and still counted (see [Automations](/docs/apis/automations/overview#rate-limits-and-caps)).
The number of messages, sent or received, in an active conversation is unlimited.
If you need to increase the conversation limit, please contact your
Ayrshare account representative.
You can see the current conversation count with the [user endpoint](/docs/apis/user/overview).
### Messaging Control Issues on Facebook and Instagram
Meta (Facebook and Instagram) uses a "thread control" system for messaging conversations. This means only one app can send messages in a conversation at a time, while other apps can only receive messages.
#### What Does This Mean?
If you see the error **"Message failed to send because another app is controlling this thread now"**, it means:
* A different third-party app currently controls that conversation
* Ayrshare can only receive messages in that conversation
* Ayrshare cannot send messages until it regains control
#### How to Fix This Issue
To allow Ayrshare to send messages again, you need to remove the third-party app that's controlling the conversation.
Please note that Meta (Facebook and Instagram) may update the process for removing third-party apps, so use the following steps as a general guideline.
**For Facebook Pages:**
1. Go to [Facebook.com](https://facebook.com) and log into your account
2. Click the menu (☰) in the top right → **Settings & Privacy** → **Settings**
3. In the left sidebar, click **Apps and Websites**
4. Find the third-party app that's controlling your messaging
5. Click **Remove** next to that app
6. Confirm the removal
**For Instagram Business Accounts:**
1. Open the Instagram app on your phone
2. Go to your **Profile** → tap the menu (☰) → **Settings and Privacy**
3. Tap **Apps and Websites**
4. Find and remove the third-party app
**Alternative for Instagram:**
1. Go to [business.facebook.com](https://business.facebook.com)
2. Navigate to **Business Settings**
3. Under **Accounts**, click **Instagram Accounts**
4. Select your Instagram account
5. Check for connected apps and remove the problematic one
#### After Removal
Once you've removed the third-party app:
Thread control will automatically return to Ayrshare
You should be able to send messages normally again
No further action is required on your part
If you continue to experience issues after removing the app, please contact our support team.
## Message WebHooks
See [Messages Webhooks](/docs/apis/webhooks/actions#messages-action) to automatically receive messages, read receipts, or reactions.
# Send Message
Source: https://www.ayrshare.com/docs/apis/messages/send-message
POST /messages/:platform
Send a direct message on Facebook, Instagram, X/Twitter, or WhatsApp with text or mediaUrls; reactions are WhatsApp only. Active conversation pricing applies.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Send a new direct message to a recipient.
> [!NOTE]
>
> **Pricing & Limits:** Each User Profile can have up to **1,000 active
> conversations per billing cycle** across all social networks. Pricing depends
> on your plan: **Premium** plans pay a flat **\$49 per month** add-on, while
> **Launch** and **Business** plans are billed at **\$0.09 per active
> conversation**. A conversation is counted as
> active when you send a message to a recipient during the billing cycle;
> inbound messages alone do not count. The number of messages within an active
> conversation is unlimited.
You can send an emoji as part of the `message` text.
Facebook and Instagram `mediaUrls` must end in a known extension. Including query parameters
will cause the media URL to fail.
> [!NOTE]
>
> **WhatsApp 24-hour customer service window.** WhatsApp Business only permits free-form
> outbound messages within **24 hours** of the most recent message from the correspondent. After
> that window, message sends will be rejected by Meta until the correspondent messages again.
> Templated messages outside the 24-hour window are not currently supported through this endpoint.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**platform** `string` *required*
The platform to send the message: `facebook`, `instagram`, `twitter`, `whatsapp`
## Body Parameters
**recipientId** `string`
The ID of the message recipient. Required for Facebook, Instagram, and WhatsApp. For X/Twitter,
send `recipientId` or `conversationId`.
For **Facebook**, **Instagram**, and **X/Twitter** this is the platform-specific user ID
(PSID, IGSID, or user ID).
For **WhatsApp** this is the recipient's phone number in E.164 digits-only format (for
example `14155551234`, not `+1 (415) 555-1234`).
**message** `string`
The message to send to the recipient. Required unless `mediaUrls` or `reaction` is sent. Must be
left out when sending a `reaction`.
Facebook, Instagram, and WhatsApp `message` may be an empty string or omitted entirely when
`mediaUrls` is provided.
X requires a message of at least one character even if a `mediaUrls` is provided.
**mediaUrls** `array`
Array of media URLs for attaching images, video, audio (including voice messages), or documents.
URLs of media items should end in the file extension without additional parameters appended.
Facebook and Instagram support multiple media URLs.
X only accepts a single media URL.
Voice messages are supported on Facebook and Instagram as an `aac` or `wav` file.
**WhatsApp** accepts multiple media URLs. Ayrshare fetches each URL and uses its response
`Content-Type` to select the WhatsApp media category. Meta accepts:
**conversationId** `string`
X/Twitter only. Send the message into an existing conversation instead of to a `recipientId`.
Get the ID from [Get Messages](/docs/apis/messages/get-messages).
**reaction** `object`
WhatsApp only. React to a message instead of sending one. Do not send `message` or `mediaUrls`
with a reaction.
`messageId`: Required. The ID of the WhatsApp message to react to.
`emoji`: Required. A single emoji. Send an empty string `""` to remove your reaction.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"message": "What's up!", "recipientId": "283j839222"} \
-X POST https://api.ayrshare.com/api/messages/instagram
```
```javascript JavaScript theme={"system"}
const apiKey = 'API_KEY';
const url = 'https://api.ayrshare.com/api/messages/instagram';
const data = {
message: "What's up!",
recipientId: '283j839222',
};
const headers = {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
};
fetch(url, {
method: 'POST',
headers: headers,
body: JSON.stringify(data),
})
.then(response => response.json())
.then(data => {
console.log('Response:', data);
})
.catch(error => {
console.error('Error:', error);
});
```
```python Python theme={"system"}
import requests
api_key = 'API_KEY'
url = 'https://api.ayrshare.com/api/messages/instagram'
data = {
'message': "What's up!",
'recipientId': '283j839222',
}
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
}
response = requests.post(url, json=data, headers=headers)
print('Response:', response.json())
```
```php PHP theme={"system"}
"What's up!",
'recipientId' => '283j839222',
];
$headers = [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if ($response === false) {
$error = curl_error($ch);
echo 'Error: ' . $error;
} else {
$responseData = json_decode($response, true);
print_r($responseData);
}
curl_close($ch);
?>
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
string apiKey = "API_KEY";
string url = "https://api.ayrshare.com/api/messages/instagram";
var data = new
{
message = "What's up!",
recipientId = "283j839222"
};
var jsonData = JsonSerializer.Serialize(data);
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
try
{
var response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
var responseData = JsonSerializer.Deserialize(responseBody);
Console.WriteLine("Response:");
Console.WriteLine(responseData);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) {
String apiKey = "API_KEY";
String url = "https://api.ayrshare.com/api/messages/instagram";
String data = "{\"message\": \"What's up!\", \"recipientId\": \"283j839222\"}";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(data))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
String responseBody = response.body();
System.out.println("Response:");
System.out.println(responseBody);
} else {
System.out.println("Request failed. Status code: " + response.statusCode());
}
} catch (IOException | InterruptedException e) {
System.out.println("Error: " + e.getMessage());
}
}
}
```
```json 200: Success theme={"system"}
{ // single text message
"status": "success",
"recipientId": "72706337063589124",
"messageId": "aWdfZAG1faXRlbToxOkl",
"message": "What is up?"
}
```
```json 400: Error theme={"system"}
{
// single text message
"action": "messages",
"status": "error",
"code": 363,
"message": "The recipient ID was not found. Please confirm the recipient ID is correct."
}
```
```json 200: Media Urls theme={"system"}
{
// single image
"status": "success",
"recipientId": "761943",
"messageId": "m_EgvfBrgjaM",
"type": "image",
"mediaUrl": "https://img.ayrshare.com/012/gb.jpg"
}
```
```json 400: Media Urls theme={"system"}
{
// text and image message
"action": "send",
"status": "error",
"code": 337,
"message": "Error sending message.",
"messages": [
{
"recipientId": "727063370635",
"messageId": "aWdfZAG1faXRlbT",
"message": "What a great day"
},
{
"action": "messages",
"status": "error",
"code": 365,
"message": "An error occurred sending the message attachment. Please verify the attachment is still available and try again."
}
]
}
```
```json 200: WhatsApp Text theme={"system"}
{
"status": "success",
"messagingProduct": "whatsapp",
"contacts": [
{
"input": "14155551234",
"waId": "14155551234"
}
],
"recipientId": "14155551234",
"messageId": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI4OUYxRkExNzE0M0EwQTYwM0EA",
"message": "Thanks — your order has shipped."
}
```
```json 200: WhatsApp Text + Media theme={"system"}
{
"status": "success",
"messages": [
{
"messagingProduct": "whatsapp",
"recipientId": "14155551234",
"messageId": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI3MEM3Q0NDQjlGRTUzMjJBNEUA",
"message": "Here's the shipping label you requested."
},
{
"messagingProduct": "whatsapp",
"recipientId": "14155551234",
"messageId": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBJDODc3NEJBRTdGN0NGNkVCMEMA",
"type": "document",
"mediaUrl": "https://example.com/label.pdf"
}
]
}
```
# Set Auto Response
Source: https://www.ayrshare.com/docs/apis/messages/set-auto-response
POST /messages/autoresponse
Send an auto response to Facebook, Instagram, and X/Twitter messages, with a custom message and wait time, when your support desk is unavailable.
Automatically send message auto responses to the correspondent. This is useful if your customer service support desk is not currently available.
If active, the auto response is used for Facebook, Instagram, and X/Twitter messages for a given User Profile. On X/Twitter, it is sent only when new messages are retrieved with your X API keys, through [Get Messages](/docs/apis/messages/get-messages) or the Messages page of the Ayrshare dashboard, and each retrieval sends at most one auto response, to the sender of the newest message. WhatsApp messages do not get auto responses.
> [!NOTE]
>
> An auto response is a message sent on your behalf, so it counts toward the User Profile's monthly [conversation limit](/docs/apis/messages/overview#conversation-limit-&-pricing) the same as a message you send yourself. When the limit is reached, auto responses to new recipients are not sent until the next billing cycle.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**autoResponseActive** `boolean` (default: false)
Whether the auto response is active.
**autoResponseWaitSeconds** `number` (default: 86400)
The number of seconds to wait before sending the auto response again to the correspondent. Default is 86,400 seconds (24 hours).
**autoResponseMessage** `string`
The auto response message.
Default: "Thank you for contacting us. A customer care agent will get back to you soon."
Send an empty "" string to reset the message to the default.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"autoResponseActive": true, "autoResponseWaitSeconds": 30, "autoResponseMessage": "Howdy!"' \
-X POST https://api.ayrshare.com/api/messages/autoresponse
```
```javascript JavaScript theme={"system"}
const url = 'https://api.ayrshare.com/api/messages/autoresponse';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
},
body: JSON.stringify({
'autoResponseActive': true,
'autoResponseWaitSeconds': 30,
'autoResponseMessage': 'Howdy!'
})
};
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```python Python theme={"system"}
import json
import requests
url = 'https://api.ayrshare.com/api/messages/autoresponse'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
}
data = {
'autoResponseActive': True,
'autoResponseWaitSeconds': 30,
'autoResponseMessage': 'Howdy!'
}
response = requests.post(url, headers=headers, data=json.dumps(data))
try:
response.raise_for_status()
data = response.json()
print(data)
except requests.exceptions.RequestException as e:
print('Error:', e)
```
```php PHP theme={"system"}
true,
'autoResponseWaitSeconds' => 30,
'autoResponseMessage' => 'Howdy!'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 400) {
echo 'Error: HTTP ' . $httpCode;
} else {
$data = json_decode($response, true);
print_r($data);
}
}
curl_close($ch);
```
```csharp C# theme={"system"}
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string url = "https://api.ayrshare.com/api/messages/autoresponse";
var headers = new Dictionary
{
{"Content-Type", "application/json"},
{"Authorization", "Bearer API_KEY"}
};
var data = new Dictionary
{
{"autoResponseActive", true},
{"autoResponseWaitSeconds", 30},
{"autoResponseMessage", "Howdy!"}
};
using (var client = new HttpClient())
{
var jsonData = JsonSerializer.Serialize(data);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
foreach (var header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
var response = await client.PostAsync(url, content);
try
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
var responseData = JsonSerializer.Deserialize>(responseBody);
Console.WriteLine(JsonSerializer.Serialize(responseData, new JsonSerializerOptions { WriteIndented = true }));
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String url = "https://api.ayrshare.com/api/messages/autoresponse";
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Authorization", "Bearer API_KEY");
Map data = new HashMap<>();
data.put("autoResponseActive", true);
data.put("autoResponseWaitSeconds", 30);
data.put("autoResponseMessage", "Howdy!");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers(headers.entrySet().stream()
.map(entry -> entry.getKey() + ": " + entry.getValue())
.toArray(String[]::new))
.POST(HttpRequest.BodyPublishers.ofString(getJsonString(data)))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
System.out.println("Error: HTTP " + response.statusCode());
} else {
System.out.println(response.body());
}
} catch (IOException | InterruptedException e) {
System.out.println("Error: " + e.getMessage());
}
}
private static String getJsonString(Map data) {
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{");
for (Map.Entry entry : data.entrySet()) {
jsonBuilder.append("\"").append(entry.getKey()).append("\":");
if (entry.getValue() instanceof String) {
jsonBuilder.append("\"").append(entry.getValue()).append("\"");
} else {
jsonBuilder.append(entry.getValue());
}
jsonBuilder.append(",");
}
if (jsonBuilder.length() > 1) {
jsonBuilder.setLength(jsonBuilder.length() - 1);
}
jsonBuilder.append("}");
return jsonBuilder.toString();
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"updated": {
"autoResponseActive": true,
"autoResponseMessage": "Howdy!",
"autoResponseWaitSeconds": 30
}
}
```
```json 400: Error theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Update Messages
Source: https://www.ayrshare.com/docs/apis/messages/update-messages
PUT /messages/:platform/:conversationId
Update the status of a Facebook, Instagram, X/Twitter, or WhatsApp conversation to active or archived when it is no longer active or relevant to your team.
Update the status of a conversation to active or archived. You may want to archive conversations if they are no longer active or relevant.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## **Path Parameters**
**platform** `string` *required*
The platform for status update: `facebook`, `instagram`, `twitter`, `whatsapp`
**conversationId** `string` *required*
The ID of the conversation to action. For WhatsApp this is the correspondent's phone number
(digits only, E.164 without the leading `+`).
## Body Parameters
**status** `string` *required*
Values: `active` or `archived`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"status": "archived"}' \
-X PUT https://api.ayrshare.com/api/messages/instagram/aWdfZMTpIyzQw
```
```javascript JavaScript theme={"system"}
const url = 'https://api.ayrshare.com/api/messages/instagram/aWdfZMTpIyzQw';
const options = {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
},
body: JSON.stringify({
'status': 'archived'
})
};
fetch(url, options)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```python Python theme={"system"}
import json
import requests
url = 'https://api.ayrshare.com/api/messages/instagram/aWdfZMTpIyzQw'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
}
data = {
'status': 'archived'
}
response = requests.put(url, headers=headers, data=json.dumps(data))
try:
response.raise_for_status()
data = response.json()
print(data)
except requests.exceptions.RequestException as e:
print('Error:', e)
```
```php PHP theme={"system"}
'archived'
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >=
```
```csharp C# theme={"system"}
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
string url = "https://api.ayrshare.com/api/messages/instagram/aWdfZMTpIyzQw";
var headers = new Dictionary
{
{"Content-Type", "application/json"},
{"Authorization", "Bearer API_KEY"}
};
var data = new Dictionary
{
{"status", "archived"}
};
using (var client = new HttpClient())
{
var jsonData = JsonSerializer.Serialize(data);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
foreach (var header in headers)
{
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
var response = await client.PutAsync(url, content);
try
{
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
var responseData = JsonSerializer.Deserialize>(responseBody);
Console.WriteLine(JsonSerializer.Serialize(responseData, new JsonSerializerOptions { WriteIndented = true }));
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```java Java theme={"system"}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
String url = "https://api.ayrshare.com/api/messages/instagram/aWdfZMTpIyzQw";
Map headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.put("Authorization", "Bearer API_KEY");
Map data = new HashMap<>();
data.put("status", "archived");
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.headers(headers.entrySet().stream()
.map(entry -> entry.getKey() + ": " + entry.getValue())
.toArray(String[]::new))
.PUT(HttpRequest.BodyPublishers.ofString(getJsonString(data)))
.build();
try {
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 400) {
System.out.println("Error: HTTP " + response.statusCode());
} else {
System.out.println(response.body());
}
} catch (IOException | InterruptedException e) {
System.out.println("Error: " + e.getMessage());
}
}
private static String getJsonString(Map data) {
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{");
for (Map.Entry entry : data.entrySet()) {
jsonBuilder.append("\"").append(entry.getKey()).append("\":\"").append(entry.getValue()).append("\",");
}
if (jsonBuilder.length() > 1) {
jsonBuilder.setLength(jsonBuilder.length() - 1);
}
jsonBuilder.append("}");
return jsonBuilder.toString();
}
}
```
```json 200: Success theme={"system"}
{
"status": "success"
}
```
```json 400: Missing Status theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Bulk Post
Source: https://www.ayrshare.com/docs/apis/post/bulk-post
POST /post/bulk
Schedule many posts at once by uploading a CSV file as multipart/form-data. Covers the required CSV fields, duplicate post rules, and a CSV template.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Bulk schedule posts with CSV (Comma Separated Values) file of posts data.
Content-Type must be `multipart/form-data`.
> [!WARNING]
>
> We recommend using the direct [Post endpoint](/docs/apis/post/post) instead of this bulk method for
> scheduling posts. The direct endpoint provides a more comprehensive feature set and easier
> debugging capabilities.
## Header Parameters
**Authorization** `string` *required*
Format: `Authorization: Bearer API_KEY`. See [API overview](/docs/apis/overview#authorization) for more
information.
**Profile-Key** `string`
Profile Key of a user profile.
**Content-Type** `string` *required*
`Content-Type: multipart/form-data`
## Body Parameters
**file** `object`
Multipart form-data CSV file of scheduled posts. See below for [CSV template](#csv-template).
## Request Examples
A multipart form-data containing a CSV file of posts will schedule them for a future date.
The CSV file contains the following fields (template below) and are required:
`post`: The post text.
`platforms`: Comma separated list of platforms, e.g. "twitter, facebook, instagram".
`mediaUrls`: URL of media, such as an image or video to include in the post.
`scheduleDate`: Datetime to schedule the post in UTC format. For example, use format
`YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`. Please see
[utctime](https://www.utctime.net/) for more examples.
> [!WARNING]
>
> Don't schedule duplicate posts within 24 hours of each other.
>
> Each post is checked against a 48-hour window centered on its own `scheduleDate` — 24 hours before through 24 hours after. If another post with identical or near-identical text falls inside that window, the post is rejected with [error 137](/docs/errors/errors-ayrshare#duplicate-content-errors). The window looks forward as well as back, so a row can be rejected because of a row scheduled after it. The check runs when a post is submitted or scheduled, not only when it is about to publish.
> This is to protect your account at the networks; they can suspend or shadow-ban accounts with frequent duplicate posts.
## CSV Template
Download the template and save as a .csv file.
[Ayrshare CSV Template](https://img.ayrshare.com/012/Ayrshare_CSV_Template.csv)
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: multipart/form-data' \
-F 'file=@"./Ayrshare CSV Template.csv"' \
-X POST https://api.ayrshare.com/api/post/bulk
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const FormData = require("form-data");
const fs = require("fs");
const formData = new FormData();
formData.append("file", fs.createReadStream("./Ayrshare CSV Template.csv"));
fetch("https://api.ayrshare.com/api/post/bulk", {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
...formData.getHeaders()
},
body: formData
})
.then((res) => res.json())
.then((data) => {
console.log(JSON.stringify(data));
})
.catch((error) => {
console.log(error);
});
```
```python Python theme={"system"}
import requests
API_KEY = "API_KEY"
# Open the CSV file in binary read mode
with open('./Ayrshare CSV Template.csv', 'rb') as file:
# Prepare the files dictionary for the multipart/form-data request
files = {'file': file}
# Set up the authorization header
headers = {'Authorization': f'Bearer {API_KEY}'}
try:
# Make the POST request to the API
response = requests.post(
'https://api.ayrshare.com/api/post/bulk',
headers=headers,
files=files
)
# Parse and print the JSON response
data = response.json()
print(data)
except Exception as e:
print(f"Error: {e}")
```
```javascript 200: OK Example with two scheduled posts. theme={"system"}
{
"status": "success",
"posts": [
{
"status": "scheduled",
"scheduleDate": "4/6/21 12:50",
"id": "X3uTExuEJhyM3u8wCRsA",
"post": "A great post"
},
{
"status": "scheduled",
"scheduleDate": "4/6/21 13:00",
"id": "8RGrekuxMnVa7lVnARFm",
"post": "An even better post"
}
]
}
```
# Copy a Post
Source: https://www.ayrshare.com/docs/apis/post/copy-post
POST /post/copy
Copy an existing post to new social media platforms with the same content, choose target platforms, set a schedule date, and override platform options.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Copy an existing post to new social media platforms with the same content and settings. This endpoint allows you to reuse successful posts across different platforms or with different configurations.
Please be sure to add your `API_KEY`, and the `PROFILE_KEY` if copying to a User Profile, in the [Authorization header](/docs/apis/overview#authorization).
The API Key can be found in the Ayrshare Developer Dashboard under the API Key page.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**postId** `string` *required*
The ID of the existing post to copy. This is the Ayrshare Post ID returned from the original
`/post` endpoint.
**platforms** `array` *required*
Social media platforms to post the copied content to. Accepts an array of Strings with values: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, or `youtube`.
Please note: use `facebook` for Facebook Pages, and `gmb` for Google Business Profile.
The platforms specified here will override the original post's platforms - you must explicitly choose which platforms to copy to.
**scheduleDate** `string`
The datetime to schedule the copied post. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
If not provided, the post will be published immediately.
**blueSkyOptions** `object`
Override the original post's Bluesky settings. See [Bluesky
details](/docs/apis/post/social-networks/bluesky).
**faceBookOptions** `object`
Override the original post's Facebook settings. See [Facebook
details](/docs/apis/post/social-networks/facebook).
**gmbOptions** `object`
Override the original post's Google Business Profile settings. See [Google Business Profile
details](/docs/apis/post/social-networks/google).
**instagramOptions** `object`
Override the original post's Instagram settings. See [Instagram
details](/docs/apis/post/social-networks/instagram).
**linkedInOptions** `object`
Override the original post's LinkedIn settings. See [LinkedIn
details](/docs/apis/post/social-networks/linkedin).
**pinterestOptions** `object`
Override the original post's Pinterest settings. See [Pinterest
details](/docs/apis/post/social-networks/pinterest).
**redditOptions** `object`
Override the original post's Reddit settings. See [Reddit
details](/docs/apis/post/social-networks/reddit).
**snapChatOptions** `object`
Override the original post's Snapchat settings. See [Snapchat
details](/docs/apis/post/social-networks/snapchat).
**telegramOptions** `object`
Override the original post's Telegram settings. See [Telegram
details](/docs/apis/post/social-networks/telegram).
**threadsOptions** `object`
Override the original post's Threads settings. See [Threads
details](/docs/apis/post/social-networks/threads).
**tikTokOptions** `object`
Override the original post's TikTok settings. See [TikTok
details](/docs/apis/post/social-networks/tiktok).
**twitterOptions** `object`
Override the original post's X/Twitter settings. See [X/Twitter
details](/docs/apis/post/social-networks/x-twitter).
**youTubeOptions** `object`
Override the original post's YouTube settings. See [YouTube
details](/docs/apis/post/social-networks/youtube).
**shortenLinks** `boolean`
Override the original post's link shortening setting.
[Max Pack required](/docs/additional/maxpack) for link shortening.
**autoHashtag** `object`
Override the original post's auto hashtag settings. See [auto
hashtags](/docs/apis/post/overview#auto-hashtags) for details.
**disableComments** `boolean`
Override the original post's comment settings. Only available for Instagram, LinkedIn, and TikTok.
**firstComment** `object`
Override the original post's first comment settings. See [first
comment](/docs/apis/post/overview#first-comment) for details.
**notes** `string`
Add new notes to the copied post. These will replace any notes from the original post. Notes are
for reference only and do not affect the post content.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"postId": "RhrbDtYh7hdSMc67zC8H",
"platforms": ["twitter", "facebook", "linkedin"],
"scheduleDate": "2026-07-08T12:30:00Z"
}' \
-X POST https://api.ayrshare.com/api/post/copy
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post/copy", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
postId: "RhrbDtYh7hdSMc67zC8H", // required
platforms: ["twitter", "facebook", "linkedin"], // required
scheduleDate: "2026-07-08T12:30:00Z", // optional
faceBookOptions: {
link: "https://example.com/new-link"
}
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {
'postId': 'RhrbDtYh7hdSMc67zC8H',
'platforms': ['twitter', 'facebook', 'linkedin'],
'scheduleDate': '2026-07-08T12:30:00Z',
'faceBookOptions': {
'link': 'https://example.com/new-link'
}
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
}
r = requests.post('https://api.ayrshare.com/api/post/copy',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"RhrbDtYh7hdSMc67zC8H",
"platforms" => ["twitter", "facebook", "linkedin"],
"scheduleDate" => "2026-07-08T12:30:00Z",
"faceBookOptions" => [
"link" => "https://example.com/new-link"
]
];
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.ayrshare.com/api/post/copy',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer API_KEY',
'Content-Type: application/json'
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"postId": "RhrbDtYh7hdSMc67zC8H",
"platforms": []string{"twitter", "facebook", "linkedin"},
"scheduleDate": "2026-07-08T12:30:00Z",
"faceBookOptions": map[string]interface{}{
"link": "https://example.com/new-link",
},
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/post/copy",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostCopyRequest_csharp
{
class PostCopy
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post/copy";
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"postId\" : \"RhrbDtYh7hdSMc67zC8H\","
+ "\"platforms\" : [ \"twitter\", \"facebook\", \"linkedin\" ],"
+ "\"scheduleDate\" : \"2026-07-08T12:30:00Z\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```ruby Ruby theme={"system"}
require 'httparty'
res = HTTParty.post("https://api.ayrshare.com/api/post/copy",
headers: {Authorization: "Bearer API_KEY"},
body: {
postId: "RhrbDtYh7hdSMc67zC8H",
platforms: ['twitter', 'facebook', 'linkedin'],
scheduleDate: '2026-07-08T12:30:00Z',
faceBookOptions: {
link: 'https://example.com/new-link'
}
}).body
puts res
```
```json 200: Success - Immediate Post theme={"system"}
{
"status": "success",
"copiedFrom": "RhrbDtYh7hdSMc67zC8H",
"originalPost": "Today is a great day!",
"errors": [],
"postIds": [
{
"status": "success",
"id": "1288899996423983106",
"platform": "twitter",
"postUrl": "https://x.com/handle/status/1288899996423983106"
},
{
"status": "success",
"id": "104923907983682_108329000309743",
"platform": "facebook",
"postUrl": "https://www.facebook.com/104923907983682_108329000309743"
},
{
"status": "success",
"id": "urn:li:share:7282181682126807042",
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:7282181682126807042",
"owner": "urn:li:organization:77682157",
"platform": "linkedin"
}
],
"id": "KpwxFmNj3kgQLa92vE5T"
}
```
```json 200: Success - Scheduled Post theme={"system"}
{
"status": "scheduled",
"scheduleDate": "2025-05-28T14:32:00Z",
"id": "KpwxFmNj3kgQLa92vE5T",
"refId": "7a8e2f15c94d73a6e2bf89dd4c847b9f2e3a6d18",
"copiedFrom": "RhrbDtYh7hdSMc67zC8H",
"originalPost": "Today is a great day!"
}
```
```json 400: Bad Request theme={"system"}
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 156,
"message": "Threads is not linked. Please confirm the linkage on the Social Accounts page in the dashboard. https://www.ayrshare.com/docs/dashboard/connect-social-accounts/overview",
"platform": "threads"
}
],
"postIds": [],
"id": "KpwxFmNj3kgQLa92vE5T",
"refId": "7a8e2f15c94d73a6e2bf89dd4c847b9f2e3a6d18"
}
```
```json 400: Bad Request - Missing Platforms theme={"system"}
{
"status": "error",
"code": 163,
"message": "Missing, empty, or not valid platforms parameter. Please verify sending an array of valid platforms. https://www.ayrshare.com/docs/apis/post",
"details": "platforms parameter is required for copying posts"
}
```
```json 400: Bad Request - Duplicate or Similar Content theme={"system"}
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 137,
"message": "Duplicate or similar content posted within the same two day period. The social networks prohibit duplicate content and ban accounts that do not comply. https://www.ayrshare.com/docs/help-center/technical-support/dealing-with-duplicate-posts#dealing-with-duplicate-posts",
"details": "Duplicate found in user profile: \"Primary Profile\", post id: e5ELaDhKjhJBU1z6dbud, and refId: 5da3de47a8e3b1d8c2bd319350aa7d4e85d4ec74",
"verifyCheck": true,
"platform": "twitter"
}
],
"postIds": [],
"id": "KpwxFmNj3kgQLa92vE5T",
"refId": "7a8e2f15c94d73a6e2bf89dd4c847b9f2e3a6d18"
}
```
# Delete a Post
Source: https://www.ayrshare.com/docs/apis/post/delete-post
DELETE /post
Delete published or scheduled posts by Ayrshare Post ID, bulk delete several posts, or delete all pending scheduled posts for a User Profile at once.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Delete a post using the post ID returned from the [/post](/docs/apis/post/post) endpoint.
You can delete both published and scheduled posts, but there are some differences based on the social network `platform`:
Scheduled posts can be deleted for all social networks `platforms`.
Published posts can be deleted for all social networks `platforms` except
for Instagram and TikTok. Instagram and TikTok do not provide API support
for deleting published posts. If you need to delete a published post on
these platforms, you must do so manually using their respective mobile apps.
Facebook does not permit deleting image stories via their API (video stories
can be deleted). If you need to delete an image story, please go to the
Facebook mobile app and perform the deletion manually.
> [!NOTE]
>
> Instagram and TikTok do not support delete via their APIs. Please go to the
> Instagram or TikTok mobile apps to delete the posts.
>
> Facebook does not support image story deletion via its API. Please go to the
> Facebook mobile app to delete stories.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
Ayrshare Post ID of the post to delete.
Not required if `bulk` or `deleteAllScheduled` parameter sent.
If the post is scheduled and still `pending`, the scheduled posts will be deleted and not sent to the networks.
If the post has already been sent to the networks, the post will be deleted from the networks.
**bulk** `array`
Array of Strings post Ids to bulk delete.
Required if `id` or `deleteAllScheduled` parameter not sent.
**deleteAllScheduled** `boolean` (default: false)
If `true` will delete all scheduled posts still in a pending state, i.e. posts that have not yet been published to the networks, for the User Profile.
The `id` parameter is ignored if set to `true`. If `bulk` is also sent, `bulk` is used and `deleteAllScheduled` is ignored.
> [!TIP]
>
> This will delete all scheduled posts, so be sure to use this with caution.
**markManualDeleted** `boolean` (default: false)
If `true`, the post will mark as deleted in Ayrshare and Ayrshare will not try to delete the post at the networks.
A common use case for this is when the post was manually deleted at the networks and you want to mark it as deleted in Ayrshare.
Prior to marking the post as deleted, please ensure that the post was already manually deleted on the social networks.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API Key" \
-H 'Content-Type: application/json' \
-d '{"id": "Post ID"}' \
-X DELETE https://api.ayrshare.com/api/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const id = "Post ID";
fetch("https://api.ayrshare.com/api/post", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({ id }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': 'Post ID'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$postId]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace DeletePOSTRequest_csharp
{
class Delete
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post";
// Set up request headers
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
// Prepare JSON content
string json = "{\"id\" : \"Post ID\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// Create and send DELETE request with content
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri(url),
Content = content
};
HttpResponseMessage response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
// Read response
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```javascript 200: Success theme={"system"}
{
"twitter": {
"action": "delete",
"status": "success",
"id": "1288900099663775749" // Twitter Social Post ID
},
"facebook": {
"action": "delete",
"status": "success",
"id": "104920007983682_108683297607743" // Facebook Social PostID
},
"status": "success"
}
```
```json 200: Success with markManualDeleted theme={"system"}
{
"status": "success",
"id": "p8Ee6xDPx8PRxsjtDXf3" // Ayrshare Post ID
}
```
```json 400: ID Not Found theme={"system"}
{
"action": "delete",
"status": "error",
"code": 114,
"message": "Delete id not found.",
"id": "IdpUOyihLEpIx0d0N9mI" // Ayrshare Post ID used for delete, comment, analytics, etc.
}
```
```json 400: Post Already Deleted theme={"system"}
{
"action": "delete",
"status": "error",
"code": 383,
"message": "The post is already deleted.",
"id": "p8Ee6xDPx8PRxsjtDXf3" // Ayrshare Post ID
}
```
```json 400: Mark Manual Deleted Error theme={"system"}
{
"action": "delete",
"status": "error",
"code": 382,
"message": "The post was not manually deleted at the social network. By using the markManualDeleted option, you are responsible for deleting the post at the social network. Please delete the post and try again.",
"id": "DD3fA2DC4qe2T48hSdgs",
"markManualDeleted": true
}
```
# Get a Post
Source: https://www.ayrshare.com/docs/apis/post/get-post
GET /post/:id
Get the history of a post sent via Ayrshare by its Post ID, including its status, such as pending, success, or error, post parameters, and other details.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Get the history for a specific posts sent via Ayrshare. Returns the status, post parameters, and other details. Replace `:id` with the Ayrshare Post ID.
> [!NOTE]
>
> Call the [/history by id](/docs/apis/history/get-history-id) endpoint for the same data.
## Post Statuses
The following statuses are returned for a post.
| Status | Description |
| :- | :- |
| `awaiting approval` | Posts are waiting to be approved via the [approval workflow](/docs/apis/post/overview#approval-workflow). |
| `deleted` | Post has been deleted. Note: deleted posts are only returned with the status query filter. Please see below. |
| `error` | An error occurred with one or more social networks. |
| `pending` | The post has not yet been processed. Typically a scheduled post. |
| `success` | The post was successfully sent to all social networks. |
> [!NOTE]
>
> See the [/history](/docs/apis/history/overview) endpoint for retrieving all posts, including
> posts not sent via Ayrshare.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
Ayrshare Post ID from /post
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/post/TBEAAqAMMJoweA9wKHUl
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post/TBEAAqAMMJoweA9wKHUl", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/post/TBEAAqAMMJoweA9wKHUl', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
[!NOTE]
>
> WhatsApp is messaging-only on Ayrshare, so you cannot publish posts to it. Use the
> [Messaging API](/docs/apis/messages/overview) for WhatsApp conversations.
Start publishing posts with the [/post POST endpoint](/docs/apis/post/post).
## Approval Workflow
If your publishing workflow requires approval before sending a post, set the `requiresApproval` field to `true`.
This is equivalent to pausing the post until the `approved` parameter is set to `true`.
### Approval Workflow Example
The post will have a status of "awaiting approval" until the `approved` parameter is set to `true` via the [/post PATCH endpoint](/docs/apis/post/update-post).
**1. Publish with parameters**
Publish your post using the [/post endpoint](/docs/apis/post/post) with the `requiresApproval` field
as `true`. You may also include standard parameters such as `scheduleDate`.
**2. Status is awaiting approval**
The post will be in a status of "awaiting approval" and will be held until approval is granted.
**3. Approve the post**
Update the post with the [/post PATCH operation](/docs/apis/post/update-post) setting the `approved`
field as `true`. The post will now be sent at the scheduled time.
**4. Use notes**
Optional: Set `notes` on the post for reference, such as who needs to approve the post.
```json Publish the Post with Approval theme={"system"}
{
"requiresApproval": true,
"notes": "need approval by John Smith" // optional
}
```
> [!WARNING]
>
> If the `scheduleDate` is included and the date is in the past, the post will be published
> immediately upon approval.
### Approval Workflow Video
Please see the video below for an example of the approval workflow.
[Social Approval Workflow API](https://www.youtube.com/embed/DtmTGgqQ-Mo)
## Auto Hashtags
Add the most relevant hashtags to your post.
`autoHashtag` is an object, or Boolean - see below, with the following parameters:
`max`: (optional) Integer of hashtags to add, range 1-10. Default 2.
`position`: (optional) String "auto" or "end". Auto adds the hashtags within the post or to the
end. "end" adds hashtags just to the end.
*A paid plan is required.*
```json theme={"system"}
{
"hashtags": {
"max": 3, // optional: Integer range 1-10
"position": "auto" // optional: String "auto" or "end"
}
}
```
If you do not want to send any of the above options, pass the Boolean value `true` instead of an object.
```json theme={"system"}
{
"autoHashtag": true
}
```
## Auto Repost
Automatically reposts your content multiple times at regular intervals, creating evergreen content that stays fresh and visible to your audience.
A paid plan is required.
Parameters:
* `repeat`: (required) The number of times to repost the content. Must be between 1 and 10.
* `days`: (required) The number of days between each repost. Must be at least 2 days.
* `startDate`: (optional) When to start the repost schedule, in ISO-8601 UTC format. If not specified, the first post will be published immediately. You should use the `startDate` parameter in lieu of the top-level `scheduleDate` parameter.
```json Auto Repost theme={"system"}
{
"repeat": 2, // min 2, max 10
"days": 5, // min 2
"startDate": "2021-07-08T12:30:00Z"
}
```
The response will include all the future scheduled reposts and an `autoRepostId` for each repost.
```json Auto Repost Response theme={"system"}
{
"status": "scheduled",
"scheduleDate": "2025-06-13T12:30:00.000Z",
"id": "eIT96IYEodNuzU4oMmwG",
"refId": "9abf1426d6ce9122ef11c72bd",
"autoRepostId": "F5wdoaOAAGtDQVciExSxL",
"post": "The most important things are the hardest to say - Stephen King"
}
```
When creating an auto repost an ID `autoRepostId` is assigned to track that series of posts.
You may get all of the auto reposts for a post with the [History call](/docs/apis/history/get-history) with the `autoRepostId`.
If you need to delete a repost, you can use the [DELETE call](/docs/apis/post/delete-post) with the post ID.
> [!TIP]
>
> Important: When using auto-repost, ensure you follow each social network's posting frequency guidelines to avoid account restrictions.
>
> Note: The `autoRepost` feature cannot be used together with `scheduleDate`. If you include both parameters, `autoRepost` is used and `scheduleDate` is ignored. To choose when the reposts start, use the `startDate` parameter in `autoRepost`.
## First Comment
Automatically add in a first comment, with media, after the post is published. For TikTok the comment is deferred until the video finishes processing (see [First Comment Processing Time](#first-comment-processing-time)).
Posting the first comment on your own social media post can help kickstart engagement and set the tone for the discussion that follows.
```json theme={"system"}
{
"firstComment": {
"comment": "My first comment", // required
"mediaUrls": ["https://..."] // Facebook, LinkedIn, and X/Twitter only
}
}
```
## First Comment Processing Time
For most social networks, the API response is delayed because our system must (1) wait for the original post to be fully published, then (2) add the comment to that published post. This sequential process adds an approximately 20 second delay.
**TikTok is handled differently.** TikTok processes videos asynchronously, so the post `id` is `"pending"` until TikTok's `post.publish.publicly_available` webhook resolves the real video id (see [TikTok Processing](/docs/apis/post/social-networks/tiktok#tiktok-processing)). The `/post` response therefore returns the TikTok first comment with `status: "pending"` right away, and the comment is posted automatically once TikTok finishes processing and the [`tikTokPublished` Scheduled Action webhook](/docs/apis/webhooks/actions#scheduled-action) fires. TikTok does not guarantee a processing time, so there is no fixed delay.
**Important TikTok Note**: For first comments to work properly on TikTok, the post's `visibility` parameter must be set to `public`. A non-public video never receives the `publicly_available` webhook, so its first comment cannot be posted; in that case Ayrshare returns a comment error rather than leaving it pending.
## Idempotent Posts
[Idempotency ](https://en.wikipedia.org/wiki/Idempotence) is an optional feature that ensures a request is only executed once, even if it is accidentally sent multiple times.
When posting content using the API, you can include an optional `idempotencyKey` parameter in the request body to uniquely identify the operation. This allows you to safely retry the post request without the risk of creating duplicate posts.
To use idempotency, add the `idempotencyKey` parameter to the JSON body of the `/post` POST request:
```json Idempotency Key theme={"system"}
{
"idempotencyKey": "Unique Key"
}
```
The value of `idempotencyKey` should be a unique string per User Profile. If a request is made with the same `idempotencyKey` for a given User Profile, regardless of the post's state (success, error, pending, or deleted), an error will be returned, indicating that a duplicate key was found.
> [!WARNING]
>
> The API must first accept and process the POST request to store the idempotency key and check for
> duplicates. However, if multiple POST requests with the same `idempotencyKey` are sent
> simultaneously or scheduled for the same posting time, the API may not detect the duplicate keys.
> This is because the API processes these concurrent or simultaneously scheduled requests in
> parallel, before it has a chance to register the idempotency key from any single request. As a
> result, there's no guarantee that duplicate idempotent keys will be caught in these scenarios of
> simultaneous submission or execution of scheduled posts.
Using idempotency helps prevent the accidental creation of duplicate posts when retrying failed requests or handling network issues. However, it's still recommended to implement appropriate error handling and retry mechanisms in your application to handle potential failures gracefully.
## Image & Video Requirements
Posting images and videos have different requirements for each network, but don't worry. Our system verifies your post before sending, so you'll get an error response if something is wrong. See the link below for details on image and video guidelines.
> [card] [Image & Video Guidelines](/docs/media-guidelines/overview)
### Valid URL
Be sure your media URL(s) are valid and directly access the media.
A first test is trying the URL in a browser.
If the image will not load or cannot be downloaded in a browser it will likely fail.
For example, a DropBox URL that opens the DropBox web app will not work.
> [!TIP]
>
> If you have a [Google Drive Share
> URL](https://www.ayrshare.com/how-to-get-direct-download-urls-from-google-drive/) or a [Dropbox
> Share URL](https://www.ayrshare.com/blog/how-to-get-direct-download-urls-from-dropbox/), you can just
> use the URL in the `mediaUrls` parameter when publishing a post or comment. Ayrshare will
> automatically convert the share URL to a download link.
We verify the media URL by making a `HEAD` request.
Please be sure the hosting provider is not blocking the `HEAD` request or the post will fail with a 403 error.
For example, here is a `HEAD` request to the media URL:
```javascript Fetch HEAD Request theme={"system"}
const run = async () => {
const url = "https://img.ayrshare.com/012/gb.jpg";
return await fetch(url, {
method: "HEAD"
})
.then((res) => {
if (res.ok) {
return console.log("Success:", res.status, res.statusText);
} else {
return console.error("Error:", res.status, res.statusText, res);
}
})
.catch((error) => {
return console.error("Error Catch:", error);
});
};
run();
```
#### Automated Media Protection
Ayrshare includes built-in media protection that can detect and resolve certain media delivery issues during posting. When a post succeeds but a content issue was detected and resolved, each affected entry in `postIds[]` includes an optional `contentIssues` object so you can identify and fix the underlying issue:
```json theme={"system"}
{
"postIds": [
{
"status": "success",
"platform": "instagram",
"id": "17878176260289172",
"postUrl": "https://www.instagram.com/p/CP1dI9Hp_WO/",
"contentIssues": {
"originMediaHostFailed": true,
"details": ["Media URL could not be retrieved by the social network. Successfully posted using Ayrshare automated media protection."]
}
}
]
}
```
If you see `originMediaHostFailed` in your responses, review your media hosting configuration. See [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked) for common causes and solutions.
#### Spaces & Special Characters
We recommend to avoid the following in your media URLs:
Spaces in the URL.
URL encoded spaces in the URL.
Special characters in the URL even if they are url encoded, such as accent marks é.
For example:
```bash theme={"system"}
https://img.ayrshare.com/012/test .webp
```
This URL fails because of the space `test .webp`. We also don't recommend using URL encoded spaces, such as `%20` in the URL - this can also cause issues with some social networks.
And this Unsplash image:
```bash theme={"system"}
https://unsplash.com/photos/a-house-in-the-middle-of-a-field-with-trees-in-the-background-znbmh2-cIj0
```
This Unsplash image fails because it isn't directly accessing the image, but showing a web app.
#### Sanitize File Names & URLs
You can sanitize your file names with a regular expression such as `/[^a-z0-9\/\.]/gi`.
```javascript theme={"system"}
const sanitizeFileName = (url) => url.replace(/[^a-z0-9\/\.]/gi, "_");
sanitizeFileName("tést .webp"); // t_st_.webp
```
or sanitize a URL
```javascript theme={"system"}
const sanitizeUrl = (url) => {
const [protocol, rest] = url.split("://");
const [domain, ...path] = rest.split("/");
const sanitizedPath = path.join("/").replace(/[^a-z0-9\/\.]/gi, "_");
return `${protocol}://${domain}/${sanitizedPath}`;
};
// Output: https://img.ayrshare.com/012/t_st_.webp
sanitizeUrl("https://img.ayrshare.com/012/tést .webp");
```
### Additional Information
> [!NOTE]
>
>
>
> If you're self hosting, make sure the URL can be externally accessed and doesn't require
> special permissions.
>
>
>
> See below for how to handle videos with unknown extensions, often signed URLs such as AWS S3.
>
>
>
> If you are using a signed URL, such as S3, we recommend to set the URL expiration to at least
> 7 days. This allows our team to assist with any questions you have on publishing the post.
>
>
>
> Test if the media URL exists with our [verify media tools](/docs/apis/media/verify-media-url).
>
>
### Download Speed
Be sure your media hosting has a fast connection, especially download speed. You can test your media hosting performance at [pingdom](https://tools.pingdom.com/). We recommend at least a *B rating*.
### Video Extension
If your URL does not end in a known video extension such as `mp4`, you can use the `isVideo: true` field in the post to specify the `mediaUrl` is a video. Ayrshare will try to determine the file type, such as `MOV`. However, we recommend explicitly ending your video file with a known extension, such as `mp4`, since this has a higher success rate with the social networks.
### Image or Video Only
A few social networks support sending media without post text. If you do not want post text included, send an empty string: `post: ""`
The following social networks support no post/blank text: Facebook, Instagram, LinkedIn, Threads, TikTok, and X/Twitter.
### Testing Images and Videos
Consider using the [generate random text and random image or video](/docs/quickstart#publish-test-posts) to speed up your testing. Stop trying to think up something different for each of your test posts!
## Line Breaks
> [!NOTE]
>
> If you want to add line breaks (new lines) in a post, use the invisible line break `\u2063\n.`For example, `This is a new\u2063\nline.`
>
> We also recommend trying in Postman to see how the new line break is translated in your language of choice. For example, PHP often only uses a `\n`
>
> Some social networks do not currently support line breaks in the post text.
## Multi-Platform Posts and Media
This feature allows you to customize your post content and media for different social networks within a single API call. You can specify unique text and/or images for each platform by using objects for the `post` and `mediaUrls` fields.
1. Use an object structure for `post` and/or `mediaUrls` fields.
2. Specify platform-specific content using platform names as keys.
3. Include a `default` key for content to be used on platforms not explicitly specified.
```json theme={"system"}
{
"post": {
"instagram": "Great IG pic!",
"facebook": "Great FB pic!",
"default": "Great default pic!"
},
"platforms": ["instagram", "facebook", "linkedin"],
"mediaUrls": {
"instagram": "https://img.ayrshare.com/012/gb.jpg",
"linkedin": "https://img.ayrshare.com/012/gb.jpg",
"default": "https://img.ayrshare.com/012/gb.jpg"
}
}
```
In the example above:
Instagram will use its specific text and image URL.
Facebook will use its specific text and the default image URL.
LinkedIn will use the default text and its specific image URL.
> [!NOTE]
>
> If you need to post multiple images to different platforms, create separate posts for each
> platform instead of using this multi-platform structure.
## Profile Keys
Post to on behalf of user by providing users' Profile Keys as a body parameter and the additional data in the response. *Business or Enterprise Plan required.*
> [card] [Profiles](/docs/apis/profiles/overview)
## Rich Text Posts
You can add rich text such as "𝓗𝓮𝓵𝓵𝓸, how about a little 𝗯𝗼𝗹𝗱 𝘁𝗲𝘅𝘁 and 𝘪𝘵𝘢𝘭𝘪𝘤𝘴 𝘵𝘦𝘹𝘵 and an x₂?". You can use rich text on networks such as Twitter, Facebook, LinkedIn, Telegram, and Instagram.
If posting to Reddit, please use [Reddit-flavored Markdown formatting](https://www.reddit.com/wiki/markdown#wiki_new_reddit-flavored_markdown).
HTML elements are used to specify the type of rich text, which is translated into unicode. For example:
```json theme={"system"}
{
"post": "Hello, how about a little bold text and italics text and an x2?"
"platforms": ["twitter"]
}
```
### HTML Elements
| HTML | Example |
| - | - |
| \Nice One!\ | **Nice One!** |
| \Hello, world!\ | **Hello, world!** |
| \World\ | *World* |
| normal \italics \bold italics\\ | normal *italics **bold italics*** |
| \`\Hello\, world!\` | `Hello, world!` |
| \`\Hello\, world!\` | **`Hello`**`, world!` |
| \123\ | 𝟷𝟸𝟹 |
| \Hello\ | 𝓗𝓮𝓵𝓵𝓸 |
| x\2\ | x₂ |
| x\2\ | x² |
### CSS Codes
| Code | Example | Result |
| - | - | - |
| \u00B0 | It's 25\u00B0C today! | It's 25°C today! |
| \u2063\n | This is a new\u2063\nline. | This is a new line. |
## Schedule Posts
### Create Scheduled Posts
You can schedule future posts by specifying the `scheduleDate` parameter with the datetime in Zulu/UTC. Zulu Time, also known as Coordinated Universal Time (UTC), is the world standard for time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
```json {5} theme={"system"}
{
"post": "Hello, world!",
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["facebook", "instagram"],
"scheduleDate": "2023-07-08T12:30:00Z"
}
```
Please see [https://www.utctime.net/](https://www.utctime.net/) on how to convert your local time to Zulu/UTC time.
If the scheduled datetime is in the past, the post will immediately be sent.
> [!NOTE]
>
> If a `mediaUrl` is included with a scheduled post, the media must be available at the scheduled
> publication time. For example, if the post is scheduled to be published on March 5, 2026, the
> media must be available on March 5, 2026.
> [!TIP]
>
> Error Handling for Scheduled vs Immediate Posts
>
> There is an important difference in how validation errors are handled between immediate and scheduled posts:
>
> * Immediate Posts
> * When publishing immediately (without scheduleDate), if one platform fails validation checks, the other platforms will continue to be processed. For example, if a post exceeds Twitter's character limit but is valid for Facebook and Instagram, the post will fail on Twitter but still be published to Facebook and Instagram.
> * Scheduled Posts
> * When scheduling posts for future publication (with scheduleDate), all platforms must pass initial validation checks before the post is scheduled. If any platform fails these pre-validation checks, the entire scheduling operation will be rejected and an error will be returned immediately.
> * However, some platform-specific errors may not be detected until the actual posting time. In these cases, the scheduled post will attempt to publish to all platforms, and individual platform failures will be reported in the final results without affecting the other platforms.
### Check a Scheduled Post Status
You can check the status of a scheduled post several ways:
Set up the [Webhook Scheduled Action](/docs/apis/webhooks/actions#scheduled-action) to automatically
receive the status of the scheduled post. This is available for the Business plan and is the
recommended method.
Get the status of a scheduled post with the [GET call](/docs/apis/post/get-post) with the post ID.
Check the status in the Ayrshare Dashboard. First switch to the User Profile the post was
published under, then go to "Posts" page and search using the Ayrshare Post ID.
### Pause Scheduled Posts
You may pause scheduled posts that have not yet been published.
Pausing a scheduled post will prevent it from being published until the post has been unpaused.
Use the [PATCH call](/docs/apis/post/update-post) to pause or unpause the scheduled post.
Please note if a post is unpaused and the `scheduleDate` is in the past the post will immediately be published. Consider updating the `scheduleDate` before unpausing.
## Shorten Links
Links in a post can be shortened using the Ayrshare [link shortener](/docs/apis/links/overview). You can turn on the automatic link shortening with the `shortenLinks` parameter when sending a post. [Max Pack required](/docs/additional/maxpack).
```json {4} theme={"system"}
{
"post": "Hello, world with a link https://www.ayrshare.com",
"platforms": ["linkedin"],
"shortenLinks": true
}
```
## Unsplash Images
The following fields are available for the `unsplash` body parameter:
Random Image: `random` returns a random Unsplash image.
Search Based Image: value String search term ; e.g. `money` will select a random image based on
money.
Image IDs: value Array of Ids; e.g. \["HubtZZb2fCM"] of image
[https://unsplash.com/photos/HubtZZb2fCM](https://unsplash.com/photos/HubtZZb2fCM)
```json {4,5,6} theme={"system"}
{
"post": "Hello, world!",
"platforms": ["instagram"],
"unsplash": "random",
"unsplash": "search term", // unsplash: "money"
"unsplash": ["unsplash image ID"] // unsplash: ["HubtZZb2fCM"]
}
```
> [!NOTE]
>
> If copying an Unsplash URL to post in `mediaUrls`, please be sure to copy the image address and
> not just the URL. Please see this
> [example](/docs/help-center/technical-support/get-an-unsplash-image-url) for more information.
# Publish a Post API: Post to Any Social Network
Source: https://www.ayrshare.com/docs/apis/post/post
POST /post
Publish to Facebook, Instagram, X, LinkedIn, TikTok, YouTube, and more with one Ayrshare API call. See parameters, code examples, and supported media.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Publish posts to the social networks you or your users have linked.
If you want to publish posts to User Profiles, please see the [/profiles endpoint](/docs/apis/profiles/overview) for more details.
Please be sure to add your `API_KEY`, and the `PROFILE_KEY` if publishing to a User Profile, in the [Authorization header](/docs/apis/overview#authorization).
The API Key can be found in the Ayrshare Developer Dashboard under the API Key page.
See the [Post API Overview](/docs/apis/post/overview) for more details on posting options.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**post** `string` *required*
The post text sent to the social networks specified in the platforms parameter.
See here for [advanced options](/docs/apis/post/overview), including how to include URLs and rich text.
You may send an empty string `""` to publish with no text.
**platforms** `array` *required*
Social media platforms to post. Accepts an array of Strings with values: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, or `youtube`.
Please note: use `facebook` for Facebook Pages, and `gmb` for Google Business Profile.
Use `all` to post to all linked social networks. Also include the required fields for all social networks.
E.g. `title` must be included in `youTubeOptions` if `youtube` is linked.
**mediaUrls** `array`
An array of image or video URLs to include in the post. Please see [/media endpoint](/docs/apis/media/overview) to learn more.
URLs must be secure and begin with `https://`. If the URL has special characters, e.g. ñ, please encode the special characters before sending.
Videos require a paid plan.
Please see here for [Image and Video Requirements](/docs/apis/post/overview#image-&-video-requirements) and [other advanced options](/docs/apis/post/overview).
**isVideo** `boolean` (default: false)
Ayrshare will try to determine the media type based on the file extension in the URL (.mp4). You can explicitly set the media as a video if the URL does not end in a known video extension, such as animated GIFs.
Please see [video extension](/docs/apis/post/overview#video-extension) for details.
**scheduleDate** `string`
The datetime to schedule a future post. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
Also see [schedule posts](/docs/apis/post/overview#schedule-posts) for details.
**validateScheduled** `boolean` (default: true)
By default, scheduled posts are checked (pre-validated) for issues such as media requirements before they are accepted.
If any problems are found by Ayrshare checks, you will receive an immediate error response and the post will not be scheduled.
When the scheduled date is reached, the post will be published, at which time the final success or error response will be sent either via webhook or the [/history endpoint](/docs/apis/history/overview).
To skip this pre-validation step, set `validateScheduled` to `false`.
We recommend keeping validation enabled for scheduled posts to catch errors early.
Otherwise, the post will be scheduled and you will only receive an error when it is published.
Please see [scheduled webhook actions](/docs/apis/webhooks/actions#scheduled-action) for more details.
**firstComment** `object`
Add in a first comment automatically after posting. See [first comment](/docs/apis/post/overview#first-comment) for details.
**disableComments** `boolean` (default: false)
Disable comments for the post. Only available for Instagram, LinkedIn, and TikTok.
**shortenLinks** `boolean` (default: false)
Shorten links in the post for all platforms using [Ayrshare's link shortener](/docs/apis/links/overview).
Only URLs starting with https will be shortened.
[Max Pack required](/docs/additional/maxpack) for link shortening.
Please see here for using [3rd party link shorteners](/docs/apis/links/overview#custom-link-domain).
**autoSchedule** `object`
Please see [auto-schedule](/docs/apis/auto-schedule/overview) for details.
**autoRepost** `object`
Automatically reposts your content multiple times at regular intervals, creating evergreen content that stays fresh and visible to your audience.
See [auto repost](/docs/apis/post/overview#auto-repost) for details.
**autoHashtag** `object`
See [auto hashtags](/docs/apis/post/overview#auto-hashtags) for details.
**unsplash** `string`
See [unsplash](/docs/apis/post/overview#unsplash-images) for details.
**blueSkyOptions** `object`
See [Bluesky details](/docs/apis/post/social-networks/bluesky).
**faceBookOptions** `object`
See [Facebook details](/docs/apis/post/social-networks/facebook).
**gmbOptions** `object`
See [Google Business Profile details](/docs/apis/post/social-networks/google).
**instagramOptions** `object`
See [Instagram details](/docs/apis/post/social-networks/instagram).
**linkedInOptions** `object`
See [LinkedIn details](/docs/apis/post/social-networks/linkedin).
**pinterestOptions** `object`
See [Pinterest details](/docs/apis/post/social-networks/pinterest).
**redditOptions** `object`
See [Reddit details](/docs/apis/post/social-networks/reddit).
**snapChatOptions** `object`
See [Snapchat details](/docs/apis/post/social-networks/snapchat).
**telegramOptions** `object`
See [Telegram details](/docs/apis/post/social-networks/telegram).
**threadsOptions** `object`
See [Threads details](/docs/apis/post/social-networks/threads).
**tikTokOptions** `object`
See [TikTok details](/docs/apis/post/social-networks/tiktok).
**twitterOptions** `object`
See [X/Twitter details](/docs/apis/post/social-networks/x-twitter).
**youTubeOptions** `object`
See [YouTube details](/docs/apis/post/social-networks/youtube).
**requiresApproval** `boolean` (default: false)
See [approval workflow](/docs/apis/post/overview#approval-workflow) for details.
**randomPost** `boolean` (default: false)
[Generate random post text](/docs/quickstart#random-quote) for testing. `randomPost: true` will ignore the `post` field.
**randomMediaUrl** `boolean` (default: false)
[Generate a random media image](/docs/quickstart#random-image) for testing. `randomMediaUrl: true` will ignore the `mediaUrls` field.
**idempotencyKey** `string`
An optional unique ID associated with the post. Duplicate IDs will be rejected. Please see [idempotency](/docs/apis/post/overview#idempotent-posts) for details.
**notes** `string`
Set notes on a post that can be retrieved via the [/history endpoint](/docs/apis/history/overview). Notes are for reference only and do not affect the post.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"post": "Today is a great day!",
"platforms": ["twitter", "facebook", "instagram", "linkedin"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"]
}' \
-X POST https://api.ayrshare.com/api/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
post: "Today is a great day!", // required
platforms: ["bluesky", "facebook", "instagram", "linkedin", "twitter"], // required
mediaUrls: ["https://img.ayrshare.com/012/gb.jpg"] //optional
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'post': 'Today is a great day!',
'platforms': ['bluesky', 'facebook', 'instagram', 'linkedin', 'twitter'],
'mediaUrls': ['https://img.ayrshare.com/012/gb.jpg']}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
"Today is a great day!",
"platforms" => ["bluesky", "facebook", "instagram", "linkedin", "pinterest", "twitter"],
"mediaUrls" => ["https://img.ayrshare.com/012/gb.jpg"]
];
curl_setopt_array($curl, [
CURLOPT_URL => 'https://api.ayrshare.com/api/post',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer API_KEY', // Replace 'API_KEY' with your actual API key
'Content-Type: application/json'
],
]);
$response = curl_exec($curl);
curl_close($curl);
echo $response;
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"post": "Today is a great day!",
"platforms": []string{"bluesky", "facebook", "instagram", "linkedin", "twitter"},
"mediaUrls": []string{"https://img.ayrshare.com/012/gb.jpg"}
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/post",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostPOSTRequest_csharp
{
class Post
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post";
// Set up request headers
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
// Prepare JSON content
string json = "{\"post\" : \"Today is a great day!\","
+ "\"platforms\" : [ \"bluesky\", \"facebook\", \"instagram\", \"linkedin\", \"twitter\" ],"
+ "\"mediaUrls\" : [ \"https://img.ayrshare.com/012/gb.jpg\" ]}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// Send POST request
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
// Read response
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```ruby Ruby theme={"system"}
require 'httparty' # gem install httparty
res = HTTParty.post("https://api.ayrshare.com/api/post",
headers: {Authorization: "Bearer API_KEY"},
body: {
post: "Today is a great day!",
platforms: ['bluesky', 'facebook', 'instagram', 'linkedin', 'twitter'],
mediaUrls: ["https://img.ayrshare.com/012/gb.jpg"]
}).body
puts res
```
```json 200: Success theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/bluesky
"status": "success",
"id": "at://did:plc:n7atrjd22xgkmgwig6dzlhzd/app.bsky.feed.post/3lez7fwx452", // Bluesky Social Post ID
"cid": "bafyreie6n475cd3ynr6sfacvohu5qgjibcooxnug6zcbghkwnrwi5stafy", // Bluesky Content ID
"postUrl": "https://bsky.app/profile/madworlds.bsky.social/post/3lez7fwx4572",
"platform": "bluesky"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/facebook
"status": "success",
"id": "104923907983682_108329000309742", // Facebook Social Post ID
"platform": "facebook",
"postUrl": "https://www.facebook.com/104923907983682_108329000309742",
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/google
"status": "success",
"id": "3837985438581442258", // Google Business Profile Social Post ID
"postUrl": "https://local.google.com/place?id=5229466225881728772&use=posts&lpsid=CM",
"type": "localPosts",
"platform": "gmb"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/instagram
"status": "success",
"platform": "instagram", // Instagram Social Post ID
"id": "17878176260289172",
"postUrl": "https://www.instagram.com/p/CP1dI9Hp_WO/",
"usedQuota": 12,
"contentIssues": { // Optional — only present when Ayrshare detected and resolved a content issue
"originMediaHostFailed": true,
"details": ["Media URL could not be retrieved by the social network. Successfully posted using Ayrshare automated media protection."]
}
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/linkedin
"status": "success",
"id": "urn:li:share:7282181682126807041", // LinkedIn Social Post ID
"postUrl": "https://www.linkedin.com/feed/update/urn:li:share:7282181682126807041",
"owner": "urn:li:organization:77682157",
"platform": "linkedin"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/pinterest
"status": "success",
"id": "42995371460659062", // Pinterest Social Post ID
"postUrl": "https://www.pinterest.com/pin/429953714606062/",
"platform": "pinterest"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/reddit
"status": "success",
"id": "1hvdvof", // Reddit Social Post ID
"postUrl": "https://www.reddit.com/r/test/comments/1hvdvof/reddit_post_title/",
"platform": "reddit"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/snapchat
"status": "success",
"id": "921ed204-e123-5b08-a9ce-zx489f1f38c5", // Snapchat Social Post ID
"mediaId": "V6noC6UOQgOcABCDEgFZEwAAgd3F0cnp1eWtxZAb9PsH-MXb9PsIWAAAAAA", // Snapchat Media ID
"postUrl": "https://www.snapchat.com/add/samsmith1920/921ed204-e123-5b08-a9ce-zx489f1f38c5",
"type": "stories",
"ended": "2025-05-23T13:04:30.545Z",
"platform": "snapchat"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/telegram
"status": "success",
"id": 635, // Telegram Social Post ID
"postUrl": "https://t.me/c/1424847122/635",
"platform": "telegram"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/tiktok
"status": "success",
"idShare": "v_pub_url~v2.7456954878846683182",
"id": "pending", // TikTok Social Post ID - see https://www.ayrshare.com/docs/apis/post/social-networks/tiktok
"isVideo": true,
"platform": "tiktok"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/twitter
"status": "success",
"id": "1288899996423983105", // X/Twitter Social Post ID
"platform": "twitter",
"postUrl": "https://x.com/handle/status/1288899996423983105"
},
{
// Details at https://www.ayrshare.com/docs/apis/post/social-networks/youtube
"status": "success",
"id": "3oQeP-kTsbo", // YouTube Social Post ID
"postUrl": "https://youtu.be/3oQeP-kTo",
"platform": "youtube"
}
],
"id": "RhrbDtYh7hdSMc67zC8H" // Ayrshare Post ID used for delete, analytics, comments, etc.
}
```
```json 200: Success for Scheduled theme={"system"}
{
"status": "scheduled",
"scheduleDate": "2023-04-01T10:04:12Z",
"id": "IUiaqFkQP96UJJXYjRpv", // Ayrshare Post ID used for delete, comment, analytics, etc.
"refId": "9abf1426d6ce9122effdeeddfdfdfd",
"post": "Genius is eternal patience. - Michelangelo"
}
```
```json 200: Success with Profile Key theme={"system"}
{
"status": "success",
"posts": [
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"id": "1869166036466991888",
"postUrl": "https://twitter.com/wondrouswaffles/status/1869",
"platform": "twitter"
},
{
"status": "success",
"id": "106638148652344_601623445855888",
"postUrl": "https://www.facebook.com/106638148652329/posts/6016",
"platform": "facebook"
}
],
"id": "bVQotNtxgXAUmLtqmw2",
"refId": "b68bdcabb379be2cf1186c1e595449804b232sa",
"profileTitle": "The Best Profile",
"post": "Formal education will make you a living. Self education will make you a fortune. - Jim Rohn"
}
]
}
```
```json 200: Success Scheduled Post and Profile Key theme={"system"}
{
"status": "success",
"posts": [
{
"status": "scheduled",
"scheduleDate": "2023-04-01T10:04:12Z",
"id": "qvu8gysraodz2WFZgRX7", // Ayrshare Post ID used for delete, comment, analytics, etc.
"refId": "9abf1426d6ce9122effdeeddfdfdfd",
"profileTitle": "Best Profile",
"post": "I never thought of myself as being handsome or good-looking or whatever. I always felt like an outsider. - Elton John"
}
]
}
```
```json 400: Bad Request theme={"system"}
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 156,
"message": "Youtube does not seem to be linked with Ayrshare. Please confirm the linkage on the Social Accounts page in your dashboard. .../ayrshare.com/additional-info/troubleshooting",
"platform": "youtube"
},
{
"action": "post",
"status": "error",
"code": 110,
"message": "Status is a duplicate.",
"post": "Today is a great day",
"platform": "twitter"
}
],
"postIds": [],
"id": "0OGBzZssN5hxy8dMSRaD" // Ayrshare Post ID used for delete, comment, analytics, etc.
}
```
```json 400: Bad Request with Profile Key theme={"system"}
{
"status": "error",
"posts": [
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 156,
"message": "Instagram is not linked.
Please confirm the linkage on the Social Accounts page in the dashboard. https://www.ayrshare.com/docs/help-center/overview",
"platform": "instagram"
}
],
"postIds": [],
"id": "ekftQJ0hFB1Fx6bnM33",
"refId": "9abf1426d6ce9122effdeeddfdfdfd",
"profileTitle": "Best Profile",
"post": "The most common way people give up their power is by thinking they don't have any. - Alice Walker"
}
]
}
```
```javascript 500: Internal Server Error theme={"system"}
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"code": 107,
"message": "Facebook Error: This status update is identical to the last one you posted. Try posting something different, or delete your previous update.",
"platform": "facebook"
}
],
"postIds": [],
"id": "6APU4qqI7XO7JM3BOy6B" // Ayrshare Post ID used for delete, comment, analytics, etc.
}
```
# Retry a Post
Source: https://www.ayrshare.com/docs/apis/post/retry-post
PUT /post/retry
Retry publishing a post that failed with an error status. The retried post is treated as a scheduled post, and you can retry each failed post only once.
> [!NOTE]
>
> **Targeting X/Twitter?** Starting March 31, 2026, all X operations require your own API credentials. After linking X via OAuth, include these 2 headers in your request:
>
> `X-Twitter-OAuth1-Api-Key` — Your API Key (Consumer Key)
>
> `X-Twitter-OAuth1-Api-Secret` — Your API Key Secret (Consumer Secret)
>
> **One-time setup per Ayrshare account.** You create one X Developer App and reuse the same API Key and Secret across every sub-profile / end-user you link. You do *not* create a new app per customer.
>
> Not linked yet? See the [full setup guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
>
> Your keys are never logged or stored by Ayrshare.
Retry to publish a post that failed with a `status` of `"error".` A retried post will be treated as a scheduled post, thus the final status can be obtained via the [/history](/docs/apis/history/overview) endpoint or the [scheduled action webhook](/docs/apis/webhooks/actions#scheduled-action).
While the post is being retried, the `status` will be `"pending"`. You may check on the status of the retry by calling the [GET post endpoint](/docs/apis/post/get-post).
The post can be retried *only once*. Recommend to only use if the error message indicates a retry is possible.
> [!NOTE]
>
> When retrying a failed social media post, first verify that the post hasn't already been
> published. Social networks may sometimes report errors even when they've successfully processed
> and published the post.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
The top-level Ayrshare Post ID. The original post must have a status of "error".
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"id": "s8k2jsk0pl"}' \
-X PUT https://api.ayrshare.com/api/post/retry
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post/retry", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`
},
body: JSON.stringify({
id: "s8k2jsk0pl" // required
})
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': 's8k2jsk0pl'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.put('https://api.ayrshare.com/api/post/retry',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
's8k2jsk0pl' // Replace with your actual ID
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostRetryPOSTRequest_csharp
{
class PostRetry
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post/retry";
// Set up request headers
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
// Prepare JSON content
string json = "{\"id\": \"s8k2jsk0pl\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// Send PATCH request
HttpResponseMessage response = await client.PutAsync(url, content);
response.EnsureSuccessStatusCode();
// Read response
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"id": "s8k2jsk0pl"
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("PUT", "https://api.ayrshare.com/api/post/retry",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```json 200: Success theme={"system"}
{
"status": "pending", // The post will be retried
"id": "N7kaDiZAfwc544OBKlgc" // Ayrshare Post ID
}
```
```json 400: Already Retried theme={"system"}
{
"action": "post",
"status": "error",
"code": 329,
"message": "This post was already retried and cannot be retried again."
}
```
# Bluesky API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/bluesky
Post to Bluesky with links, hashtags, images, and videos, plus supported and unsupported features, mentions, alternative text, and character limits.
## Posting to Bluesky
JSON for a basic post with a link, hashtag, and image/video using the Bluesky API.
Please see [Bluesky Media Guidelines](/docs/media-guidelines/bluesky) and [Bluesky Authorization](/docs/dashboard/connect-social-accounts/bluesky) for more information.:
```json Bluesky Post with Image theme={"system"}
{
"post": "The best Bluesky image post ever #best", // Max 300 characters or empty string
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["bluesky"]
}
```
```json Bluesky Post with Video theme={"system"}
{
"post": "The best Bluesky video post ever https://www.google.com", // Max 300 characters or empty string
"mediaUrls": ["https://img.ayrshare.com/012/vid.mp4"],
"platforms": ["bluesky"]
}
```
### Bluesky Supported Features
Bluesky supports text, images (up to 4), video (only 1), and posts with links or emojis.
Up to 4 images or one videos can be sent in a single post.
The post text is limited to 300 characters.
Animated GIFs are supported and sent as videos.
Bluesky does support hashtags and mentions (@handle).
Alt text on images or videos.
See [Bluesky Media Guidelines](/docs/media-guidelines/bluesky) for more information.
Link previews in posts are supported. Include a link in the post text and the Ayrshare API will
automatically add a link preview to the post.
### Bluesky Unsupported Features
Video thumbnails are not yet supported by Bluesky.
## Bluesky Mentions
Mention another Bluesky handle by adding `@handle` in the post text. For example:
```json Bluesky Post with Mention theme={"system"}
{
"post": "The best Bluesky image post ever @handle",
"platforms": ["bluesky"]
}
```
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
## Alternative Text
Add Bluesky alternative text, also known as alt text, to an image or video.
Bluesky alt text is an accessibility feature used for additional user info and screen readers.
Use the `altText` in the `blueSkyOptions` object.
```json Bluesky Alt Text theme={"system"}
{
"blueSkyOptions": {
// Array of Alt Texts
"altText": ["This is my best pic", "😃 here is the next one"]
}
}
```
Each alt text must correspond to an image or video in the `mediaUrls` array.
The alt text will be applied to each media item in order.
## Character Limits
Please see [Bluesky Character Limits](/docs/help-center/technical-support/character-limits#bluesky-character-limits) for more information.
# Facebook Page API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/facebook
Post to Facebook Pages with carousel images, Reels, Stories, location tagging, audience targeting, Page mentions, Meta Business Suite drafts, and GIFs.
> [!NOTE]
>
> If you have issues connecting Facebook, please see the [troubleshooting
> guide](/docs/help-center/technical-support/facebook-posting-issues).
> [!NOTE]
>
> If your media is hosted on a server or CDN you control, make sure Meta's publishing
> crawler can fetch it. When Meta can't fetch your remotely-hosted media — including Facebook
> Reels media ingested through the shared media-upload path — you may get the dedicated,
> non-retryable Ayrshare error code `479`. See
> [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked) to resolve it.
> [!NOTE]
>
> Posting an AI-generated image? When Ayrshare converts a HEIC, HEIF, or AVIF image to JPEG it
> writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI
> disclosure, when the image declares one, and Facebook renders that disclosure as an "AI content"
> label. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a
> location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic
> signature does not survive a re-encode. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
## Posting to Facebook
Posting using the Facebook API requires connecting a Facebook Page. Facebook does not allow personal accounts to be connected.
JSON for a basic post with a link and image to a Facebook page:
```json Facebook Post with Image theme={"system"}
{
"post": "The best FB post ever #best https://www.facebook.com", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["facebook"]
}
```
Facebook will automatically show a preview of the link in the post unless there is an image or
video included. In the above example the image will show. Removing the image will cause the link
preview to show.
If your video doesn't end in a known video extension such as mp4, please use the `isVideo`
parameter. See the [/post endpoint](/docs/apis/post/post) for details.
Facebook also supports sending media without post text. If you do not want post text included
send an empty String `post: ""`.
See [Facebook Media Guidelines](/docs/media-guidelines/facebook-pages) and [Facebook Authorization](/docs/dashboard/connect-social-accounts/facebook) for more information.
> [!WARNING]
>
> Facebook's API does not support mixing images and videos in a single post. You
> can include multiple images OR a single video, but not both.
## Carousel Images
Post Facebook Carousel images through Ayrshare's API with the `carousel` body parameter.
```json Facebook Carousel Post theme={"system"}
{
"faceBookOptions": {
"carousel": {
// The URL of the "See More At" button - required
"link": "URL of See More At...",
"items": [
// 2 min, 10 max elements in the array
{
"name": "Image name", // optional
"link": "URL when image clicked", // required
"picture": "URL of image" // required
},
{
"name": "Image name",
"link": "URL when image clicked",
"picture": "URL of image"
}
]
}
}
}
```
The top-level `link` parameter is the URL at the end of the carousel.
The `items` is an array of object containing the following values. Minimum 2 elements and max 10
elements in the items array.
`picture` - URL of the carousel image.
`link` - URL when the image is clicked.
`name` - (optional) Image displayed in each image card.
**Note:** Do not use the `media_urls` with `carousel`. If `media_urls` is used, `carousels` will be ignored.
Please see our [Facebook Carousel Guide](https://www.ayrshare.com/blog/post-a-series-of-facebook-images-as-a-carousel/) for more information.
## Facebook Reels
You can post a video to Facebook Reels with the following `facebookOptions`.
> [!NOTE]
>
> Meta enforces a limit of 30 Reels per connected Facebook Page within any 24-hour period to prevent
> spam and ensure platform quality. This is a rolling limit, meaning it counts the most recent 24
> hours, not calendar days.
```json Facebook Reels Post theme={"system"}
{
"post": "The description of the video",
"platforms": ["facebook"],
"mediaUrls": ["https://img.ayrshare.com/012/reel.mp4"],
"faceBookOptions": {
"reels": true,
"title": "Super title for the Reel", // optional
"thumbNail": "https://img.ayrshare.com/012/reel-thumbnail.jpg", // optional
"collaborators": ["123456789012345"] // optional Facebook Page ID, one supported in v1
}
}
```
`reels`: Set to `true` to post the video to Reels. Required if you want to post to Facebook
Reels.
`title`: The title of the video with a maximum of 255 characters. Over 255 characters will be
truncated. Optional.
`thumbNail`: The thumbnail (cover image) of the video. The thumbnail should a URL to a JPEG or
PNG file. The thumbnail should be less than 10MB in size. Optional.
`collaborators`: Invite one Facebook Page ID as a collaborator on the Reel. Facebook
collaborators are only supported on Facebook Reels, not on standard Facebook feed posts,
photos, or non-Reel video posts.
Ayrshare currently forwards one collaborator Page ID per Facebook Reel in v1. If you send more
than one value, only the first non-empty value is forwarded and the rest are dropped with a
warning in the `warnings` array. Ayrshare does not check that the value is a valid Page ID
first, so make sure the first value in the list is the Page you want to invite.
Meta limits collaborator invites to 10 invitations per Facebook Page within a rolling 24-hour
period.
Facebook Reel collaborators must be other Facebook Pages. Personal Facebook profiles are not
supported for collaborator invites on this API.
The Reel can publish successfully while Facebook still declines the collaborator invite. When
that happens, the Reel post returns `success` with a warning in the `warnings` array rather than
failing. Facebook declines the invite when the collaborator Page is not eligible — most commonly
because it is in the same Meta Business portfolio as the posting Page (a Page cannot collaborate
with itself), does not allow collaborators, or is not eligible for Facebook Reels collaboration.
Collaborators are not supported with Facebook's own drafts or scheduling
(`faceBookOptions.draft` or `faceBookOptions.scheduledPublishDate`), because Ayrshare sends the
invite at the moment the Reel publishes. To schedule a Reel with a collaborator, use Ayrshare scheduling (the
top-level [`scheduleDate`](/docs/apis/post/post#body-parameters) parameter) instead — Ayrshare publishes the Reel
at the scheduled time and then sends the invite.
Please see the [Reels API video requirements](/docs/media-guidelines/facebook-pages#reels) or an [example of using the Facebook Reels API](https://www.ayrshare.com/blog/facebook-reels-api-how-to-post-fb-reels-using-a-social-media-api/).
After the Reel publishes, Ayrshare sends the collaborator invite through Meta's Facebook Reels API.
You can later check the invitation status with the shared [Get Collaborator Request Status
API](/docs/apis/utils/instagram-get-collaborator).
> [!WARNING]
>
> Meta occasionally has issues processing Facebook Reels for some accounts. Even though an error response may be returned, the Reel might still have been published.
>
> If you encounter errors for Facebook Reels, we recommend checking the Reel status. If the post *has not been* published you may try [posting once more](/docs/apis/post/retry-post). This is often successful.
>
> You may use the URL contained in the `verifyReelsUrl` field to check the status. Be sure to use the same Profile Key as your original post. Wait until the time in `verifyReelsIn` to check the Reel status (10 minutes). If a HTTP error code of `400` is returned, the post failed and you may try the post one more time.
>
> ```json Facebook Reels Post Error theme={"system"}
> {
> "action": "post",
> "status": "error",
> "code": 108,
> "message": "Facebook Error: Facebook cannot process your post at this time. Please try again. ",
> "detailsData": {
> "id": "104619420979033_36930641143",
> "verifyReelsUrl": "https://api.ayrshare.com/history/36930641143?searchPlatformId=true&platform=facebook",
> "verifyReelsIn": "2024-01-26T00:07:05.170Z",
> "postUrl": "https://www.facebook.com/1046194209_36930641143",
> "reels": true
> },
> "retryAvailable": true,
> "platform": "facebook"
> }
> ```
## Facebook Stories
Publish Facebook Stories to Facebook Pages either as a photo or as a video.
Facebook Stories do not support post text - any text provided in the `post` field, including
mentions, will be ignored.
A photo or video uploaded for a story cannot have been used in a previously published post.
A video story cannot exceed 60 seconds.
Turn on [Stories Archive](https://www.facebook.com/help/2058997717520567) to be able to GET
stories.
```json Facebook Stories Post theme={"system"}
{
"post": "", // Ignored by stories
"platforms": ["facebook"],
"mediaUrls": ["https://img.ayrshare.com/012/stories.mp4"],
"faceBookOptions": {
"stories": true
}
}
```
Please see the [Stories API image and video requirements](/docs/media-guidelines/facebook-pages#stories).
## Media Captions
Set a Facebook caption for each media image posted.
Accepts an array of string caption text. Each array must correspond to a media url. E.g. \["This is my best pic", "😃 here is the next one"] refers to the 1st and 2nd urls in `mediaUrls`.
```json Facebook Media Captions theme={"system"}
{
"faceBookOptions": {
"mediaCaptions": ["This is my best pic", "😃 here is the next one"]
}
}
```
## Location Tagging
A Facebook location tag is specified by a `locationId,` which is a Facebook Page ID or Facebook Page name. For example, Facebook page Id of the [Guggenheim Museum](https://www.facebook.com/guggenheimmuseum) is `7640348500` or Facebook page name `"@guggenheimmuseum"`. Pages must be associated with a physical location.
```json Facebook Location Tagging theme={"system"}
// Using the Facebook Page Id - must be associated with a location
{
"faceBookOptions": {
"locationId": 7640348500 // Guggenheim Museum Page Id
}
}
```
You can look up the `locationId` (Page Id) with the [brand endpoint](/docs/apis/listen/search/fb-page-search). Please note that the Page must have a location listed or the locationId will return an error.
Not supported on text posts, images, reels, and stories. Videos are not supported by Meta for location tagging.
## Audience Targeting
When creating a Page post on Facebook, you have the option to limit its visibility to a specific audience using two types of targeting:
1. Facebook Targeting: This allows you to define the audience for your post based on factors such as age, gender, location, and relationship. By setting these parameters, you can ensure that your post is shown only to people who meet the specified criteria.
2. Facebook Feed Targeting: This option enables you to further refine the visibility of your post within the News Feeds of your targeted audience.
You can use either targeting option individually or combine them.
### Targeting
Facebook targeting [limits the audience](https://www.facebook.com/help/352402648173466) for publishing content to specific demographics. Anyone not in these demographics will not be able to view this content. This will not override any Page-level demographic restrictions that may be in place.
Available demographic fields `targeting`:
`ageMin`: Limit the minimum age that can view the posts. Accepted values of 13, 15, 18, 21, or
25\. Other ages will be rejected.
`countries`: Also known as geofencing, you can limit the countries that can view the post. Array
of [country codes](/docs/iso-codes/country). The maximum number of countries that can be targeted is
25\. If you need to target more than 25 countries, or restrict certain countries, please see [Facebook Page Country Restrictions](/docs/help-center/technical-support/facebook-page-country-restrictions).
```json Facebook Targeting theme={"system"}
{
"faceBookOptions": {
"targeting": {
"ageMin": 18,
"countries": ["DE", "BR"]
}
}
}
```
### Feed Targeting
Anyone in these Facebook feed targeting groups are more likely to see this post, others are less likely, but may still see it anyway. Use targeting, see above, if you want to guarantee the restriction.
Use the `feedTargeting` object in the `faceBookOptions` object.
Available demographic fields for `feedTargeting`:
`ageMin`: Limit the minimum age that can view the posts.. Integer value 13 or higher. Default is
0\.
`ageMax`: Limit the maximum age that can view the posts. Integer value 65 or lower.
`countries`: Required. Also known as geo targeting, you can indicate the countries that should be able to
view the post. Array of [country codes](/docs/iso-codes/country).
`collegeYears`: Target the college graduation age to view the post. Array of integers for
graduation year from college.
`educationStatuses`: Target the education status to view the post. Array of integers for
targeting based on education level. Use `1` for high school, `2` for undergraduate, and `3` for
alum.
`genders`: Target the gender to view the post. Array of integers for targeting specific genders.
`1` targets all male viewers and `2` females. Default is to target both.
`relationshipStatuses`: Target the relationship status to view the post. Array of integers for
targeting based on relationship status. Use `1` for single, `2` for 'in a relationship', `3` for
married, and `4` for engaged. Default is all types.
```json Facebook Feed Targeting theme={"system"}
{
"faceBookOptions": {
"feedTargeting": {
"ageMax": 30,
"ageMin": 26,
"countries": ["DE", "BR"],
"genders": [1, 2],
"relationshipStatuses": [1, 2]
}
}
}
```
## Alternative Text
Add Facebook alternative text, also known as alt text, to an image or video. Facebook alt text is an accessibility feature used for additional user info and screen readers.
Use the `altText` in the `faceBookOptions` object.
```json Facebook Alt Text theme={"system"}
{
"faceBookOptions": {
// Array of Alt Texts
"altText": ["This is my best pic", "😃 here is the next one"]
}
}
```
Each alt text must correspond to an image or video in the `mediaUrls` array. The alt text will be applied to each image in order.
## Video Thumbnail
Set a Facebook video thumbnail (cover image). Send a remote URL of a PNG or JPG file that is the same dimensions as the video and less than 10 MB. URL should end in .png or .jpg.
```json Example Facebook Video Thumbnail theme={"system"}
{
"faceBookOptions": {
"thumbNail": "https://octodex.github.com/images/Fintechtocat.png"
}
}
```
## Video Title
Add a Facebook video title with the `title` parameter.
```json Facebook Video Title theme={"system"}
{
"faceBookOptions": {
"title": "The best video ever!"
}
}
```
## Animated GIFs
Only one `mediaUrls` URL is allowed in the array when posting a Facebook animated GIF.
If the media URL does not end in ".gif" or ".GIF", set the `isVideo` field to `true`.
```json Facebook Animated GIF theme={"system"}
{
"randomPost": true,
"platforms": ["facebook"],
// Set to true if the GIF URL does not end in .gif or .GIF
"isVideo": false,
// Only one URL is allowed in the array
"mediaUrls": ["https://img.ayrshare.com/012/cat.gif"]
}
```
## Facebook Page Mentions
You can mention another Facebook Page, also known as Facebook mentions or Facebook tagging, by including the Facebook Page name or Page ID in the post text.
Facebook does not allow mentions or tags of personal profiles or groups.
Include a mention with the following @mention:
`@page-name` or `@[page-id]`
### Mention with Page Name
An example post text with a Page name. You can find the Page name from the Facebook URL, e.g. [https://www.facebook.com/Ayrshare](https://www.facebook.com/Ayrshare)
```json Facebook Page Mention with Page Name theme={"system"}
{
"post": "This is the best social media api by @Ayrshare"
}
```
Ayrshare will make a best attempt to find the matching Page based on the name, but sometimes a match cannot be found. A more reliable method is using a Page ID.
### Mention with Page ID
An example post text with a Page ID; note the use of \[]. Please see below on how to find the Page ID.
```json Facebook Page Mention with Page ID theme={"system"}
{
"post": "This is the best social media api by @[738681876342836]" // Ayrshare Page ID
}
```
The Facebook Page ID will resolve to the matching page and ***notify the Page mentioned***.
> [!WARNING]
>
> Use Page mentioning with caution by only mentioning Pages you are associated. You should be careful to never spam.
>
> When you mention a Page, **the owner of the Page will be notified via an alert and email**. If several Pages complain about you mentioning them, Facebook could ban your account. Generally you should only tag Pages that you have an established relationship with.
>
> The Facebook Page being mentioned **must allow** other Pages to mention/tag their Page. Mentions can be enabled in the Page's general settings under "Others Tag this Page".
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
### Find a Facebook Page ID
#### Brand Endpoint
You can look up either a Facebook user or Facebook Page using the [Brand endpoints](/docs/apis/brand/overview).
#### At Facebook.com
1. From the Facebook News Feed, click **Pages** in the left side menu.
2. Click your Page name to go to your Page.
3. Click **About** at the top of your Page. If you don't see it, click **More**▼.
4. Scroll down to find your **Page ID** below **MORE INFO**.
or if you want to find the ID of a page your don't own:
If the Facebook Page has a URL such as`https://www.facebook.com/ayrshare-1234567890` then the ID
is ***1234567890***.
If the Facebook Page has a URL such as:
`https://www.facebook.com/pages/ayrshare/123466789203` then the Page ID is the number at the end, such as **123466789203**.
You can test that the Page ID is correct by going to `https://facebook.com/page-id` and it will resolve to the Page.
You can also try a 3rd party Facebook Page look up tool, which is not supported by Ayrshare, such as: [https://lookup-id.com/](https://lookup-id.com/)
If all other methods fail, you can go to the Facebook page in a browser and view the page source. In Chrome, right click to select "View Source". Then search for either `owning_profile_id` or `profile_id` to find the Page ID.
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
### Enable Page Mentions
The Page must allow mentions and tagging in posts and comments. By default this is enabled, but you can verify by logging into facebook.com and switching to your Page.
1. Viewing as your Page, click your page's **profile image** in the top right corner.
2. Click **Settings & Privacy** -> **Settings**.
3. In the Settings menu, click **Privacy**.
4. Click **Page and Tagging**.
5. Scroll down to **Reviewing**.
6. Toggle settings to allow tag and mentions.
## Meta Business Suite
### Draft Posts
Create a Facebook draft post that appears in the [Meta Business Suite Draft](https://business.facebook.com/latest/posts/draft_posts) tab. Facebook draft posts are posts that you have started writing, but have not yet published. You can save draft posts to come back to later, or you can schedule them to be published at a later date.
Add the `draft` parameter to `faceBookOptions` to send the post as a draft. Available for text, images, video, and reels posts.
```json Facebook Draft Post theme={"system"}
{
"faceBookOptions": {
"draft": true
}
}
```
### Schedule Publish Time
Set a schedule publish time for Facebook posts that appear in the [Meta Business Suite Scheduled Posts](https://business.facebook.com/latest/posts/scheduled_posts) tab. Facebook scheduled posts are posts that you have created and scheduled to be published at a later date and are managed in the Meta Business Suite. This can be useful for planning your social media posts in advance, or for publishing posts at times when you know your audience is most active.
Available for text, images, video, and reels posts.
> [!WARNING]
>
> The publish time must be greater than 10 minutes from the current time and within 29 days of the current date.
>
> Unless you need to manage Facebook posts in Meta Business Suite, we recommend using the standard [post schedule date](/docs/apis/post/overview#schedule-posts).
Add the `scheduledPublishDate` parameter to `faceBookOptions` to schedule a post time.
```json Facebook Scheduled Post theme={"system"}
{
"faceBookOptions": {
"scheduledPublishDate": "2023-09-28T21:44:06Z" // Future date in UTC
}
}
```
## Link Preview
A Facebook post will automatically create a link preview of a link in the post body.
However, if you want to specify either a different link or force the link preview, use the `link` parameter with URL of the link.
```json Facebook Link Preview theme={"system"}
{
"faceBookOptions": {
"link": "https://www.ayrshare.com/instagram-hashtag-guide/"
}
}
```
If a `mediaUrls` field is included in the post request, the media will be displayed instead of the link preview.
## Facebook Ads
Facebook Ads are a way to promote your posts to a wider audience.
> [card] [Facebook Ads](/docs/apis/ads/overview)
## Character Limits
Please see [Facebook Character Limits](/docs/help-center/technical-support/character-limits#facebook-character-limits) for more information.
## Additional Information
### How do I add line breaks or rich text to a Facebook post?
Facebook line breaks can be added to a post with a special [new line character](/docs/apis/post/overview#line-breaks).
Rich text, such as bold or italic lettering, can be added to a Facebook post with a few [html elements](/docs/apis/post/overview#rich-text-posts).
### Why do I see "Published by Ayrshare" on a post?
Don't worry, it only shows in the admin view. Please see the [troubleshooting guide](/docs/help-center/technical-support/facebook-shows-published-by-ayrshare) for more information.
# Google Business Profile API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/google
Publish Google Business Profile standard, image, video, event, and offer posts, with posting requirements, product categories, and call to action options.
> [!NOTE]
>
> When Ayrshare converts a WebP, HEIC, HEIF, or AVIF image to JPEG for Google Business Profile it
> preserves the image's color rendering and writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI disclosure, when the image declares one. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic signature
> does not survive a re-encode. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
## Posting to Google Business Profile
There are four types of Google Business Profile, formerly known as Google My Business, posts that can be created using the Google Business Profile API.
In the below example, the `post` text is posted as an update to the user's Google business page.
[Find out more](https://www.ayrshare.com/blog/google-my-business-what-is-gmb-why-you-need-it-and-how-to-use-it/) about Google Business Profile (GBP).
> [!WARNING]
>
> You must [claim](https://support.google.com/business/answer/2911778) your Google Business Profile page before linking it with Ayrshare. Be sure to choose the Google account that is an admin of your GBP page during link authorization. The business profile must also be verified and public.
>
> You can access your GBP manage console and check the verification status at [https://business.google.com/](https://business.google.com/)
Product posts cannot be created via the Google Business Profile API at this time.
JSON for a basic post with just text to GBP, which will appear in the "What's New" section of :
```json Google Business Profile Post theme={"system"}
{
"post": "The best GMB ever #best https://www.google.com",
"platforms": ["gmb"] // Please note to use `gmb`
}
```
or JSON for a basic post with an image to GBP:
```json Google Business Profile Post with Image theme={"system"}
{
"post": "The best GMB ever #best https://www.google.com",
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["gmb"]
}
```
If your video doesn't end in a known video extension such as mp4, please use the `isVideo`
parameter. See the [/post endpoint](/docs/apis/post/overview) for details.
See [Google Business Profile Media Guidelines](/docs/media-guidelines/google-business-profile) and [Google Business Profile Authorization](/docs/dashboard/connect-social-accounts/google-business) for more information.
more information.
## Google Business Profile Posting Requirements
Please be sure your post does not contain the following. Google will reject your post and the post will show as "This post is no longer available" or with a "Rejected" label in the Google Console:
Phone numbers. Google will reject all posts containing phone numbers.
Spam, false claims, or false representation.
Off-topic post that doesn't pertain to your business.
Duplicate content.
Inappropriate content.
Offensive language.
Please see [Google Business Profile's Content Policy](https://support.google.com/business/answer/7213077?hl=en) for more information.
## Standard Post (What's New)
A standard local post appears in the "*Posts*" GBP section and is categorized as "What's New".
It can contain post text, an image, or a Call to Action ([see below](/docs/apis/post/social-networks/google#call-to-action)).
Videos are not supported for to be published to the What's New section via the Google Business Profile API.
Please see [Image or Video Post](#image-or-video-post) for details on how to publish videos to Google Business Profile.
*These posts will appear in the "**Posts -> What's New**" section in the GMB management console.*
```json Google Business Profile Standard Post theme={"system"}
{
"post": "A wonderful post",
"platforms": ["gmb"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"], // optional - images only
"gmbOptions": {
"callToAction": {
// optional
"actionType": "learn_more",
"url": "https://www.ayrshare.com"
}
}
}
```
## Image or Video Post
You can publish Image or Video posts to Google Business Profile.
Images and videos will appear in the "**Photos**" section in the GBP management console.
### Requirements
Only one image or one video can be added to your GBP location, which will appear in the "Photos"
section of the GBP console.
Only one image or video is allowed per post.
The post text is required by the endpoint, but is not used by GBP.
Call to Action is not supported for image or video posts.
Be sure to include the `isPhotoVideo` parameter in the `gmbOptions` object.
```json Google Business Profile Image Post {6} theme={"system"}
{
"post": "What an image!",
"platforms": ["gmb"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"], // required
"gmbOptions": {
"isPhotoVideo": true, //required
"category": "product" // optional
}
}
```
### Video Processing
Google at times takes extra time to process uploaded videos.
You can view the status of the media in the Google Business Profile console:
1. Go to your [Google Business Profile console](https://business.google.com/us/business-profile/).
2. Click the button "See your profile". You will br brought to the GBP page.
3. Click on the "Photos" tab.
4. You will see a list of photos and videos.
5. Click on the photo or video you want to view.
> [!WARNING]
>
> If your video has a "pending" status, it is still being processed by Google. Please wait a few
> minutes and refresh the page. Be sure your Google Business Profile has been verififed and the
> profile is public.
>
> If the video seems stuck in "pending", this may be due to a bug at Google.
> Please contact [Google Business support](https://support.google.com/business/gethelp) for
> assistance.
### Product Category
You can specify a product category for an image or video so the media is categorized in the "Photos" section of the GBP console. For example, if an image has the category "product", it will appear under the "Product" tab in Photos.
Some categories are not allowed for certain business types. For example, Ayrshare could not use `food_and_drink` since we are not in the food/drink industry.
Available Categories:
`cover`: Cover photo. A location has only one cover photo.
`profile`: Profile photo. A location has only one profile photo.
`logo`: Logo photo.
`exterior`: Exterior media.
`interior`: Interior media.
`product`: Product media.
`at_work`: At work media.
`food_and_drink`: Food and drink media.
`menu`: Menu media.
`common_area`: Common area media.
`rooms`: Rooms media.
`teams`: Teams media.
## Events Post
Promote an event at your business. Events require a title, start and end dates, and can include a video. For example, a real estate agent may advertise an open house showing.
Events support Call to Action.
Include an optional image with the `mediaUrls` parameter.
Videos are not supported for Events.
Event posts will appear in the "**Posts -> Events**" section in the GBP management console.
The `post` text will be used for the Event Details.
```json Google Business Profile Event Post theme={"system"}
{
"post": "A great event!", // Event Details
"platforms": ["gmb"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"], // optional
"gmbOptions": {
"event": {
"title": "Check this event out.", // required
"startDate": "2021-08-12T20:17:46.384Z", // required
"endDate": "2021-09-12T20:17:46.384Z" // required
}
}
}
```
## Offers Post
Provide promotional sales or offers from your business. Offers require a title as well as start and end dates and times. A "View offer" action button is automatically added to the post. You can also include a photo, description, coupon code, link, and terms and conditions with the post. For example, a toy store may advertise 20% off all beanie babies for a week.
Include an image with the `mediaUrls` parameter.
*Videos are not supported for Offers.*
*These posts will appear in the "**Posts -> Offers**" section in the GBP management console.*
```json Google Business Profile Offers Post theme={"system"}
{
"post": "A great offer for everyone!",
"platforms": ["gmb"],
"gmbOptions": {
"offer": {
"title": "Great Sale.", // required
"startDate": "2021-08-12T20:17:46.384Z", // required
"endDate": "2021-09-12T20:17:46.384Z", // required
"couponCode": "BOGO-JET-CODE", // required - max 58 characters
"redeemOnlineUrl": "https://www.ayrshare.com", // required
"termsConditions": "Offer only valid if you can prove you are a time traveler" // required
}
}
}
```
`couponCode` has a maximum of 58 characters.
## Call to Action
Provide general information about your business and appears as an action button. For example, a website might promote a *Learn More* button to redirect to their website.
A Call to Action can be added to a Standard post or Event. Offers cannot have Call to Actions.
Include an image with the `mediaUrls` parameter.
```json Google Business Profile Call to Action Post theme={"system"}
{
"post": "Take this action!",
"platforms": ["gmb"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"], //optional
"gmbOptions": {
"callToAction": {
"actionType": "order", // required
"url": "https://www.ayrshare.com" // required for all action types but "call"
}
}
}
```
The `actionType` is displayed as a button on the post. Possible values are:
**book**: Prompts a user to book an appointment, table, or something similar.
**order**: Prompts a user to order something.
**shop**: Prompts a user to browse a product catalog.
**learn\_more**: Prompts a user to see additional details on a website.
**sign\_up**: Prompts a user to register, sign up, or join something.
**call**: Prompts a user to call a business. The `url` field is not required for `call`.
However, you must first have set a phone number in your Google Business Profile for the call
action to appear on the post.
The `url` parameter is the URL the user will be directed to upon clicking.
Additional [Google Business Profile information](https://www.ayrshare.com/blog/google-my-business-what-is-gmb-why-you-need-it-and-how-to-use-it/).
> [card] [Google Business Profile Linking](/docs/dashboard/connect-social-accounts/google-business)
## First Comment
Google Business Profile does not support post comments, so the `firstComment` parameter is skipped for Google Business Profile-only posts.
A `firstComment` warning/skip object is returned, but the post itself still succeeds, with no error raised.
## Google Business Profile Mentions
While you can add a `@handle` to a Google Business Profile post, Google Business Profile does not support resolving mentions in the post text.
The `@handle` will remain as plain text.
## Character Limits
Please see [Google Business Profile Character Limits](/docs/help-center/technical-support/character-limits#google-business-profile-character-limits) for more information.
# Instagram API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/instagram
Publish Instagram carousels, Reels with music, trial reels, and Stories, plus thumbnails, alt text, user tags, collaborators, and AI content labels.
> [!NOTE]
>
> If you have issues connecting Instagram, please see the [troubleshooting
> guide](/docs/help-center/technical-support/facebook-or-instagram-linking-issues).
> [!NOTE]
>
> If your media is hosted on a server or CDN you control, make sure Meta's publishing
> crawler can fetch it. A hard media-fetch / crawler-block failure returns the dedicated,
> non-retryable Ayrshare error code `479` ("social network could not download media from this URL"),
> often with error code 138 or "Restricted by robots.txt" in the details as the underlying signal.
> See [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked) to resolve it.
> [!NOTE]
>
> Posting an AI-generated image? When Ayrshare converts a WebP, HEIC, HEIF, or AVIF image to JPEG it
> writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI
> disclosure, when the image declares one, and Instagram renders that disclosure as an "AI info"
> label. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a
> location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic
> signature does not survive a re-encode. Note that `instagramOptions.autoResize` resizes
> through a separate step that carries no metadata, so leave it off if the disclosure needs to reach
> Instagram. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
[Posting to Instagram API](https://www.youtube.com/embed/3F7mbVN0qrw)
The Instagram API has the following requirements and restrictions.
Business or Creator Instagram Account connected with a Facebook Page - [see
here](/docs/dashboard/connect-social-accounts/instagram).
Only 50 Instagram posts are allowed over a 24 hour period. See below for `usedQuota`
The `post` text may contain up to *5 hashtags* (e.g. #wildtimes) and *3 username mentions*
(e.g. @natgeo).
@mention Instagram users will receive a notification.
Maximum 2,200 post characters.
Multi-image/videos posts are supported and sent as a carousel. You may send up to 10 videos and
images.
Instagram does not support deletes via an API. Deletes must occur manually using the Instagram
app.
If your Reel video doesn't end in a known video extension such as mp4, please use the `isVideo`
parameter. See the [/post endpoint](/docs/apis/post/post) for details.
Instagram also supports sending media without post text. If you do not want post text included
send an empty String `post: ""`.
See [Instagram Media Guidelines](/docs/media-guidelines/instagram) and [Instagram Authorization](/docs/dashboard/connect-social-accounts/instagram) for more information.
## Posting to Instagram
JSON for a basic post with an image and hashtags to Instagram:
```json Instagram Post theme={"system"}
{
"post": "The best IG ever #best #awesome https://www.instagram.com",
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["instagram"]
}
```
Hashtags are clickable in Instagram posts, but links are not.
> [!NOTE]
>
> The aspect ratio of images & videos and the duration of video are very important to successfully post to Instagram. If they do not meet the requirements, the post will be rejected.
>
> [Please see the Instagram section of Image and Video Guidelines](/docs/media-guidelines/instagram).
## Instagram Business or Creator Account
Your Instagram account must be a Business or Creator Account and connected with a Facebook Page. The set up is free and easy.
See here for detailed instructions:
> [card] [Instagram Linking](/docs/dashboard/connect-social-accounts/instagram)
## Carousel of Images and Videos
You can post multiple images or Reel videos to Instagram as a carousel; up to a combined total of 10 images or videos may be used in a carousel.
Just add your additional images or videos to the `mediaUrls` array, and the carousel will automatically be created.
```json Instagram Carousel Post theme={"system"}
{
// Max 10 images or videos
"mediaUrls": ["https://url.com/image.jpg", "https://url.com/video.mp4"]
}
```
> [!NOTE]
>
> Video URLs must end in a known extension such as `mp4`. The `isVideo` parameter is not supported
> for Instagram carousels.
## Instagram Reels
In Instagram a video post is called a Reel.
You can post a video to Instagram Reels API with the following optional `instagramOptions` options.
```json Instagram Reels Options theme={"system"}
{
"instagramOptions": {
"shareReelsFeed": true,
"audioName": "The Weeknd - Blinding Lights",
"thumbNail": "https://img.ayrshare.com/012/gb.jpg", // if used, thumbNailOffset will be ignored.
"thumbNailOffset": 30000
}
}
```
Please see the [Reels API video requirements](/docs/media-guidelines/instagram#reels) for details on
the video requirements.
`shareReelsFeed`: Boolean set to `true` to indicate that the Reel *can* appear in both the
**Feed** and **Reels** tabs or `false` to indicate the Reel *can* only appear in the **Reels**
tab. This value is a hint to Instagram where you would like the Reel to appear, but neither
value determines whether the Reel *actually* appears in the **Reels** or **Feed** tab because
the Reel may not meet eligibility requirements or may not be selected by Instagram's algorithm.
`audioName`: String name of the audio music of your Reels media. You can only rename once, either
while creating a reel or after from the audio page. For example, `"The Weeknd - Blinding
Lights"`. This only sets the **display label** on audio you already supplied in the video. To
attach a real licensed track from Instagram's catalog, use
[`audioConfiguration`](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel) instead.
`audioConfiguration`: Object that attaches a licensed track from Instagram's audio catalog. See
[Adding music to a Reel](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel).
**Requires Instagram linked through Facebook Login.**
`thumbNail`: String URL of the Reel cover image (thumbnail). Please see here for [details on
thumbNail](/docs/apis/post/social-networks/instagram#reels-thumbnails).
`thumbNailOffset`: Integer offset in milliseconds of the thumbnail frame. Please see here for
[details on thumbNailOffset.](/docs/apis/post/social-networks/instagram#reels-thumbnails)
Please see the [Reels API video requirements](/docs/media-guidelines/instagram#reels) or an [example of using the Instagram Reels API](https://www.ayrshare.com/blog/instagram-reels-api-how-to-post-videos-to-reels-using-a-social-media-api/).
## Adding Music to a Reel
> [!WARNING]
>
> **Requires Instagram linked through Facebook Login.** Instagram's Audio API is only available to accounts connected via **Facebook Login**, which needs a Facebook Page connected to the Instagram account. Accounts connected with **Instagram Login** (the Ayrshare default) cannot search, look up, or attach audio, and every audio request returns `code: 514`.
>
> This is a Meta restriction, not an Ayrshare one. Meta's documentation states the Audio API "is only available on the Instagram API with Facebook Login. It is not supported on the Instagram API with Instagram Login." No permission or plan upgrade changes it.
>
> **Three things are required:**
>
> 1. An Instagram **Business** or **Creator** account
>
> 2. A **Facebook Page connected** to that Instagram account
>
> 3. Instagram linked to Ayrshare with **Instagram Login disabled**, so the Facebook Login flow is used
>
> See the [step-by-step setup guide](/docs/dashboard/connect-social-accounts/instagram#music-attach-eligibility-facebook-login-required).
`instagramOptions.audioConfiguration` attaches a licensed track from Instagram's audio catalog to a Reel. Find a track with [audio search](/docs/apis/media/search-instagram-audio), then pass its `audioId` when you publish.
```json Reel with music theme={"system"}
{
"post": "New drop",
"platforms": ["instagram"],
"mediaUrls": ["https://img.ayrshare.com/012/vid.mp4"],
"instagramOptions": {
"audioConfiguration": {
"audioId": "17901234567890123",
"audioVolume": 80,
"videoVolume": 20
}
}
}
```
`audioId`: **Required.** String track id from [audio
search](/docs/apis/media/search-instagram-audio) or [audio
lookup](/docs/apis/media/get-instagram-audio). Missing or empty returns `code: 516`.
`audioVolume`: Optional integer from `0` to `100` setting how loud the attached track plays.
`videoVolume`: Optional integer from `0` to `100` setting how loud the video's own audio plays
underneath the track.
> [!NOTE]
>
> Both volumes are **optional integers**. Omit them and Instagram applies its own default of `100`.
> A decimal, a numeric string such as `"80"`, a value outside 0 to 100, or an explicit `null` is
> rejected with `code: 101` before Instagram is called. `null` is not accepted as "unset" — omit the
> field instead.
### Reels Only
Music can be attached to **Reels only**. Stories, images, and carousels are rejected with `code: 515`.
You do **not** need to set `reels: true` or `isVideo: true`. A video `mediaUrls` entry is detected as a Reel automatically, and an auto-detected Reel accepts `audioConfiguration` the same as an explicit one. Setting the flags explicitly still works and takes precedence.
### audioConfiguration and audioName Together
Instagram allows only one track per Reel. If you send both, `audioConfiguration` wins, `audioName` is dropped, and a non-fatal warning with `code: 518` is returned in `postIds[].warnings[]`. The post still publishes.
Send only `audioConfiguration` to attach a licensed track, or only `audioName` to set a display label on audio already in your video.
```json 200: Published with a warning theme={"system"}
{
"status": "success",
"postIds": [
{
"status": "success",
"id": "17901234567890123",
"postUrl": "https://www.instagram.com/reel/ABC123/",
"usedQuota": 1,
"warnings": [
{
"action": "post",
"status": "warning",
"code": 518,
"message": "The audioName value was ignored because audioConfiguration was also sent, and Instagram can attach only one track per Reel. Send only audioConfiguration to attach a licensed track, or only audioName to set the display label.",
"feature": "audioName"
}
],
"platform": "instagram"
}
]
}
```
`warnings[]` is nested on the **`postIds` entry for that platform**, not lifted to the top level, so read it from `postIds[0].warnings` rather than `warnings`.
### Audio Errors
| Code | Meaning |
| - | - |
| 101 | `audioVolume` or `videoVolume` is not an integer from 0 to 100, or is `null`. |
| 514 | Instagram is linked with Instagram Login. The Audio API requires Facebook Login. |
| 515 | `audioConfiguration` was sent on something that is not a Reel. |
| 516 | `audioId` is missing or empty. |
| 517 | Instagram rejected the track as invalid, region-restricted, or not licensed for this use. |
| 518 | Warning, not an error. `audioName` was ignored because `audioConfiguration` was also sent. |
You may also set a [Reels cover url](/docs/apis/post/social-networks/instagram#reels-thumbnails) and [location & user tags](/docs/apis/post/social-networks/instagram#user-tags-and-locations).
## Trial Reels
A trial reel is a Reel that is published only to non-followers when it is first posted, allowing you to test how a Reel performs with a fresh audience before it reaches your existing followers. Set `trialParams.graduationStrategy` in `instagramOptions` to publish a Reel as a trial.
```json Instagram Trial Reel theme={"system"}
{
"post": "Testing this with a fresh audience first",
"mediaUrls": ["https://img.ayrshare.com/random/portrait1.mp4"],
"platforms": ["instagram"],
"instagramOptions": {
"trialParams": {
"graduationStrategy": "MANUAL"
}
}
}
```
`graduationStrategy` controls how a trial reel later "graduates" — i.e. becomes visible to your followers as well. It is required when `trialParams` is provided and must be one of:
`"MANUAL"` — the post stays a trial reel until you manually graduate it from
inside the Instagram app.
`"SS_PERFORMANCE"` — Meta auto-graduates the Reel based on early performance
against non-followers.
> [!NOTE]
>
> Graduation itself (promoting a published trial reel to followers) is not currently exposed by
> Meta's API and must be performed manually in the Instagram app. Ayrshare will add a graduation
> endpoint once Meta exposes one.
### Trial reel restrictions
The trial reel request is rejected at the Ayrshare edge — before any Meta call — when any of these conditions are not met:
Exactly one media URL ending in `.mp4` or `.mov` (case-insensitive). Carousels are not supported.
`instagramOptions.stories` must not be `true`. Stories cannot be trial reels.
`graduationStrategy` must be present and exactly `"MANUAL"` or `"SS_PERFORMANCE"` (case-sensitive).
Failures return one of three Ayrshare error codes — see [Instagram Trial Reel Errors](/docs/errors/errors-ayrshare#instagram-specific-error-codes) (`447`, `448`, `449`) for the full payloads.
## Instagram Stories
You can post a single image or video as an Instagram Story with the following `instagramOptions`. Instagram stories disappear after 24 hours.
```json Stories Post theme={"system"}
{
"post": "The description of the video",
"mediaUrls": ["https://img.ayrshare.com/random/portrait1.mp4"],
"instagramOptions": {
"stories": true
}
}
```
Please see the [Stories API requirements](/docs/media-guidelines/instagram#stories).
Instagram Stories do not support post text - any text provided in the `post` field, including
mentions, will be ignored.
Stories expire after 24 hours.
Stories can be published from both Instagram Business and Creator accounts.
Instagram Stories do not support collaborators.
Publishing stickers (i.e., link, poll, location) is not supported by Instagram.
## Reels Thumbnails
You may select a frame of the Reel as the thumbnail image or your own cover image (thumbnail) from an external URL.
```json Instagram Thumbnail theme={"system"}
{
"instagramOptions": {
// milliseconds
"thumbNailOffset": 30000,
// If both thumbNail and thumbNailOffset includes, thumbNail will be used.
"thumbNail": "https://img.ayrshare.com/012/gb.jpg"
}
}
```
The offset is the location in milliseconds of the thumbnail frame of the video Reel. Default value is `0`, which is the first frame of the Reel.
If you specify both a thumbnail URL and a thumbnail offset, the thumbnail offset will be ignored.
The Reel thumbnail must follow the [Reels thumbnail requirements](/docs/media-guidelines/instagram#reels-thumbnails).
Signed URLs with redirects are not guaranteed to be compatible with cover URLs.
We recommend a non-signed URL or use the [/media endpoint](/docs/apis/media/overview).
## Alternative Text
Add Instagram alternative text, also known as alt text, to an image.
Instagram alt text is an accessibility feature used for additional user info and screen readers.
Alt text supports up to 1,000 characters per image.
Instagram does not support alt text for Reels or Stories.
Use the `altText` in the `instagramOptions` object.
```json Instagram Alt Text theme={"system"}
{
"instagramOptions": {
// Array of Alt Texts
"altText": ["This is my best pic", "😃 here is the next one"]
}
}
```
Each alt text must correspond to an image or video in the `mediaUrls` array.
The alt text will be applied to each image in order.
## User Tags & Locations
> [!NOTE]
>
> The Instagram user will be notified when you use their username in a post. Please be cautious to
> not spam users or post with their username repeatedly. If you do, Instagram could suspend or
> deactivate your account.
An image or a Reel can be tagged with Instagram users and an image, video, or a Reel can be tagged with a location using the `instagramOptions` parameter.
### Location
A location is specified by a `locationId,` which is a Facebook Page ID or Facebook Page name. For example, Facebook page Id of the [Guggenheim Museum](https://www.facebook.com/guggenheimmuseum) is `7640348500` or Facebook page name `"@guggenheimmuseum"`. Pages must be associated with a physical location.
```json Instagram Location theme={"system"}
// Using the Facebook Page Id - must be associated with a location
{
"instagramOptions": {
"locationId": 7640348500 // Guggenheim Museum Page Id
}
}
// Using the Facebook Page name - must be associated with a location
{
"instagramOptions": {
"locationId": "@guggenheimmuseum" // Guggenheim Museum Page name. Must begin with @
}
}
```
You can look up the `locationId` (Page Id) with the [brand endpoint](/docs/apis/brand/overview). Please note that the Page must have a location listed or the locationId will return an error.
> [!NOTE]
>
> Not supported on images or videos in carousels.
### User Tags
Instagram tags allow you to tag other Instagram users in your post.
Users are specified by a `userTags` containing an Array of objects with an Instagram username and x/y coordinates (image only). User tags can be added for single images or Reels, but not regular videos, multiple images, or Stories.
Usernames must be public Instagram accounts. Do not include the @ of the user handle.
`x` and `y` values must be `float` numbers that originate from the top-left of the image, with a
range of `0.0`–`1.0`. Single images. Do not include with Reels or an error will occur.
```json Instagram User Tags theme={"system"}
{
"instagramOptions": {
"userTags": [
{
"username": "ayrshare", // Required: Instagram username
"x": 0.5, // Required for Images, cannot be used with Reels
"y": 1.0 // Required for Images, cannot be used with Reels
},
{
"username": "johnboy", // Required: Instagram username
"x": 0.1, // Required for Images, cannot be used with Reels
"y": 0.9 // Required for Images, cannot be used with Reels
}
]
}
}
```
## Instagram Mentions
Mention another Instagram handle by adding @handle in the post text.
For example you can mention the @ayrshare handle in the post text:
```json Instagram Mentions theme={"system"}
{
"post": "The best social media API @Ayrshare ever!", // empty string is allowed
"mediaUrls": ["https://images.com/image.jpg"],
"platforms": ["instagram"]
}
```
> [!WARNING]
>
> The `@mentioned` user will be notified of the mention. Please review the [important
> rules](/docs/testing/post-verification#mentions) on mentions.
## Collaboration
Instagram collaboration allows you to co-author content with other Instagram accounts by [tagging others as collaborators](https://www.facebook.com/help/instagram/291200585956732).
This allows you to designate other Instagram users as creators on your post.
When tagged, these users receive an invitation to collaborate in the mobile app.
If they accept, the post will also appear in their feed and be visible to their followers, extending the post’s reach and engagement potential.
### Collaborators
The public original author can tag another public account as an Instagram collaborator.
The other account will receive a message, which allows them to either accept or deny the request.
If the other account accepts, the post will also show on their profile and be distributed to their followers in Instagram feed.
The post’s header will attribute the content to both accounts.
You may add collaborators to a Reel, image, or carousel.
Tagging a private collaborator is not permitted via the Instagram API. While
such a functionality exists within the Instagram app, platforms and their APIs
are often not at [feature parity](/docs/help-center/product/are-social-networks-native-apps-and-the-apis-at-parity).
> [!WARNING]
>
> Instagram Stories do not support collaborators.
> [!WARNING]
>
>
>
**Only invite collaborators you expect to `accept` your invite**.
>
>
> If the [collaborator
> responds](/docs/apis/post/social-networks/instagram#get-collaborator-request-status) with a
> `declined` collaboration request, **do not re-invite them** until you contact them to
> understand the reason for the decline.
>
>
>
> Repeated declines from the same or multiple users will put your Instagram and Ayrshare account
> at risk of cancelation.
>
>
>
The original author can add or remove a collaborator at any time.
>
Invite *up to three* collaborators with an array of public Instagram usernames.
```json Instagram Collaborators theme={"system"}
{
"instagramOptions": {
"collaborators": ["ayrshare", "therock", "taylorswift"] // Up to three
}
}
```
These three collaborators will receive a message invitation in their Instagram mobile app and can accept or decline the invite, and then you may [check the invited user request status](/docs/apis/post/social-networks/instagram#get-collaborator-request-status).
Please only invite collaborators who you know will accept your request or your Instagram account may be negatively affected.
> [!WARNING]
>
> Note about invited collaborators: With a few exceptions, data on or about co-authored media can
> only be accessed through the API by the user who published the media; collaborators are unable to
> access this data via the API.
### Get Collaborator Request Status
After inviting an Instagram collaborator, you can check the status of the request using the [Get Collaborator Request Status API](/docs/apis/utils/instagram-get-collaborator).
## AI Content Label
Self-disclose AI-generated media so Instagram applies its **AI info** label to the published post.
```json Instagram AI Content Label theme={"system"}
{
"post": "Generated with a little help from a model",
"platforms": ["instagram"],
"mediaUrls": ["https://img.ayrshare.com/random/portrait1.mp4"],
"instagramOptions": {
"isAIGenerated": true
}
}
```
`isAIGenerated`: Boolean set to `true` to add Instagram's AI info label to
the post. Default `false` — omitting the parameter publishes the post with no label.
Instagram shows the label as **AI info** beneath the account name. Opening it reads
"\[account] added an AI label to this content".
Accepted values are `true`, `"true"`, `false`, and
`"false"`. Any other value — `"yes"` or `1`, for example —
never fails the request: the post publishes normally without the label, and the response adds a
non-fatal `warnings` entry (`code: 497`,
`feature: "isAIGenerated"`) listing the accepted values.
Applies to single images, single videos, Reels, Stories, and carousels.
For a [carousel](/docs/apis/post/social-networks/instagram#carousel-of-images-and-videos), the label
applies to the carousel as a whole. Individual carousel items cannot be labeled separately.
This is a self-disclosure only. Ayrshare does not detect or infer whether your media is
AI-generated. Instagram may separately label content its own systems detect as AI-generated.
> [!NOTE]
>
> The Ayrshare [MCP Server](/docs/additional/mcp-action-server) takes `isAIGenerated` as a strict
> boolean — pass `true`, not `"true"`. The REST API accepts both.
> [!NOTE]
>
> The label cannot be added or removed after a post publishes. Set `isAIGenerated` at post time.
## Auto Image Resize
> [!NOTE]
>
> Max Pack Required
Images are automatically resized to 1080 x 1080 px to work with Instagram with the `autoResize` parameter. Please note, this will resize the image for all included platforms, so we recommend making one call for Instagram and a different /post call for additional platforms.
```json Instagram Auto Image Resize theme={"system"}
{
"post": "Let it go!",
"platforms": ["instagram"],
"mediaUrls": ["https://images.ayrshare.com/imgs/GhostBusters.jpg"],
"instagramOptions": {
"autoResize": true, // Max Pack
"locationId": 7640348500,
"userTags": [
{
"username": "ayrshare",
"x": 0.5,
"y": 0.5
},
{
"username": "ayrshare",
"x": 0.3,
"y": 0.2
}
]
}
}
```
## Used Quota
The Instagram response will include the current `usedQuota` for the number of Instagram posts done over a rolling 24-hour period. Only 50 Instagram posts are permitted by Instagram in a 24-rolling-hour period.
```json Instagram Used Quota theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"id": "17823977408036085",
"postUrl": "https://www.instagram.com/p/CeBrkQuN1Kv/",
"usedQuota": 15,
"platform": "instagram"
}
],
"id": "l4FaPHSXWJNdmMfm3dIE",
"refId": "65806e8d9efd78a58c05566a887043329dcdc76b",
"post": "Luckily for Alice, the little magic bottle had now had its full effect."
}
```
If the quota has been reached, an error message will be returned.
## Content Issues
Ayrshare includes built-in media protection that can detect and resolve certain media delivery issues during posting. When a post succeeds but a content issue was detected and resolved, the response includes an optional `contentIssues` object. This allows you to identify and fix issues with your media hosting proactively.
The `contentIssues` object is only present when an issue was detected and resolved — normal successful posts do not include it.
| Field | Type | Description |
| - | - | - |
| `originMediaHostFailed` | boolean | The social network could not retrieve the media from the provided URL. Ayrshare's automated media protection resolved the issue and completed the post successfully. Consider reviewing your media hosting configuration — see [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked) for common causes. |
| `details` | array of strings | Human-readable descriptions of each detected issue. |
```json Content Issues Response Example theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"id": "17878176260289172",
"postUrl": "https://www.instagram.com/p/CP1dI9Hp_WO/",
"usedQuota": 12,
"platform": "instagram",
"contentIssues": {
"originMediaHostFailed": true,
"details": [
"Media URL could not be retrieved by the social network. Successfully posted using Ayrshare automated media protection."
]
}
}
],
"id": "abc123",
"refId": "65806e8d9efd78a58c05566a887043329dcdc76b",
"post": "Your post text here"
}
```
> [!TIP]
>
> If you see `originMediaHostFailed` in your responses, there may be an issue with your media hosting that prevents social networks from accessing your media. See [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked) for detailed troubleshooting steps.
## Error Details
> [!NOTE]
>
> When an Instagram media publish fails, the error object now surfaces Meta's underlying error text in the `details` field, alongside the Ayrshare `code` and `message`. This lets you distinguish between different root causes (for example, an aspect-ratio rejection versus a media-download failure) without contacting support.
```json Instagram Publish Error theme={"system"}
{
"status": "error",
"errors": [
{
"action": "post",
"status": "error",
"platform": "instagram",
"code": 156,
"message": "An error occurred while posting to Instagram.",
"details": "The submitted image was not found. The aspect ratio is not supported. Please see https://developers.facebook.com/docs/instagram-api for more information."
}
]
}
```
The `message` is Ayrshare's stable, human-readable summary, while `details` echoes the raw text returned by Meta for the failed publish.
## Adding line breaks or rich text to an Instagram post
Instagram line breaks can be added to a post with a special [new line character](/docs/apis/post/overview#line-breaks).
Rich text, such as bold or italic lettering, can be added to an Instagram post with a few [html elements](/docs/apis/post/overview#rich-text-posts).
## Character Limits
Please see [Instagram Character Limits](/docs/help-center/technical-support/character-limits#instagram-character-limits) for more information.
## Additional endpoints
> [card] [Get Collaborator Request Status](/docs/apis/utils/instagram-get-collaborator)
# LinkedIn API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/linkedin
Post to LinkedIn with documents, multi image posts, mentions, media titles, alternative text, audience targeting, post visibility, and video thumbnails.
> [!NOTE]
>
> Relying on embedded image metadata for AI disclosure? LinkedIn strips embedded metadata when it
> ingests an image, so an `Iptc4xmpExt:DigitalSourceType` disclosure does not survive
> LinkedIn's image processing and no AI label is applied. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
The LinkedIn API has the following requirements and restrictions:
The `post` field accepts up to 3,000 characters.
LinkedIn will automatically preview the link in the post unless there is an image or video
included. In the above example the image will show. Removing the image will cause the link
preview to show. If the link preview cannot be retrieved, the post will still be published and a
`linkPreviewFailed: true` field will be returned in the /post response.
Hashtags are clickable in LinkedIn. Use the # symbol to specify a LinkedIn hashtag.
If publishing a video and doesn't end in a known video extension such as mp4, please use the
`isVideo` parameter. See the [/post endpoint](/docs/apis/post/post) for details.
LinkedIn video processing takes several minutes to complete. Please wait before trying the share
URL or getting video analytics.
LinkedIn limits 150 posts per day per LinkedIn account.
Posting to LinkedIn Groups is not supported.
LinkedIn also supports sending media without post text. If you do not want post text included
send an empty String `post: ""`.
You may link either a company page or a personal LinkedIn account.
See [LinkedIn Media Guidelines](/docs/media-guidelines/linkedin) and [LinkedIn
Authorization](/docs/dashboard/connect-social-accounts/linkedin) for more information.
> [!WARNING]
>
> You must be a "Super Admin" or a "Content Admin" to connect a LinkedIn company page.
## Posting to LinkedIn
JSON for a basic post with a link and image to LinkedIn:
```json LinkedIn Post theme={"system"}
{
"post": "The best LinkedIn post ever #best https://www.linkedin.com", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["linkedin"]
}
```
## LinkedIn Options
You can set additional options for a post by using the `linkedInOptions` parameter.
```json LinkedIn Options theme={"system"}
{
"linkedInOptions": {
"altText": ["This is my best pic", "😃 here is the next one"],
"disableShare": true,
"targeting": {
"countries": ["US", "IN", "DE", "GB"],
"seniorities": ["Senior", "VP"],
"degrees": ["Master of Digital Design", "Bachelor of Engineering"],
"fieldsOfStudy": ["Consumer Economics", "Computer Games and Programming Skills"],
"industries": ["Telecommunications Carriers", "Banking"],
"jobFunctions": ["Information Technology", "Entrepreneurship"],
"staffCountRanges": ["size_51_to_200", "size_5001_to_10000"]
},
"thumbNail": "https://example.com/thumbnail.jpg",
"title": "Sample PPTX",
"titles": ["This is an amazing video", "😃 here is the next one"],
"visibility": "public"
}
}
```
LinkedIn options are optional fields that can be used to control the post.
**altText** `array of strings`
The alternative text of images for accessibility and screen readers.
See [LinkedIn Alternative Text](/docs/apis/post/social-networks/linkedin#alternative-text) for more information.
**disableShare** `boolean` (default: false)
Disable the ability for users to reshare the LinkedIn post.
See [LinkedIn Disable Share](/docs/apis/post/social-networks/linkedin#disable-share) for more information.
**targeting** `object`
Target your organic posts to specific groups by countries, industry, job title, and more.
Requires at least 300 followers in the targeted audience.
See [LinkedIn Audience Targeting](/docs/apis/post/social-networks/linkedin#audience-targeting) for more information.
**thumbNail** `string`
The thumbnail of a video. Must be PNG or JPG, same dimensions as video, under 10MB. URL should end in .png or .jpg.
See [LinkedIn Video Thumbnail](/docs/apis/post/social-networks/linkedin#video-thumbnail) for more information.
**title** `string`
The title of a document when posting PPT, PPTX, DOC, DOCX, or PDF files. Maximum length: 400 characters.
If not specified, the filename is used.
See [LinkedIn Documents](/docs/apis/post/social-networks/linkedin#documents) for more information.
**titles** `array of strings`
Title or media captions to LinkedIn images or videos.
Each title corresponds to a media URL in order.
See [LinkedIn Media Titles](/docs/apis/post/social-networks/linkedin#media-titles) for more information.
**visibility** `string` (default: public)
The visibility of the post.
Values: `public`, `connections`, or `loggedin`.
See [LinkedIn Post Visibility](/docs/apis/post/social-networks/linkedin#post-visibility) for more information.
## Alternative Text
Add LinkedIn alternative text, also known as LinkedIn alt text, to a LinkedIn image. LinkedIn alt text is an accessibility feature used for additional user info and screen readers. LinkedIn does not support altText on videos or documents.
Use the `altText` in the `linkedInOptions` object.
```json LinkedIn Alt Text theme={"system"}
{
"linkedInOptions": {
"altText": ["This is my best pic", "😃 here is the next one"] // Array of Alt Texts
}
}
```
Each alt text must correspond to an image in the `mediaUrls` array. The alt text will be applied to each image in order.
## Audience Targeting
LinkedIn allows you to target your organic posts to specific groups of users. You can target by countries, industry, job title, and more.
> [!WARNING]
>
> LinkedIn targeting requires at least **300 followers in the targeted audience**. For example if
> you have 400 followers and target just users in the country US, but only 200 of your followers are
> in the US, the targeting will fail since the audience is too small.
Use the `targeting` object in the `linkedInOptions` object:
```json LinkedIn Audience Targeting theme={"system"}
{
"linkedInOptions": {
"targeting": {
"countries": ["US", "IN", "DE", "GB"],
"seniorities": ["Senior", "VP"],
"degrees": ["Master of Digital Design", "Bachelor of Engineering"],
"fieldsOfStudy": ["Consumer Economics", "Computer Games and Programming Skills"],
"industries": ["Telecommunications Carriers", "Banking"],
"jobFunctions": ["Information Technology", "Entrepreneurship"],
"staffCountRanges": ["size_51_to_200", "size_5001_to_10000"]
}
}
}
```
The available targeting options are listed in the following JSON files:
## Authorization Refresh
LinkedIn must be reauthorized *every year* via the Social Accounts page.
An email and webhook social action notification will be sent 15 days in advance of authorization
expiration.
The refresh required date and remaining days can be retrieved from the
[/user](/docs/apis/user/overview) endpoint.
The user will see on their social account linkage page an alert and a refresh button 30 days
prior to expiration.
## Character Limits
Please see [LinkedIn Character Limits](/docs/help-center/technical-support/character-limits#linkedin-character-limits) for more information.
## Disable Share
You can disable the ability for users to reshare the LinkedIn post by setting the `disableShare` field to `true` in the `linkedInOptions` object:
```json LinkedIn Disable Share theme={"system"}
{
"linkedInOptions": {
"disableShare": true
}
}
```
## Documents
Use the API to post a document on LinkedIn. Supported file formats include: PPT, PPTX, DOC, DOCX, and PDF. Ensure the document is under 100MB in size and does not exceed 300 pages.
Below is a code sample demonstrating how to post a PPTX document with a title:
```json LinkedIn Document Post theme={"system"}
{
"post": "What a great document",
"platforms": ["linkedin"],
"linkedInOptions": {
"title": "Sample PPTX" // optional. If not specified, the file name used.
},
"mediaUrls": ["https://scholar.harvard.edu/files/torman_personal/files/samplepptx.pptx"]
}
```
The media URL must point to a file with a known document extension: ppt, pptx, doc, docx, or pdf. A query string after the file name is fine, so signed URLs such as Firebase Storage or presigned S3 links work, for example `https://example.com/files/deck.pdf?token=abc`.
## LinkedIn Mentions
You may mention other LinkedIn organizations, i.e. company pages, or member profiles, i.e. individual profile pages, in your posts.
Ayrshare makes an attempt to resolve the @mention by doing a lookup on the handle.
If the @mention is not found, the @ will be removed from the post since LinkedIn will not accept posts with unresolved mentions.
Individual LinkedIn profiles cannot be mentioned.
> [!WARNING]
>
> If any mention in a LinkedIn post cannot be resolved (due to privacy settings,
> account restrictions, or other platform limitations), LinkedIn may fail to
> render all mentions in that post as clickable links. This is LinkedIn's platform
> behavior and not an API issue. To avoid this, ensure all mentioned users and
> organizations allow mentions from your organization or consider posting mentions
> separately to isolate any problematic mentions.
Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
### Organization Profiles
Mention another LinkedIn organization handle by adding `@handle` in the post text. The `@handle` is the name of the company or organization.
You can mention in posts, comments, and reply to comments.
For example,
Ayrshare's LinkedIn company profile is
[https://www.linkedin.com/company/ayrshare](https://www.linkedin.com/company/ayrshare) and the
handle used would be *@ayrshare*.
Zara's LinkedIn company profile is
[https://www.linkedin.com/company/zara-sa/](https://www.linkedin.com/company/zara-sa/) and the
handle used would be *@zara-sa*.
```json LinkedIn Mention theme={"system"}
{
"post": "The best social media API @ayrshare ever!",
"platforms": ["linkedin"]
}
```
### Member Profiles
Mention another LinkedIn member profile by adding `@vanity_name` or `@[John Smith]` in the post text.
1. `@vanity_name` is the vanity name of the member profile. For example in the member's profile URL `https://www.linkedin.com/in/vanity_name/`, the vanity name is `vanity_name`.
2. `@[John Smith]` is the person's full name. For example if the person's full name is `John Smith`, the mention would be `@[John Smith]`.
You can mention in posts, comments, and reply to comments.
```json LinkedIn Mention theme={"system"}
{
"post": "The best social media API @[John Smith] and @ava_smith ever!",
"platforms": ["linkedin"]
}
```
In the above example, John Smith and Ava Smith will be mentioned in the post - the first using their full name and the second using their vanity name.
**Important Notes:**
1. You must have a LinkedIn company page connected to your Ayrshare account to mention member
profiles. If you connect your own personal LinkedIn account, you will not be able to mention other
people's profiles.
2. LinkedIn only allows mentioning people who follow your organization / company page.
## Media Titles
Add title or media captions to LinkedIn images or videos.
Use the `titles` in the `linkedInOptions` object.
```json LinkedIn Media Titles theme={"system"}
{
"linkedInOptions": {
"titles": ["This is an amazing video", "😃 here is the next one"] // Array of Alt Texts
}
}
```
Each title must correspond to an image or video in the `mediaUrls` array. The title will be applied to each image or video in order.
## Multi Image Posts
You may post up to *9 image URLs* in the `mediaUrls` to LinkedIn.
```json LinkedIn Multi Image Post theme={"system"}
{
"mediaUrls": ["https://url1", ..., "https://url9"]
}
```
LinkedIn does not yet support carousel images for organic posts.
## Post Visibility
LinkedIn allows you to control the visibility of your posts.
You can set the visibility to public, connections only, or loggedin users only.
If no visibility is specified, the post will be "public" and visible to all LinkedIn users.
`public`: The post will be visible to all LinkedIn users. Available for both company pages and
personal LinkedIn connected accounts. Default if no visibility is specified.
`connections`: The post will be visible to the 1st degree network of the owner (user profile).
Available for personal LinkedIn connected accounts only.
`loggedin`: The post will be visible to logged in LinkedIn users only. Available for both
company pages and personal LinkedIn connected accounts.
Use the `visibility` field in the `linkedInOptions` object:
```json LinkedIn Post Visibility theme={"system"}
{
"linkedInOptions": {
"visibility": "public" // "public", "connections", or "loggedin"
}
}
```
## Video Publishing Permissions
On LinkedIn, only users with specific permissions can publish videos to company pages:
1. Users with Admin permissions.
2. Users with Direct Sponsored Content (DSC) permissions.
Content Administrators do not have the ability to publish videos to LinkedIn company pages.
## Video Thumbnail
Set a LinkedIn thumbnail for a video. Send a remote URL of a PNG or JPG file that is the same dimensions as the video and less than 10 MB. URL should end in .png or .jpg.
Use the `thumbNail` in the `linkedInOptions` object.
```json LinkedIn Video Thumbnail theme={"system"}
{
"linkedInOptions": {
"thumbNail": "https://octodex.github.com/images/Fintechtocat.png"
}
}
```
Please see here for more [examples of using the LinkedIn API](https://www.ayrshare.com/how-to-post-and-get-analytics-with-the-linkedin-api#linkedin-api-examples).
# Supported Social Networks
Source: https://www.ayrshare.com/docs/apis/post/social-networks/overview
Compare the 13 networks the /post endpoint publishes to, with character, image, and video limits for each and an example that posts to several at once.
The Ayrshare [/post endpoint](/docs/apis/post/post) publishes to every social network below from a single API call.
Add one or more networks to the `platforms` array and Ayrshare handles the network-specific formatting, media conversion, and delivery.
Each network supports its own publishing options, media types, and limits. Select a network below for its full reference.
## Supported Social Networks
> [card] [Bluesky](/docs/apis/post/social-networks/bluesky)
>
> Text posts with up to 4 images or 1 video, alt text, and animated GIFs.
> [card] [Facebook](/docs/apis/post/social-networks/facebook)
>
> Page posts, Reels, image carousels, and automatic link previews.
> [card] [Google Business Profile](/docs/apis/post/social-networks/google)
>
> Standard What's New posts, events, and offers, plus single image or video uploads.
> [card] [Instagram](/docs/apis/post/social-networks/instagram)
>
> Feed posts, Reels, Stories, and carousels of up to 10 images or videos.
> [card] [LinkedIn](/docs/apis/post/social-networks/linkedin)
>
> Personal and company pages, documents (PDF/PPT/DOC), and up to 9 images.
> [card] [Pinterest](/docs/apis/post/social-networks/pinterest)
>
> Image and video Pins, carousels of up to 5 images, and board selection.
> [card] [Reddit](/docs/apis/post/social-networks/reddit)
>
> Submit image or text posts to any subreddit you have access to.
> [card] [Snapchat](/docs/apis/post/social-networks/snapchat)
>
> Stories and Spotlight Snaps with a single image or video per post.
> [card] [Telegram](/docs/apis/post/social-networks/telegram)
>
> Channel and group posts with an image or video, animated GIFs, and link previews.
> [card] [Threads](/docs/apis/post/social-networks/threads)
>
> Text posts and carousels of up to 20 combined images or videos.
> [card] [TikTok](/docs/apis/post/social-networks/tiktok)
>
> Direct video publishing plus photo posts of up to 35 images.
> [card] [X (Twitter)](/docs/apis/post/social-networks/x-twitter)
>
> Tweets with up to 4 images or 1 video, long video, and BYO credentials.
> [card] [YouTube](/docs/apis/post/social-networks/youtube)
>
> Publish videos with a title, description, tags, visibility, and playlists.
## Posting at a Glance
A quick reference for the most common publishing limits. See [Character Limits](/docs/help-center/technical-support/character-limits) and [Media Requirements](/docs/media-guidelines/overview) for the complete, per-network reference.
| Network | Post Characters | Max Images | Video |
| - | - | - | - |
| [Bluesky](/docs/apis/post/social-networks/bluesky) | 300 | 4 | 1 |
| [Facebook](/docs/apis/post/social-networks/facebook) | 63,206 | 10 | 1 |
| [Google Business Profile](/docs/apis/post/social-networks/google) | 1,500 | 1 | 1 |
| [Instagram](/docs/apis/post/social-networks/instagram) | 2,200 | 10 | 1 |
| [LinkedIn](/docs/apis/post/social-networks/linkedin) | 3,000 | 9 | 1 |
| [Pinterest](/docs/apis/post/social-networks/pinterest) | 500 | 5 | 1 |
| [Reddit](/docs/apis/post/social-networks/reddit) | 5,000 | 1 | Not yet supported |
| [Snapchat](/docs/apis/post/social-networks/snapchat) | 500 | 1 | 1 |
| [Telegram](/docs/apis/post/social-networks/telegram) | 1,024 | 1 | 1 |
| [Threads](/docs/apis/post/social-networks/threads) | 500 | 20 | 1 |
| [TikTok](/docs/apis/post/social-networks/tiktok) | 2,200 | 35 | 1 |
| [X (Twitter)](/docs/apis/post/social-networks/x-twitter) | 280 (25,000 Premium) | 4 | 1 |
| [YouTube](/docs/apis/post/social-networks/youtube) | 5,000 | Video only | 1 |
> [!NOTE]
>
> Most networks accept **either** multiple images **or** a single video in one post, not both. Instagram and Threads are the
> exceptions: their carousels may combine images and videos (up to 10 and 20 items respectively). See each network's page and the
> [Media Requirements](/docs/media-guidelines/overview) for the exact rules.
## Posting to Multiple Networks
Publish the same content everywhere by listing each network in the `platforms` array. Network-specific options are passed in their
own object (for example, `instagramOptions` or `youTubeOptions`) and are ignored by the other networks.
```json Post to Multiple Networks theme={"system"}
{
"post": "Launch day is here! 🚀",
"platforms": ["bluesky", "facebook", "instagram", "linkedin", "twitter"],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"]
}
```
> [!NOTE]
>
> Each network must first be linked to the user profile. See [Connect Social Accounts](/docs/dashboard/connect-social-accounts/overview)
> to authorize the networks you want to publish to.
# Pinterest API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/pinterest
Create Pinterest image pins, carousels, and video pins with board ID, pin title, pin link, pin note, alternative text, and Pinterest AI disclosures.
## Posting an Image Pin to Pinterest
The Pinterest API requires an image to be included in the post. Other parameters are optional.
Please see [Pinterest Media Guidelines](/docs/media-guidelines/pinterest) and [Pinterest Authorization](/docs/dashboard/connect-social-accounts/pinterest) for more information.
```json Pinterest Post theme={"system"}
{
"post": "The best Pinterest API ever!", // Maximum 500 characters or empty string
"platforms": ["pinterest"],
"mediaUrls": ["https://images.ayrshare.com/imgs/GhostBusters.jpg"]
}
```
## Pinterest Options
You can set additional options for a post by using the `pinterestOptions` parameter.
```json Pinterest Post theme={"system"}
{
"post": "The best Pinterest API ever!", // Maximum 500 characters or empty string
"platforms": ["pinterest"],
"mediaUrls": ["https://images.ayrshare.com/imgs/GhostBusters.jpg"],
"pinterestOptions": {
"altText": "This is my best pic", // maximum 500 characters
"boardId": "93383204522",
"carouselOptions": [
{
"title": "Image 1",
"link": "https://www.cnn.com",
"description": "Super Image 1"
},
{
"title": "Image 2",
"link": "https://www.google.com",
"description": "Super Image 2"
}
],
"link": "https://www.ayrshare.com", // maximum 2048 characters
"note": "Private note", // maximum 1000 characters
"aiDisclosures": ["AI_MODIFIED"],
"thumbNail": "https://images.ayrshare.com/imgs/GhostBusters.jpg",
"title": "A Pinterest Board for You" // maximum 100 characters
}
}
```
Pinterest options are optional fields that can be used to control the post.
**altText** `string`
The alternative text for images and videos for accessibility and screen readers. Maximum 500 characters. If you send an array, only the first item is used.
See [Pinterest Alternative Text](/docs/apis/post/social-networks/pinterest#alternative-text) for more information.
**aiDisclosures** `array of strings`
Declare AI disclosures for the whole Pin. Accepted values are `AI_MODIFIED` and `SYNTHETIC_PERFORMER`.
See [Pinterest AI Disclosures](/docs/apis/post/social-networks/pinterest#pinterest-ai-disclosures) for more information.
**boardId** `string`
Post to another one of the user's Pinterest Boards by specifying the ID from the /user/details endpoint.
If not specified, posts to the default linked board.
See [Pinterest Board ID](/docs/apis/post/social-networks/pinterest#board-id) for more information.
**carouselOptions** `array of objects`
Add up to 5 images as a Pinterest carousel with optional titles, links, and descriptions.
Each object can contain: `title`, `link`, and `description`.
See [Pinterest Image Carousel](/docs/apis/post/social-networks/pinterest#pinterest-image-carousel) for more information.
**link** `string`
The destination URL users will be directed to when clicking the pin image. Maximum 2048 characters.
Creates a clickable link attached to your pin.
See [Pin Link](/docs/apis/post/social-networks/pinterest#pin-link) for more information.
**note** `string`
Add private notes to individual Pins that only you and board collaborators can see. Maximum 1000 characters.
See [Pin Note](/docs/apis/post/social-networks/pinterest#pin-note) for more information.
**thumbNail** `string`
Set a thumbnail for video pins. Must be PNG or JPG, same dimensions as video, under 10MB.
Required for video pins.
See [Video Pin (Thumbnail)](/docs/apis/post/social-networks/pinterest#video-pin-thumbnail) for more information.
**title** `string`
The title of the pin to display in the "Add your title" section. Maximum 100 characters.
See [Pin Title](/docs/apis/post/social-networks/pinterest#pin-title) for more information.
\*Links in the `post` field are not clickable. Please use the `link` field to add a
clickable link to the Pin.
See [Pinterest Media Guidelines](/docs/media-guidelines/pinterest) for more information.
## Alternative Text
Add alternative text, also known as alt text, to a Pinterest image or video. Pinterest alt text is an accessibility feature used for additional user info and screen readers.
Use the `altText` in the `pinterestOptions` object.
```json Pinterest Alt Text theme={"system"}
{
"pinterestOptions": {
"altText": "This is my best pic"
}
}
```
The alt text is limited to 500 characters.
## Pinterest AI Disclosures
Use `pinterestOptions.aiDisclosures` to send Pinterest's AI disclosure declarations when creating a Pin. The disclosures apply to the whole Pin, including image carousels, and are supported for image Pins, carousel Pins, and video Pins.
```json Pinterest AI Disclosure theme={"system"}
{
"post": "An AI-assisted concept for a garden studio",
"platforms": ["pinterest"],
"mediaUrls": ["https://example.com/garden-studio.jpg"],
"pinterestOptions": {
"aiDisclosures": ["AI_MODIFIED"]
}
}
```
You can send both accepted values together:
```json Pinterest AI Disclosure theme={"system"}
{
"pinterestOptions": {
"aiDisclosures": ["AI_MODIFIED", "SYNTHETIC_PERFORMER"]
}
}
```
Accepted values:
`AI_MODIFIED`: The Pin contains AI-modified content.
`SYNTHETIC_PERFORMER`: The Pin contains a synthetic performer.
If `aiDisclosures` is omitted or an empty array, Ayrshare does not send Pinterest an AI disclosure field. Duplicate values are sent once. Values are case-sensitive and are not coerced.
If the option is invalid, for example an unknown value, wrong casing, a non-array value, or an array containing a non-string entry, the Pin can still publish successfully, but Ayrshare omits the entire disclosure option and returns a nonfatal warning on the Pinterest result. Correct the request and send one or both accepted values if the Pin needs an explicit AI disclosure. Pinterest may still apply its own labels independently.
The MCP server and the GraphQL API check value types first. On those interfaces, a malformed value such as a list containing a number is rejected before posting, instead of publishing with a warning. Unknown or wrongly cased strings still publish with the warning.
## Board ID
Post to another one of the user's Pinterest Boards by specifying the ID obtained from the [/user/details](/docs/apis/user/pinterest-board) endpoint. Otherwise post to the default linked board.
Use the `boardId` in the `pinterestOptions` object.
```json Pinterest Board ID theme={"system"}
{
"pinterestOptions": {
"boardId": "93383204522"
}
}
```
## Character Limits
Please see [Pinterest Character Limits](/docs/help-center/technical-support/character-limits#pinterest-character-limits) for more information.
## Pin Link
The destination URL that users will be directed to when they click on your pin image. This creates a clickable link attached to your pin. Maximum length is 2048 characters.
Use the `link` in the `pinterestOptions` object.
```json Pin Link theme={"system"}
{
"pinterestOptions": {
"link": "https://www.ayrshare.com"
}
}
```
## Pin Note
You can add private notes to individual Pins on your boards. Only you and board collaborators will be able to see them.
Use the `note` in the `pinterestOptions` object.
```json Pin Note theme={"system"}
{
"pinterestOptions": {
"note": "Private note for collaborators"
}
}
```
## Pinterest Image Carousel
Post up to five images as a Pinterest carousel. By adding more than one media URL, a carousel is automatically created. You can also add in optional carousel parameter. Please see below.
```json Pinterest Carousel Post theme={"system"}
{
"post": "Carousel Time",
"platforms": ["pinterest"],
"mediaUrls": [
"https://img.ayrshare.com/random/photo-1.jpg",
"https://img.ayrshare.com/random/photo-2.jpg"
],
"pinterestOptions": {
"carouselOptions": [
// optional
{
"title": "Image 1",
"link": "https://www.cnn.com",
"description": "Super Image 1"
},
{
"title": "Image 2",
"link": "https://www.google.com",
"description": "Super Image 2"
}
]
}
}
```
The optional `carouselOptions` field takes an array of objects. Each carousel object corresponds to the equivalent media URL string, e.g. the first carousel object refers to the first `mediaUrl` string.
`title`: The image title.
`link`: The external destination link for the image.
`description`: The image description.
## Pinterest Mentions
While you can add a @handle to a Pinterest post, Pinterest does not support resolving mentions in the post text.
The @handle will remain as plain text.
## Pin Title
The title of the pin to display. This appears in the "Add your title" section of a new pin. Maximum 100 characters.
Use the `title` in the `pinterestOptions` object.
```json Pin Title theme={"system"}
{
"pinterestOptions": {
"title": "A Pinterest Board for You"
}
}
```
## Video Pin (Thumbnail)
A video may be posted as a Pin using the standard `mediaUrls` field in the [/post endpoint](/docs/apis/post/post). Additionally, a thumbnail image URL **must be included** with the video with a valid media Content-Type such as `image/jpeg`.
Please see [requirements](/docs/media-guidelines/pinterest).
```json Video Pin with Thumbnail theme={"system"}
{
"pinterestOptions": {
// required
"thumbNail": "https://images.unsplash.com/photo-1513093496871-0a81425386e5?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxNjQ1ODN8MHwxfHJhbmRvbXx8fHx8fHx8fDE2Mzg0ODAzNDA&ixlib=rb-1.2.1&q=80&w=400"
}
}
```
Additional information on [posting Pins using the API](https://www.ayrshare.com/blog/pinterest-api-integration-on-ayrshare/).
If your video doesn't end in a known video extension such as mp4, please use the `isVideo` parameter. See the [/post endpoint](/docs/apis/post/post) for details.
# Reddit API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/reddit
Post text, links, and images to a subreddit with the Reddit API, add Reddit flair, check if a subreddit exists, add rich text, and use Reddit mentions.
## Posting to Reddit
The following are required body parameters for posting using the Reddit API. Please be sure the subreddit allows automated posting.
Please see [Reddit Media Guidelines](/docs/media-guidelines/reddit) and [Reddit Authorization](/docs/dashboard/connect-social-accounts/reddit) for more information.
```json Reddit Post theme={"system"}
{
"post": "Reddit post", // empty string is allowed
"platforms": ["reddit"], // required
"redditOptions": {
"title": "Reddit Post Title", // required
"subreddit": "test", // required (no "/r/" needed)
"link": "https://www.website.com" // optional: post a link
}
}
```
`title` (required): The title of the post on Reddit.
`subreddit` (required): The subreddit to send the post. Please be sure to follow the subreddit's
posting guidelines. A good subreddit to use for testing is */r/test/* and can be viewed at:
[https://www.reddit.com/r/test/](https://www.reddit.com/r/test/)
`link` (optional): If posting to Reddit and you want to post a link instead of text to the
subreddit. While the `post` parameter text is still required, it is not included in the post
since Reddit links can't have a text body.
> [!WARNING]
>
> Always check the rules of the subreddit before posting. Some subreddits have a karma threshold,
> require membership for a time period, limit the frequency of posting, don't allow images, or
> require flair to be added. Also, most subreddits have specific rules on spam and self-promotion.
> Not following these rules can result in being banned by the subreddit or Reddit.
## Posting an Image to Reddit
Post an image to Reddit either by including the image URL in the `mediaUrl` parameter or the `link` parameter of `redditOptions`. Images posted as a `link` will resolve to the full image.
The `post` parameter text is ignored, but is still required to successfully submit the post. Reddit restricts posting images and text together to their online "fancy pants" editor. This means either an image or text can be posted, but not both at the same time.
```json Reddit Post with Image theme={"system"}
{
"post": "Reddit post", // required, but not added to the post
"platforms": ["reddit"], // required
"mediaUrls": "https://my-media-url.com",
"redditOptions": {
"title": "Reddit Post Title", // required
"subreddit": "test" // required (no "/r/" needed)
}
}
```
## Add Reddit Flair
Some subreddits require flair. Subreddit moderators pre-define the flair and each flair has a unique ID. In the Reddit post to Ayrshare add the flair ID and an optional flair text if the text can be overridden.
```json Reddit Post with Flair theme={"system"}
{
"redditOptions": {
"flairId": "jM8nH92enjswas", // Id of the flair
"flairText": "My New Flair text" // Override the flair text if allowed by flair
}
}
```
You can find the flair ID of a subreddit by calling the [/post/redditFlair](/docs/apis/utils/reddit-get-flair) endpoint.
## Check if a Subreddit Exists
Check if a subreddit exists with the [/validate endpoint](/docs/apis/validate/check-subreddit).
## Add rich text formatting to Reddit posts
If you want to add bold, italic, or superscript text to a Reddit post, use [Reddit-flavored Markdown](https://www.reddit.com/wiki/markdown#wiki_new_reddit-flavored_markdown).
```json Reddit Post with Rich Text theme={"system"}
{
"post": "For example, this can be *italic* or **bold** or ^super ",
"platforms": ["reddit"],
"redditOptions": {
"title": "Rich Text Post",
"subreddit": "test"
}
}
```
## Reddit Mentions
Mention another Reddit user or subreddit.
Mention a Reddit user by adding `@subreddit` or `u/username` in the post text.
Mention a subreddit by adding `r/subreddit` in the post text.
For example:
```json Reddit Post with Mention theme={"system"}
{
"post": "The best Reddit post ever for user @ayrshare and subreddit r/SiliconValleyHBO",
"platforms": ["reddit"]
}
```
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
## Character Limits
Please see [Reddit Character Limits](/docs/help-center/technical-support/character-limits#reddit-character-limits) for more information.
## Additional Endpoints
> [card] [Get Reddit Flair](/docs/apis/utils/reddit-get-flair)
# Snapchat API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/snapchat
Publish to Snapchat Stories, Saved Stories, and Spotlight with the Snapchat API, plus how Snapchat mentions, hashtags, and character limits are handled.
> [!NOTE]
>
> When Ayrshare converts a WebP, HEIC, HEIF, or AVIF image to JPEG for Snapchat it preserves the image's color rendering and writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI disclosure, when the image declares one. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic signature
> does not survive a re-encode. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
## Posting to Snapchat
The Snapchat API enables direct publishing of content to both Stories and Spotlight.
Stories are temporary posts that disappear after 24 hours, while Spotlight posts are permanent and can help creators and businesses reach a wider audience.
There are three types of posts you can create on Snapchat:
Snapchat accepts either video or image media. Only one media item is allowed per post.
You can learn more using Snapchat's API:
[Snapchat Media Guidelines](/docs/media-guidelines/snapchat) and [Snapchat Authorization](/docs/dashboard/connect-social-accounts/snapchat).
[Snapchat API integration guide](https://www.ayrshare.com/blog/complete-guide-to-snapchat-api-integration/) to learn more about how to post or get analytics on Snapchat.
## Stories
Stories are ephemeral Snaps viewable by friends, subscribers, and non-subscribers in the Snapchat app's 4th tab Discover feed for up to 24-hours.
Publish a Snapchat story as you would any other post:
```json Publish a Snapchat story theme={"system"}
{
"post": "An amazing story", // empty string is allowed
"platforms": ["snapchat"],
"mediaUrl": ["https://img.ayrshare.com/video.mp4"]
}
```
## Saved Stories
Saved Stories are permanent Stories that are viewable on a Public Profile indefinitely.
Publish a Snapchat story with `snapChatOptions` and the `savedStory` option:
```json Publish a Snapchat saved story theme={"system"}
{
"post": "An amazing saved story",
"platforms": ["snapchat"],
"mediaUrl": ["https://img.ayrshare.com/video.mp4"],
"snapChatOptions": {
"savedStory": true
}
}
```
## Spotlight
Spotlights are permanent video Snaps that are distributed and viewable on Spotlight, Snapchat's entertainment platform for user-generated content.
Spotlight offers a channel for creators and businesses to grow their audience on Snapchat.
Publish a Snapchat story with `snapChatOptions` and the `spotlight` option:
```json Publish a Snapchat spotlight theme={"system"}
{
"post": "An amazing spotlight",
"platforms": ["snapchat"],
"mediaUrl": ["https://img.ayrshare.com/video.mp4"],
"snapChatOptions": {
"spotlight": true
}
}
```
## Snapchat Mentions
Currently, user mentions (@username) in Spotlight posts don't resolve as interactive mentions through the API. The text will appear as plain text in your Spotlight posts rather than creating clickable user links.
```json Example with mentions (displays as plain text) theme={"system"}
{
"post": "Check out this awesome content from @johndoe and @janedoe!",
"platforms": ["snapchat"],
"mediaUrl": ["https://img.ayrshare.com/image.jpg"],
"snapChatOptions": {
"spotlight": true
}
}
```
## Snapchat Hashtags
Hashtags are fully supported in Spotlight posts and help increase discoverability. You can include multiple hashtags in your post content to categorize your content and reach relevant audiences. Hashtags are also clickable.
```json Example with hashtags theme={"system"}
{
"post": "Just launched our new product! 🚀 #ayrshare #tech #exciting",
"platforms": ["snapchat"],
"mediaUrl": ["https://img.ayrshare.com/product-launch.mp4"],
"snapChatOptions": {
"spotlight": true
}
}
```
## Character Limits
Please see [Snapchat Character Limits](/docs/help-center/technical-support/character-limits#snapchat-character-limits) for more information.
# Telegram API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/telegram
Send Telegram posts with links, images, and animated GIFs using the Telegram API, plus how Telegram mentions work and the character limits for each post.
## Posting to Telegram
JSON for a basic post with a link and image using the Telegram API:
```json Telegram Post theme={"system"}
{
"post": "The best Telegram message ever #best https://www.telegram.com", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["telegram"]
}
```
Telegram will automatically preview the link in the channel or group unless there is an image or video included. In the above example the image will show. Removing the image will cause the link preview to show.
If your video doesn't end in a known video extension such as mp4, please use the `isVideo` parameter. See the [/post endpoint](/docs/apis/post/post) for details.
See [Telegram Media Guidelines](/docs/media-guidelines/telegram) and [Telegram Authorization](/docs/dashboard/connect-social-accounts/telegram) for more information.
## Animated GIFs
Only one URL is allowed with a Telegram animated GIF. If the media URL does not end in ".gif" or ".GIF", set the `isVideo` field to `true`.
```json Telegram Animated GIF theme={"system"}
{
"randomPost": true,
"platforms": [
"telegram"
],
"isVideo": true, // Set to true if the mediaURL does not end in .gif or .GIF
"mediaUrls": ["https://img.ayrshare.com/012/cat.gif"]
}
```
## Telegram Mentions
Mention another Telegram handle by adding `@handle` in the post text. For example:
```json Telegram Post with Mention theme={"system"}
{
"post": "The best Telegram image post ever @handle",
"platforms": ["telegram"]
}
```
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
## Character Limits
Please see [Telegram Character Limits](/docs/help-center/technical-support/character-limits#telegram-character-limits) for more information.
# Threads API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/threads
Post to Threads with image and video carousels, geo restrictions, Threads mentions, and multi-post threads with thread media, thread breaks, and deletion.
> [!NOTE]
>
> Threads publishing fetches media from your URL via Meta's server-side crawler.
> If you see Ayrshare error code 379 — especially alongside an Instagram error 440 or 138 in the same
> publish — see [Meta Media Crawler Blocked](/docs/help-center/technical-support/meta-media-crawler-blocked).
> [!NOTE]
>
> Posting an AI-generated image? When Ayrshare converts a WebP, HEIC, HEIF, or AVIF image to JPEG it
> writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI disclosure, when the image declares one. Threads stores the disclosure but does not render an AI label
> today. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic signature does not survive a re-encode. See [Image
> Metadata and Content Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
## Posting to Threads
JSON for a basic post with a link and image using the Threads API:
```json Threads Post theme={"system"}
{
"post": "The best Threads post ever #best #awesome https://www.threads.net", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["threads"]
}
```
The Threads API has the following requirements and restrictions:
Threads will automatically preview the link in the post unless there is an image or video
included. In the above example the image will show. Removing the image will cause the link
preview to show.
Threads profiles are limited to 250 API-published posts within a 24-hour moving period.
Threads only allows 1 hashtag per post.
@mention Threads users will receive a notification.
Maximum 500 post characters.
Multi-image/videos posts are supported and sent as a carousel. You may send up to 20 videos and
images.
Published Threads posts can be deleted with the [delete endpoint](/docs/apis/post/delete-post).
If your video doesn't end in a known video extension such as mp4, please use the `isVideo`
parameter. See the [/post endpoint](/docs/apis/post/post) for details.
Threads also supports sending media without post text. If you do not want post text included
send an empty String `post: ""`.
See [Threads Media Guidelines](/docs/media-guidelines/threads) and [Threads
Authorization](/docs/dashboard/connect-social-accounts/threads) for more information.
## Threads Options
You can set additional options for a post by using the `threadsOptions` parameter.
```json Threads Options theme={"system"}
{
"threadsOptions": {
"allowCountries": ["US", "CA"],
"thread": true,
"threadNumber": true,
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"] // used when sending as a thread of threads
}
}
```
Threads options are optional fields that can be used to control the post.
**allowCountries** `array of strings`
Restrict posts to specific countries using country codes. Use [country codes](/docs/iso-codes/country).
Only available if Meta has enabled geo restrictions for your account.
See [Threads Geo Restrictions](/docs/apis/post/social-networks/threads#geo-restrictions) for more information.
**thread** `boolean` (default: false)
Break long posts into a connected thread series with optional numbering and media.
See [Thread](/docs/apis/post/social-networks/threads#thread) for more information.
**threadNumber** `boolean` (default: false)
Automatically add numbers at the end of each thread in the format of 1/n.
Requires `thread: true`.
**mediaUrls** `array of strings`
Add media objects to a thread of threads. One media object will be added to each thread in order.
Use `null` to skip media for a specific thread. Use objects with multiple URLs for multiple media per thread.
See [Thread Media](/docs/apis/post/social-networks/threads#thread-media) for more information.
## Adding line breaks or rich text to a Threads post
Threads line breaks can be added to a post with a special [new line character](/docs/apis/post/overview#line-breaks).
Rich text, such as bold or italic lettering, can be added to a Threads post with a few [html elements](/docs/apis/post/overview#rich-text-posts).
## Carousel of Images and Videos
You can post multiple images or videos to Threads as a carousel; up to a combined total of 20 images or videos may be use in a carousel. Just add your additional images or videos to the `mediaUrls` array and the carousel will automatically be created.
```json theme={"system"}
"mediaUrls": ["https://url.com/image.jpg", "https://url.com/video.mp4" ...]; // Max 20 images or videos
```
Video URLs must end in a known extension such as mp4.
## Character Limits
Please see [Threads Character Limits](/docs/help-center/technical-support/character-limits#threads-character-limits) for more information.
## Geo Restrictions
You can restrict a post to a specific country or countries by using the `allowCountries` array.
```json theme={"system"}
{
"threadsOptions": {
"allowCountries": ["US", "CA"]
}
}
```
`allowCountries`: An array of country codes to allow. See [country codes](/docs/iso-codes/country).
> [!WARNING]
>
> Geo restrictions (geo-gating) on Threads are only available if Meta has enabled this feature for your account.
>
> Meta decides which Threads accounts are eligible for geo restrictions based on factors such as account verification, follower count, or content creator status.
> If your account is eligible, you will see the geographic settings (globe icon) in the post composer when creating a new Threads post.
>
> You can check if a user profile is eligible for Threads geo restrictions by checking the `isEligibleForGeoRestrictions` property in the [/user](/docs/apis/user/profile-details) endpoint.
### Geo Restrictions on a Thread
`allowCountries` now applies to a [thread](/docs/apis/post/social-networks/threads#thread) as well as to a single post.
If the account is **not** eligible for geo restrictions, sending `allowCountries` with `thread: true` returns `code: 101` and **no part of the thread is published** — the same result you already get for a single post with that option.
> [!WARNING]
>
> This is a change in behavior. Previously a thread with `allowCountries` published normally on
> an ineligible account and the country restriction was silently dropped, so posts you intended
> to restrict went out unrestricted with no indication. If you send `allowCountries` on threads
> today from an ineligible account, those threads will now fail instead of publishing.
>
> Check `isEligibleForGeoRestrictions` on [/user](/docs/apis/user/profile-details) before sending
> `allowCountries`, or remove the option to publish the thread without country restrictions.
## Threads Mentions
Mention another Threads handle by adding @handle in the post text. For example:
```json theme={"system"}
{
"post": "The best social media API @Ayrshare ever!",
"mediaUrls": ["https://images.com"],
"platforms": ["threads"]
}
```
> [!WARNING]
>
> The @mentioned user will be notified of the mention. Please review the [important
> rules](/docs/testing/post-verification#mentions) on mentions.
If the quota has been reached, an error message will be returned.
## Thread
A Threads thread, also known as a threadstorm, is a connected series of posts on Threads that allows you to share longer ideas beyond a single post's character limit, appearing as one continuous narrative when viewed together.
### Posting a Thread
A Threads thread can be posted via the API. A thread is a post broken up into a set of reply posts associated together in Threads. You can either automatically break up the post or specify the [thread breaks](/docs/apis/post/social-networks/threads#thread-breaks) in the post text.
```json Threads Thread theme={"system"}
{
"threadsOptions": {
"thread": true, // required for threadstorm
"threadNumber": true, // optional to add numbers to each thread
"mediaUrls": ["https://site.com/image1.png", "https://site.com/image2.png", ...] // optional one media object is added to a thread in order
}
}
```
`thread: true` to automatically break apart the post text into threads based on line breaks.
`threadNumber: true` to automatically add numbers at the end of threads in the format of 1/n.
For example, the 2nd of 5 threads will have appended: 2/5
`mediaUrls: [array of urls]` to add each media object, an image or video, to a thread in order.
Only one media object will be added to a thread in order.
#### Thread Media
##### Skip Media
Skip media for a thread by using `null` in the array. For example:
`["https://site.com/image1.png", null, "https://site.com/image2.png"]`
This will place image1 on the first post, no image on the second post, and image2 on the third post.
##### Multiple Media
Multiple media objects can be added to a single post within a thread by adding an object `{}` with the media URLs in the `mediaUrls` array. Any unique object keys can be used. For example:
```json Threads Thread with Multiple Media URLs theme={"system"}
{
"threadsOptions": {
"thread": true,
"threadNumber": true,
"mediaUrls": [
"https://img.ayrshare.com/random/photo-1.jpg",
{
"1": "https://img.ayrshare.com/random/photo-2.jpg",
"2": "https://img.ayrshare.com/random/photo-3.jpg"
},
"https://img.ayrshare.com/random/photo-4.jpg"
]
}
}
```
In this example, the first post will contain photo-1.jpg, the second post photo-2.jpg and photo-3.jpg, and the third post photo-4.jpg.
#### Thread Breaks
Ayrshare automatically breaks up the post text into appropriate length posts (> 500 characters) for Threads. When creating threads, we prioritize keeping complete sentences in one post when possible. If a sentence won't fit, we split between sentences. For very long sentences, we split between words. In rare cases where a word is too long, we split the word itself.
You may also manually add paragraphs with `\n\n` to the post text to indicate a unique thread should be created. If you have `\n\n` in the post text, we will not automatically break the post into threads.
For example:
```json Example Threads Thread theme={"system"}
{
"post": "This is post 1\n\nThis is post 2.",
"platforms": ["threads"],
"threadsOptions": {
"thread": true
}
}
```
will result in two posts in the thread.
If you want to add paragraphs, but not break into posts, use `\u2063\n\u2063\n`.
```json Threads Thread with Paragraphs theme={"system"}
{
"post": "This is paragraph 1\u2063\n\u2063\nThis is paragraph 2.",
"platforms": ["threads"],
"threadsOptions": {
"thread": true
}
}
```
will result in one post with two paragraphs since the post is below 500 characters.
### Delete a Thread
Delete a published Threads post with the [delete endpoint](/docs/apis/post/delete-post), using the Ayrshare post `id`.
# TikTok API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/tiktok
Publish TikTok video and image posts, handle TikTok processing, and set visibility options, video thumbnails, mentions, and drafts with the TikTok API.
> [!NOTE]
>
> Ayrshare offers [direct publishing of TikTok videos](https://www.ayrshare.com/blog/introducing-tiktok-direct-publishing-analytics-and-commenting/), comment management, and retrieval of advanced analytics for either your personal or business TikTok account using the TikTok API.
>
> TikTok asynchronously processes video and photos, so the JSON response will be status: "pending" for both immediate and scheduled posts. Once TikTok has completed processing, your registered [Scheduled Action webhook](/docs/apis/webhooks/actions#scheduled-action) will be called.
> [!NOTE]
>
> When Ayrshare converts a WebP, HEIC, HEIF, or AVIF image to JPEG for a photo post it preserves the image's color rendering and writes a small XMP packet carrying only the `Iptc4xmpExt:DigitalSourceType` AI disclosure, when the image declares one. No other metadata is carried over: not EXIF, and nothing else from the original XMP, so a location, a creator name or a copyright line can't be published by accident. The C2PA cryptographic signature
> does not survive a re-encode. This is independent of the video-only `isAIGenerated`
> toggle documented below. See [Image Metadata and Content
> Credentials](/docs/media-guidelines/overview#image-metadata-and-content-credentials).
## TikTok Video Post
JSON for a basic TikTok video post that is directly published:
```json TikTok Video Post theme={"system"}
{
"post": "The best TikTok \n video ever #bestvideo", // Max 2,200 characters with a line break
"mediaUrls": ["https://img.ayrshare.com/012/tiktok.mp4"],
"platforms": ["tiktok"]
}
```
Example JSON post response:
```json TikTok Video Post Response theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"idShare": "video.7088122496758679353.nzLqBWbf",
"id": "pending",
"isVideo": true,
"platform": "tiktok"
}
],
"id": "lb42orDhySAZmLWtj6b6",
"refId": "23a9da9e0df1184a7a6a1fc2c60b8023aa9a32a1",
"post": "The best TikTok video ever #bestvideo"
}
```
TikTok does not currently support line breaks in the post text. Included line breaks will be
ignored.
Either one video or up to 35 images may be published. TikTok does not support a combination of
video and images. Please see below for more details.
If the video does not end in a known extension, use
[isVideo](/docs/apis/post/overview#video-extension).
TikTok also supports sending media without post text. If you do not want post text included send
an empty String `post: ""`.
See [TikTok Media Guidelines](/docs/media-guidelines/tiktok) and [TikTok
Authorization](/docs/dashboard/connect-social-accounts/tiktok) for more information.
### TikTok Video Requirements
Please see [TikTok Video Requirements](/docs/media-guidelines/tiktok#video).
The video must end in a known video extension such as mp4. Please either reverse proxy the URL,
add on a [vanity URL with a
CDN](https://www.ayrshare.com/blog/how-to-put-a-cdn-in-front-of-firebase-cloud-storage/), or use the
[/media](/docs/apis/media/overview) endpoint.
TikTok's post text character limit is 2,200.
> [!NOTE]
>
> TikTok limits the API video publishing to 6 videos per minute with an upper limit of 15 videos per
> day.
## TikTok Image Post
JSON for a basic TikTok image (photo) post that is directly published:
```json TikTok Image Post theme={"system"}
{
"post": "The best TikTok \n video ever #bestvideo", // Max 2,200 characters with a line break
"mediaUrls": [
"https://img.ayrshare.com/012/gb.jpg",
"https://img.ayrshare.com/random/photo-1.jpg"
], // Up to 35 images
"platforms": ["tiktok"]
}
```
Example JSON response:
```json TikTok Image Post Response theme={"system"}
{
"status": "success",
"errors": [],
"postIds": [
{
"status": "success",
"idShare": "p_pub_url~v2.7408974036430047275",
"id": "pending",
"isVideo": false,
"platform": "tiktok"
}
],
"id": "8815mJ5bWApEebWjE233",
"tikTokId": "p_pub_url~v2.7408974036430047333",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc333",
"post": "Opportunity is missed by most people because it is dressed in overalls and looks like work - Thomas Edison"
}
```
TikTok does not currently support line breaks in the post text. Included line breaks will be
ignored.
Either one video or up to 35 images may be published.
TikTok does not support a combination of video and images. Please see below for more details.
The images must be of type JPG, JPEG, or WEBP. TikTok does not accept PNG media files.
You may also select one of the images as the cover photo with the `imageCoverIndex`. By default,
the first image is used. Please see below for details.
### TikTok Image Requirements
Please see [TikTok Image Requirements](/docs/media-guidelines/tiktok#images).
Up to 35 images may be included in a post, at 20 MB per image.
The images must be of type JPG, JPEG, or WEBP. TikTok does not accept PNG media files.
TikTok's post text character limit is 2,200.
> [!NOTE]
>
> TikTok limits the API video publishing to 6 photos per minute with an upper limit of 15 photos per
> day.
## TikTok Processing
TikTok does asynchronous processing of videos and images, so the response will have the `id` field set to `"pending"`.
After TikTok completes their processing, usually within 1 - 2 minutes, the `id` field will be updated with the TikTok video `id` and a `postUrl` will be added.
You can retrieve the final status of the TikTok post using
[webhooks](/docs/apis/webhooks/actions#tiktok-publishing-webhook) or the
[/history](/docs/apis/history/overview) endpoint and usually takes up to 1-2 minutes to be available.
When the user publishes the video in the TikTok mobile app, a "scheduled" webhook will be sent
with the `subAction: "tikTokPublished"`.
A [first comment](/docs/apis/post/overview#first-comment) on a TikTok post is deferred: it is posted
automatically once the `tikTokPublished` webhook resolves the real video `id`, not at publish
time. The video `visibility` must be `public`, otherwise the first comment cannot be posted and a
comment error is returned.
If an error occurs, such as TikTok was unable to process the video or Ayrshare internal tests
failed, the `id` field will be set to "failed" and the `errors` field will contain the error
details.
The `idShare` is used for internal referencing the pending video.
## TikTok Options
When publishing TikTok videos or images, [additional options](/docs/apis/post/social-networks/tiktok#options) are available.
Video Publishing Example:
```json TikTok Video Publishing theme={"system"}
{
"tikTokOptions": {
"disableComments": true, // Default false. Disable comments on the published video.
"disableDuet": true, // Default false. Disable duets on the published video.
"disableStitch": true // Default false. Disable stitches on the published video.
}
}
```
Image Publishing Example:
```json TikTok Image Publishing theme={"system"}
{
"tikTokOptions": {
"imageCoverIndex": 1, // Use the second image in the mediaUrls.
"title": "Amazing images"
}
}
```
### Options
The following options are available for TikTok posts.
They should be added to the `tikTokOptions` object.
Please see below for more details on each option.
```json TikTok Options theme={"system"}
{
"post": "The best TikTok video ever #bestvideo",
"mediaUrls": ["https://img.ayrshare.com/012/tiktok.mp4"],
"platforms": ["tiktok"],
"tikTokOptions": {
"autoAddMusic": true,
"disableComments": true,
"disableDuet": true,
"disableStitch": true,
"draft": true,
"isAIGenerated": true,
"isBrandedContent": true,
"isBrandOrganic": true,
"imageCoverIndex": 1,
"title": "Amazing images",
"thumbNailOffset": 30000,
"visibility": "public"
}
}
```
**autoAddMusic** `boolean` (default: false)
Whether to automatically add recommended music to the post.
If you set this field to `true`, you can change the music later in the TikTok app.
Media type: image
**disableComments** `boolean` (default: false)
Whether to disable comments on the published post.
Media type: video, image
**disableDuet** `boolean` (default: false)
Disable duets on the published video.
Media type: video
**disableStitch** `boolean` (default: false)
Disable stitch on the published video.
Media type: video
**draft** `boolean` (default: false)
Whether to create a draft post.
See [draft options](/docs/apis/post/social-networks/tiktok#tiktok-draft-post) for more information.
Media type: video or image
**isAIGenerated** `boolean` (default: false)
Whether to enable the AI-generated content toggle for the video post.
If you enable the toggle, your video will be labeled as "Creator labeled as AI-generated" once posted and can't be changed.
The "Creator labeled as AI-generated" label indicates that the content was completely AI-generated or significantly edited with AI.
> [!NOTE]
>
> Turning on the AI-generated content setting won't affect the distribution of your video as long as
> it doesn't violate TikTok's [Community
> Guidelines](https://www.tiktok.com/community-guidelines/en/).
Media type: video
**isBrandedContent** `boolean` (default: false)
Whether to enable the [Branded Content](https://creatormarketplace.tiktok.com/help#/doc/9493/10008169) toggle. If this field is set to `true`, the video will be labeled as Branded Content, indicating you are in a paid partnership with a brand. A "Paid partnership" label will be attached to the video.
Media type: video, image
**isBrandOrganic** `boolean` (default: false)
Whether to enable the Brand Organic Content toggle. If this field is set to `true`, the video will be labeled as Brand Organic Content, indicating you are promoting yourself or your own business. A "Promotional content" label will be attached to the video.
Media type: video, image
**imageCoverIndex** `number` (default: 0)
The index of the `mediaUrls` to be used as the cover for the post.
Media type: image
**title** `string`
The title of the post.
Media type: image
**thumbNailOffset** `number`
The frame to use for the video cover.
See [video thumbnail options](/docs/apis/post/social-networks/tiktok#video-thumbnail) for more information.
Media type: video
**visibility** `string` (default: public)
How the post is shared and who can see it.
Values: `public`, `private`, `followers`, or `friends`.
See [visibility options](/docs/apis/post/social-networks/tiktok#visibility-options) for more information.
Media type: video, image
### Visibility Options
| Visibility | Description |
| :- | :- |
| public | Visible to all TikTok users. |
| private | Private, only visible to the account itself. |
| followers | Only visible to followers of the account. Requires a private TikTok account. |
| friends | Only visible to mutual followers. |
> [!WARNING]
>
> TikTok only offers the `followers` option on private TikTok accounts. If the
> account is public, TikTok accepts the post without an error and publishes it
> visible to everyone. TikTok Business accounts are always public, so they can't
> use `followers`. Switching an account from private to public also changes
> existing followers-only posts to be visible to everyone.
Private posts will remain in a status `pending` and no TikTok webhook will be sent until the post is made public.
## Video Thumbnail
There are two ways to set a thumbnail, also known as a cover photo, for a TikTok video:
1. Using the `thumbNailOffset` parameter to set a thumbnail frame.
2. Using the `thumbNail` parameter to set a thumbnail image from a URL.
Only videos are supported for setting a thumbnail.
### Thumbnail Offset
Set a thumbnail for a TikTok video by selecting an offset frame.
```json TikTok Video Thumbnail Offset theme={"system"}
{
"tikTokOptions": {
"thumbNailOffset": 30000 // milliseconds of offset image
}
}
```
The offset is the location in milliseconds of the thumbnail frame. Default value is `0`, which is the first frame of the video.
### Thumbnail URL
Set a thumbnail for a TikTok video by uploading an image from a URL.
```json TikTok Video Thumbnail URL theme={"system"}
{
"tikTokOptions": {
"thumbNail": "https://img.ayrshare.com/012/gb.jpg"
}
}
```
If you use the `thumbNail` parameter, the `thumbNailOffset` parameter will be ignored.
Please see [TikTok Thumbnail Requirements](/docs/media-guidelines/tiktok#video-thumbnail) for more information.
## TikTok Mentions
Mention another TikTok handle by adding `@handle` in the post text. For example:
```json TikTok Mention theme={"system"}
{
"post": "Love the @ayrshare social media api"
}
```
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
## TikTok Draft Post
Create a draft post for a TikTok video or image, allowing you to edit the video or image before publishing.
```json TikTok Video Draft Post theme={"system"}
{
"post": "The best TikTok video ever #bestvideo", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/tiktok.mp4"],
"platforms": ["tiktok"],
"tikTokOptions": {
"draft": true
}
}
```
The draft video or image post will be found under the notifications **Inbox** in bottom row of the TikTok app.
Look for a **System notifications** message and then click the top **Your content from Ayrshare is ready** message.
The Ayrshare `postUrl` will remain in `pending` status until the video is published. First comment not supported on draft posts.

## Authorization Refresh
TikTok must be reauthorized *every year* via the Social Accounts page.
An email and webhook social action notification will be sent 15 days in advance of authorization
expiration.
The refresh required date and remaining days can be retrieved from the
[/user](/docs/apis/user/overview) endpoint.
## Character Limits
Please see [TikTok Character Limits](/docs/help-center/technical-support/character-limits#tiktok-character-limits) for more information.
## Additional Information
Additional [examples on using the TikTok API](https://www.ayrshare.com/blog/tiktok-api-how-to-post-to-tiktok-using-a-social-media-api/).
# X API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/x-twitter
Post to X with your own API keys, using polls, quote tweets, reply settings, long posts and videos, alt text, geo restrictions, subtitles, and threads.
## X BYO API Keys
> [!NOTE]
>
> Starting March 31, 2026, all X/Twitter operations require your own API credentials. After linking your X account via OAuth, include these 2 headers in every request that targets X:
>
> | Header | Value |
> | - | - |
> | `X-Twitter-OAuth1-Api-Key` | Your API Key (Consumer Key) |
> | `X-Twitter-OAuth1-Api-Secret` | Your API Key Secret (Consumer Secret) |
>
> Not linked yet? See the [X BYO Key Setup Guide](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys) to connect your X account.
> [!NOTE]
>
> Ayrshare no longer imposes its own monthly or daily X/Twitter rate limits. Because X requests use
> your own credentials (BYO), your usage is governed solely by your own X Developer App limits. See
> the [X API rate limits
> documentation](https://developer.x.com/en/docs/x-api/rate-limits) for details.
## Posting to X (Twitter)
JSON for a basic post with a link and image using the X API, formerly known as the Twitter API:
```json X Post theme={"system"}
{
"post": "The best Tweet ever #best https://www.twitter.com", // empty string is allowed
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
"platforms": ["twitter"]
}
```
X will automatically preview the link in the Tweet unless there is an image or video included.
In the above example the image will show. Removing the image will cause the link preview to
show.
If your video doesn't end in a known video extension such as mp4, please use the `isVideo`
parameter. See the [/post endpoint](/docs/apis/post/post) for details.
X also supports sending media without post text. If you do not want post text included send an
empty String `post: ""`.
You may upload up to 4 images or videos in a single Tweet. Please see the important guidelines
and restrictions [posting Twitter videos](/docs/media-guidelines/x-twitter).
See [X Media Guidelines](/docs/media-guidelines/x-twitter) and [X
Authorization](/docs/dashboard/connect-social-accounts/x-twitter) for more information.
## X Options
You can set additional options for a post by using the `twitterOptions` parameter.
```json X Options theme={"system"}
{
"twitterOptions": {
"altText": ["This is my best pic", "😃 here is the next one"],
"blockCountries": ["US", "CA"], // or "allowCountries": ["GB", "IE"]
"longPost": true,
"longVideo": true,
"poll": {
"duration": 5, // required. Number in minutes
"options": ["yes", "maybe", "no"] // required
},
"quoteTweetId": "651601430669664256",
"replySettings": "mentioned",
"subscribersOnly": true,
"isAIGenerated": true,
"thumbNail": "https://img.ayrshare.com/012/gb.jpg",
// Video Metadata
"videoTitle": "My Product Demo",
"videoDescription": "A short walkthrough of our latest release.",
"embeddable": false, // single-video posts only
// Subtitles / Captions for Videos
"subTitleUrl": "https://img.ayrshare.com/012/captions.srt",
"subTitleLanguage": "en",
"subTitleName": "English",
// Thread Options
"thread": true,
"threadNumber": true,
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"],
}
}
```
X options are optional fields that can be used to control the post.
> [!NOTE]
>
> The block above lists the available options together; it is not a set that should all be sent
> at once. When `thread` is set, see [which options apply to a
> thread](/docs/apis/post/social-networks/x-twitter#thread-option-scoping) — most apply to every post
> in the thread, a few only to the first, and `longPost` has no effect at all.
**altText** `array of strings`
Alternative text for images to help with accessibility and screen readers. Maximum 1,000 characters per alt text.
See [Alt Text](/docs/apis/post/social-networks/x-twitter#alt-text) for more information.
**blockCountries** `array of strings`
Restrict media to specific countries by blocking certain regions. Use [country codes](/docs/iso-codes/country).
Cannot be used with `allowCountries`. See [Geo Restrictions](/docs/apis/post/social-networks/x-twitter#geo-restrictions) for more information.
**allowCountries** `array of strings`
Restrict media to specific countries by allowing certain regions. Use [country codes](/docs/iso-codes/country).
Cannot be used with `blockCountries`. See [Geo Restrictions](/docs/apis/post/social-networks/x-twitter#geo-restrictions) for more information.
**longPost** `boolean` (default: false)
Enable posting of longer posts up to 25,000 characters for Premium users.
See [Long Post](/docs/apis/post/social-networks/x-twitter#long-post) for more information.
**longVideo** `boolean` (default: false)
Allow posting videos longer than 2 minutes 20 seconds for approved accounts.
See [Long Video](/docs/apis/post/social-networks/x-twitter#long-video) for more information.
**poll** `object`
Conduct polls with custom options and duration.
Required fields: `duration` (number in minutes), `options` (array of strings).
See [Polls](/docs/apis/post/social-networks/x-twitter#polls) for more information.
**quoteTweetId** `string`
Quote another tweet by specifying the Tweet ID.
See [Quote Tweet](/docs/apis/post/social-networks/x-twitter#quote-tweet) for more information.
**replySettings** `string`
Control who can reply to the post.
Values: `following`, `mentioned`, `subscribers`, or `verified`.
See [Reply Settings](/docs/apis/post/social-networks/x-twitter#reply-settings) for more information.
**subscribersOnly** `boolean` (default: false)
Make the post visible only to subscribers.
See [Subscribers Only](/docs/apis/post/social-networks/x-twitter#subscribers-only) for more information.
**isAIGenerated** `boolean` (default: false)
Apply X's "Made with AI" label to disclose that the post contains AI-generated media.
Both `true` and `"true"` apply the label. Any other value leaves the label off — the post still
publishes and the response adds a `warnings` entry explaining why.
> [!NOTE]
>
> The label applies only to posts that include media, and cannot be changed after posting. Applies to
> immediate, scheduled, and threaded posts.
Media type: image, video
See [Made with AI](/docs/apis/post/social-networks/x-twitter#made-with-ai) for more information.
**subTitleUrl** `string`
Add subtitles/captions to videos using SRT files. Must be a valid SRT file URL ending in `.srt`.
See [Subtitles / Captions for Videos](/docs/apis/post/social-networks/x-twitter#subtitles-/-captions-for-videos) for more information.
**subTitleLanguage** `string` (default: en)
The language of the subtitles. Must be a valid [language code](/docs/iso-codes/language).
**subTitleName** `string`
The name of the caption track. Maximum 150 characters.
**thumbNail** `string`
Set a thumbnail (cover image) for videos. Must be a URL to a JPEG, PNG, BMP, or WebP image file.
See [Video Thumbnail](/docs/apis/post/social-networks/x-twitter#video-thumbnail) for more information and [X Media Guidelines](/docs/media-guidelines/x-twitter#video-thumbnail) for image requirements.
**videoTitle** `string`
Set the title for a video. Maps to the title field in X Media Studio.
See [Video Metadata](/docs/apis/post/social-networks/x-twitter#video-metadata) for more information.
**videoDescription** `string`
Set the description for a video. Maps to the description field in X Media Studio.
See [Video Metadata](/docs/apis/post/social-networks/x-twitter#video-metadata) for more information.
**embeddable** `boolean` (default: true)
Control whether the video plays inside embedded posts on other websites. X's default is `true`.
Set `false` to stop the video playing in embeds. Pressing play on an embed sends the viewer to x.com, and the video still plays on x.com as normal.
Applies only to a post with a single video. On any other post, the post still publishes and the response adds the warning `embeddable applies to a post with a single video; it was not applied.` A value that is not a boolean is ignored.
Media type: video
**thread** `boolean` (default: false)
Break long posts into connected thread series with optional numbering and media.
See [Threads](/docs/apis/post/social-networks/x-twitter#thread) for more information.
**threadNumber** `boolean` (default: false)
Automatically add numbers at the end of threads in the format of 1/n.
Requires `thread: true`.
**mediaUrls** `array of strings`
Add media objects to threads. One media object will be added to each thread in order.
Use `null` to skip media for a specific thread. Use objects with multiple URLs for multiple media per thread.
## Alt Text
Add alternative text, also known as alt text, to a Tweet's image. X alt text is an accessibility feature used for additional user info and screen readers.
Use the `altText` in the `twitterOptions` object.
```json X Alt Text theme={"system"}
{
"twitterOptions": {
"altText": ["This is my best pic", "😃 here is the next one"] // Array of Alt Texts
}
}
```
Each alt text must correspond to an image in the `mediaUrls` array. The alt text will be applied to each image in order.
> [!NOTE]
>
> Alt text cannot be applied to videos. If a video is included in the `mediaUrls` with `altText`,
> the video will not be posted. Alt text must be 1,000 characters or less.
## Geo Restrictions
You can restrict X media, such as an image or video, to a specific countries by using the `blockCountries` and `allowCountries` parameters with [country codes](/docs/iso-codes/country).
The post will still show in all countries, but the media will not be available in the countries specified.
```json Block Countries theme={"system"}
{
"twitterOptions": {
"blockCountries": ["US", "CA"]
}
}
```
```json Allow Countries theme={"system"}
{
"twitterOptions": {
"allowCountries": ["GB", "IE"]
}
}
```
`blockCountries`: An array of country codes to block. See [country codes](/docs/iso-codes/country).
`allowCountries`: An array of country codes to allow. See [country codes](/docs/iso-codes/country).
You must only use one of the parameters `blockCountries` or `allowCountries` at a time.
If both parameters are used or a country is not supported by X, the geo restrictions will be ignored.
## Long Post
Users with Premium X accounts, such as Premium or Premium Plus, have the ability to post longer posts of up to 25,000 characters. Ayrshare automatically allows long from posts for users with Premium X accounts.
If the user changes their X Premium status, please wait 24 hours for it to reflect in Ayrshare. You can check the Premium subscription status of a user with the [/user](/docs/apis/user/overview) or [/analytics](/docs/apis/analytics/social) endpoints.
You may also force a long form post to be accepted with the `longPost` body parameter. This can be done by including the following JSON in your request:
```json X Long Post theme={"system"}
{
"twitterOptions": {
"longPost": true
}
}
```
However, if a user without a Premium account attempts to post a long tweet, the system will return a `code: 111` error.
> [!NOTE]
>
> `longPost` has no effect when `thread` is also set — a [thread](/docs/apis/post/social-networks/x-twitter#thread)
> splits your text across separate posts, so nothing is published long form. The thread
> publishes normally and the response includes a warning saying `longPost` was ignored. Remove
> `thread` to publish a single long post.
## Long Video
*Business or Enterprise Plan required.*
X requires videos have a [maximum video length](/docs/media-guidelines/x-twitter#video) of 2 minutes and 20 seconds.
However, if X has approved the account to upload longer videos — for example an X Premium account, or one in the [Amplify Partner Program](https://media.twitter.com/en/articles/products/2018/in-stream-video-ads-for-publishers) — you can post videos up to **4 hours** in length.
> [!WARNING]
>
> Please verify that your user's X account is Premium or in the [Amplify Partner
> Program](https://media.twitter.com/en/articles/products/2018/in-stream-video-ads-for-publishers)
> before using the `longVideo` parameter. If your user's X account is not authorized to post long
> videos, the system will return an error.
Use the `longVideo` twitterOptions parameter when posting a long video:
```json X Long Video theme={"system"}
{
"twitterOptions": {
"longVideo": true
}
}
```
## Mentions
Mention another X handle by adding `@handle` in the post text. For example:
```json X Mention theme={"system"}
{
"post": "The best social media API @Ayrshare ever!",
"platforms": ["twitter"]
}
```
> [!WARNING]
>
> Please review the [important rules](/docs/testing/post-verification#mentions) on mentions.
## Polls
Conduct an X Poll with the `twitterOptions` `poll` parameter.
```json X Poll theme={"system"}
{
"twitterOptions": {
"poll": {
"duration": 5, // required. Number in minutes
"options": ["yes", "maybe", "no"] // required
}
}
}
```
`duration`: A number of minutes specifying the duration the poll will be conducted.
`options`: An array of strings of the poll options.
## Quote Tweet
You can quote another Tweet by specifying the low-level Tweet ID. The ID can be retrieved via the [`/post`](/docs/apis/post/post) response in the `postIds` field, [get history](/docs/apis/history/get-history), or directly from the Tweet URL: [https://twitter.com/Ayrshare/status/**1651601430669664256**](https://twitter.com/Ayrshare/status/1651601430669664256)
```json X Quote Tweet theme={"system"}
{
"twitterOptions": {
"quoteTweetId": "651601430669664256" // low-level Tweet Id
}
}
```
## Reply Settings
You can set the reply settings for a post to only allow replies by certian types of users.
```json X Reply Settings theme={"system"}
{
"twitterOptions": {
"replySettings": "mentioned"
}
}
```
The `replySettings` parameter can be one of the following values:
`following`: Only users who the X account is following can reply.
`mentioned`: Only users who are mentioned in the post can reply.
`subscribers`: Only users who are subscribers to the X account who posted the post can reply.
`verified`: Only users who are verified on X can reply to the post.
## Subscribers Only
You can set a post to only be visible to subscribers by using the `subscribersOnly` parameter.
```json X Subscribers Only theme={"system"}
{
"twitterOptions": {
"subscribersOnly": true
}
}
```
## Made with AI
Apply X's native "Made with AI" label to disclose that a post contains AI-generated media. Set the `isAIGenerated` field in the `twitterOptions` object to `true`.
```json X Made with AI theme={"system"}
{
"twitterOptions": {
"isAIGenerated": true
}
}
```
### Accepted Values
Both the boolean `true` and the string `"true"` apply the label; `false` and `"false"` leave it off.
Any other value — `"yes"` or `1`, for example — also leaves the label off, but never fails the
request: the post publishes normally and the response adds a `warnings` entry listing the accepted
values.
> [!NOTE]
>
> The Ayrshare [MCP Server](/docs/additional/mcp-action-server) takes `isAIGenerated` as a strict boolean —
> pass `true`, not `"true"`.
> [!NOTE]
>
> The label applies only to posts that include media (image or video). It has no effect on text-only
> posts, and it cannot be changed after the post is published.
> [!NOTE]
>
> Applies to immediate, scheduled, and threaded posts — a scheduled post publishes with the same "Made
> with AI" label it would have received if posted immediately. On a thread, each part that includes
> media is labeled.
## Subtitles / Captions for Videos
You can add X subtitles, also known as X captions, to videos by including an [SRT file](https://en.wikipedia.org/wiki/SubRip). Use the `subTitleUrl` field in the `twitterOptions` object to specify the URL to your SRT file.
```json X Subtitles theme={"system"}
{
"twitterOptions": {
"subTitleUrl": "https://img.ayrshare.com/012/captions.srt",
"subTitleLanguage": "en",
"subTitleName": "English"
}
}
```
`subTitleUrl`: A valid SRT file. The URL must start with `https://` and end in `.srt` and be a
valid SRT file.
`subTitleLanguage`: Optional: The language of the subtitles. Must be a valid [language
code](/docs/iso-codes/language). Default: "en".
`subTitleName`: Optional: The name of the caption track. The name is intended to be visible to
the user as an option during playback. The maximum name length supported is 150 characters.
Default: "English".
## Video Thumbnail
Set a thumbnail (cover image) for Twitter/X videos. The thumbnail is displayed before the video plays and helps users understand the video content. Use the `thumbNail` in the `twitterOptions` object.
```json Twitter/X Video Thumbnail theme={"system"}
{
"twitterOptions": {
"thumbNail": "https://img.ayrshare.com/012/gb.jpg"
}
}
```
"thumbNail": A URL to the thumbnail image. Supported image formats are JPEG, PNG, BMP, and WebP.
The thumbnail image should represent the video content and be visually appealing to encourage engagement.
See [X Media Guidelines](/docs/media-guidelines/x-twitter#video-thumbnail) for image requirements.
## Video Metadata
Set a title and description for a video posted to X. These map to the title and description fields in X Media Studio. Use the `videoTitle` and `videoDescription` fields in the `twitterOptions` object.
```json X Video Metadata theme={"system"}
{
"twitterOptions": {
"videoTitle": "My Product Demo",
"videoDescription": "A short walkthrough of our latest release."
}
}
```
`videoTitle`: The title for the video, mapped to the X Media Studio title field.
`videoDescription`: The description for the video, mapped to the X Media Studio description
field.
## Video Monetization (Pro Media)
Ayrshare supports X (Twitter) video monetization for eligible accounts through X's Pro Media program. This is a limited-access capability — [contact us](mailto:support@ayrshare.com) if you'd like access.
## Thread
An X Thread, also known as a tweetstorm, is a connected series of posts on X (formerly Twitter) that allows you to share longer ideas beyond a single post's character limit, appearing as one continuous narrative when viewed together.

### Posting a Thread
A X Thread can be posted via the API.
A thread is a post broken up into a set of reply threads and associated in X with a line.
You can either automatically break up the post or specify the [thread breaks](/docs/apis/post/social-networks/x-twitter#thread-breaks) in the post text.
```json X Thread theme={"system"}
{
"twitterOptions": {
"thread": true, // required for TweetStorm
"threadNumber": true, // optional to add numbers to each thread
"mediaUrls": ["https://site.com/image1.png", "https://site.com/image2.png", ...] // optional one media object is added to a thread in order
}
}
```
`thread: true` to automatically break apart the post text into threads based on line breaks.
`threadNumber: true` to automatically add numbers at the end of threads in the format of 1/n.
For example the 2nd of 5 threads will have appended: 2/5
`mediaUrls: [array of urls]` to add each media object, an image or video, to a thread in order.
Only one media object will be added to a thread in order.
If the post is sent as a X Thread, the returned post analytics will be an array of Tweets, `"twitter": []`. See [Post Analytics 200 Response](/docs/apis/analytics/post) for more information.
#### Which twitterOptions Apply to a Thread
The other `twitterOptions` you send apply to a thread as well, not only to a single post. Most apply to **every** post in the thread; a few apply only to the **first** post, because that is where X accepts them.
**First post only** — `poll`, `quoteTweetId`, `replyId` and `replySettings`. A poll or a
quote belongs to the post that opens the thread, and X only accepts reply settings on the
post that starts a conversation, so sending them on a reply is rejected. Setting
`replySettings` on a thread gates replies for the whole conversation from that first post.
**`replySettings` cannot be combined with `replyId`.** X does not accept reply settings on a
post that is itself a reply, so a thread posted as a reply to an existing post cannot also
set `replySettings` — the request is rejected and nothing publishes. This is the same
restriction a single reply has always had. Send one or the other.
**Every post** — everything else, including `subscribersOnly`, `longVideo` and the Pro
Media options. `subscribersOnly` applies to every post so that later posts in an exclusive
thread are not public.
**Media-specific** — `altText` and subtitles apply to the media on the **first** post.
Media on later posts is published without them.
> [!WARNING]
>
> An option your account is not entitled to use now fails the whole thread rather than being
> ignored. For example, sending `subscribersOnly: true` from an account without X Subscriptions
> returns `code: 111` and nothing is published — previously the thread published and the option
> was silently dropped. Check your account's entitlements, or remove the option.
`longVideo` works on a thread: attach the video with `mediaUrls` and it is verified against the long-video limit rather than the standard 2 minutes and 20 seconds. See [Long Video](/docs/apis/post/social-networks/x-twitter#long-video).
`longPost` and `thread` cannot both take effect — a thread splits your text across separate posts, so nothing is published long form. The thread still publishes and the response carries a warning saying `longPost` was ignored. Remove `thread` to publish a single long post.
#### Thread Media
##### Skip Media
Skip media for the thread by using `null` in the array. For example:
`["https://site.com/image1.png", null, "https://site.com/image2.png"]`
This will place image1 on the first Tweet, no image on the second Tweet, and image2 on the third Tweet.
##### Multiple Media
Multiple media objects can be added to a Tweet in a Thread by adding an object `{}` with the media URLs in the `mediaUrls` array. Any unique object keys can be used. For example:
```json X Thread with Multiple Media URLs theme={"system"}
{
"twitterOptions": {
"thread": true,
"threadNumber": true,
"mediaUrls": [
"https://img.ayrshare.com/random/photo-1.jpg",
{
"1": "https://img.ayrshare.com/random/photo-2.jpg",
"2": "https://img.ayrshare.com/random/photo-3.jpg"
},
"https://img.ayrshare.com/random/photo-4.jpg"
]
}
}
```
In this example, the first Tweet will contain photo-1.jpg, the second Tweet photo-2.jpg and photo-3.jpg, and the third Tweet photo-4.jpg.
##### Allowed mediaUrls Items
Each item in a thread's `mediaUrls` must be one of:
A URL string — one media object for that Tweet.
`null` — no media for that Tweet.
An object `{}` of URL strings — several media objects for that Tweet.
An item can never be an array. Use an object, not an array, to add several media objects to one Tweet.
```json Wrong - Array Items theme={"system"}
"mediaUrls": [["https://img.ayrshare.com/random/photo-1.jpg"], ["https://img.ayrshare.com/random/photo-2.jpg"]]
```
```json Right - Object Items theme={"system"}
"mediaUrls": [{ "a": "https://img.ayrshare.com/random/photo-1.jpg" }, { "b": "https://img.ayrshare.com/random/photo-2.jpg" }]
```
Any other item, such as an array, a number, or an object with a number or array value, returns [`code: 527`](/docs/errors/errors-ayrshare#thread-and-media-option-errors) with HTTP `400`. The `details` field names the first item to fix, for example `twitterOptions.mediaUrls[0]`.
#### Thread Breaks
Ayrshare automatically breaks up the post text into appropriate length tweet (> 280 characters).
When creating threads, we prioritize keeping complete sentences in one post when possible.
If a sentence won't fit, we split between sentences.
For very long sentences, we split between words.
In rare cases where a word is too long, we split the word itself.
You may also manually add paragraphs with `\n\n` to the post text to indicate a unique thread should be created.
If you have `\n\n` in the post text, we will not automatically break the post into threads.
For example:
```json Example X Thread theme={"system"}
{
"post": "This is tweet 1\n\nThis is tweet 2.",
"platforms": ["twitter"],
"twitterOptions": {
"thread": true
}
}
```
will result in two Tweets in the thread.
If you want to add paragraphs, but not break into Tweets, use `\u2063\n\u2063\n`
```json X Thread with Paragraphs theme={"system"}
{
"post": "This is paragraph 1\u2063\n\u2063\nThis is paragraph 2.",
"platforms": ["twitter"],
"twitterOptions": {
"thread": true
}
}
```
will result in one Tweet with two paragraphs since the post is below 280 characters.
### Delete a Thread
To delete a Tweet Storm, call the [/post delete endpoint](/docs/apis/post/delete-post) with the top level post ID returned in the response. All threads will be deleted.
## Character Limits
Please see [X/Twitter Character Limits](/docs/help-center/technical-support/character-limits#x/twitter-character-limits) for more information.
## X Video Compatibility
Some video software creates MP4 files that are not compatible with X. For example, Camtasia versions older than 2019.0.9 create MP4 files that X rejects. Also more than one audio track often causes problems.
If you receive back the following message while posting, it indicates the video is not compatible with Twitter and needs to be re-encoded.
`"file is currently unsupported"`
Please check your video software for compatibility. For example, Adobe Media Encoder has an export preset for Twitter 1080p Full HD.

Please see here for [more X API examples](https://www.ayrshare.com/twitter-api-how-to-post-and-get-analytics-with-the-twitter-api#twitter-api-examples).
## Subscribers Only
You can set a post to only be visible to subscribers by using the `subscribersOnly` parameter.
```json X Subscribers Only theme={"system"}
{
"twitterOptions": {
"subscribersOnly": true
}
}
```
## Reply Settings
You can set the reply settings for a post to only allow replies by certian types of users.
```json X Reply Settings theme={"system"}
{
"twitterOptions": {
"replySettings": "mentioned"
}
}
```
The `replySettings` parameter can be one of the following values:
`following`: Only users who the X account is following can reply.
`mentioned`: Only users who are mentioned in the post can reply.
`subscribers`: Only users who are subscribers to the X account who posted the post can reply.
`verified`: Only users who are verified on X can reply to the post.
# YouTube API
Source: https://www.ayrshare.com/docs/apis/post/social-networks/youtube
Upload YouTube videos and Shorts with titles, thumbnails, playlists, tags, subtitles, location targeting, and AI content disclosure, plus upload limits.
> [!WARNING]
>
> YouTube posting requires your YouTube account to have at least one Channel and be an owner on the Channel.
> To create a YouTube Channel, click on your profile in the YouTube Dashboard and choose "Create a Channel".
> You may also use this direct link to create a YouTube Channel: [http://m.youtube.com/create\_channel](http://m.youtube.com/create_channel)
>
> If you're having issues viewing YouTube channels please see the [YouTube channel troubleshooting guide](/docs/help-center/technical-support/youtube-channels-not-showing).
> [!NOTE]
>
> YouTube upload failures may return error codes **453** (timeout) or **454**
> (service unavailable) with `retryAvailable: true`. Your integration can branch
> on this flag to retry transient failures automatically with backoff. See the
> [error codes reference](/docs/errors/errors-ayrshare) for the full list.
Please see [YouTube Media Guidelines](/docs/media-guidelines/youtube) and [YouTube Authorization](/docs/dashboard/connect-social-accounts/youtube) for more information.
## Posting to YouTube
### Posting Overview
Posting using the YouTube API requires the `youTubeOptions` object with a `title` parameter - max 100 Unicode code points.
The `title` is the only required field and can be autogenerated with the [transcribe endpoint](/docs/apis/generate/transcribe-video).
YouTube limits the title to 100 Unicode code points and the description (`post`) to 5000 bytes. When `youTubeOptions.shorts` is `true`, Ayrshare adds ` #shorts` (8 bytes) to the description, and that counts toward the 5000. Until **October 21, 2026**, Ayrshare shortens a title or description that is over the limit and adds a `warnings` entry (`code: 176`, with `feature` set to `title` or `description`) to the YouTube result. From October 21, 2026, a title or description over the limit returns error **176** with a `details` message that gives the length and the limit, and nothing is posted. A scheduled post that will publish on or after October 21, 2026 is checked when you submit it, so the error comes back right away instead of at the scheduled time.
For example, to post a YouTube video with default settings:
```json YouTube Post theme={"system"}
{
// Required: Video description
"post": "My Best YouTube Description", // empty string is allowed
// Required: Platform to post to
"platforms": ["youtube"],
// Required: URL of video (only 1 allowed)
"mediaUrls": ["https://img.ayrshare.com/012/vid.mp4"],
"youTubeOptions": {
// Required: Video title (max 100 characters)
"title": "Your Best Title"
}
}
```
YouTube videos are `private` by default, but the visibility can be set to `public` or `unlisted`.
Please see the optional fields below for more details.
### YouTube Post Optional Fields
There are several other optional fields listed below, including the video `visibility`, `tags`, and `publishAt` date. Please see the comments for requirements and descriptions.
```json YouTube Post Optional Fields theme={"system"}
{
// Required fields
"post": "My Best YouTube Description", // Video description, up to 5,000 bytes
"platforms": ["youtube"], // Platform to post to
"mediaUrls": ["https://img.ayrshare.com/012/vid.mp4"], // URL of video (1 allowed)
"youTubeOptions": {
// Required fields
"title": "Your Best Title", // Video Title (max 100 characters)
/** Optional Fields **/
// Visibility: "public", "unlisted", or "private" (default: "private")
"visibility": "private",
// Thumbnail settings - JPEG/PNG URL under 2MB, must end in png/jpg/jpeg
"thumbNail": "https://img.ayrshare.com/012/gb.jpg",
// Video organization
"playListId": "PLrav6EfwgDX5", // Playlist ID to add the video
"tags": ["dancing", "dogs"], // Tag array (400 chars total, 2+ chars each)
// Video settings
"madeForKids": false, // Self-declared kids content (default: false)
"license": "youtube", // "youtube" (default) or "creativeCommon"
"embeddable": true, // default: true
"publicStatsViewable": true, // default: true
"shorts": true, // Post as YouTube Short (max 3 minutes, adds #shorts)
"notifySubscribers": true, // Send notification to subscribers (default: true)
"categoryId": 24, // Video category (24 = Entertainment)
"containsSyntheticMedia": true, // Disclose that a video contains realistic Altered or Synthetic (A/S) content
// YouTube controlled publishing - UTC publish time. See below for details.
"publishAt": "2022-10-08T21:18:36Z",
}
}
```
The `title` must be 100 Unicode code points or less. The `post` must be 5,000 bytes or less. Ayrshare replaces \< and > with \[ and ) before sending, since YouTube does not accept them.
The Playlist Id can be found by opening the playlist in a browser and copying the value after `list=`. The authenticated user and channel must be the owner of the playlist to add videos.
If your video doesn't end in a known video extension such as mp4, please use the `isVideo` parameter. See the [/post endpoint](/docs/apis/post/post) for details.
The `publishAt` field will allow YouTube to control the publishing time.
The video will be private until the publish time when it will be made public.
If the publish time is in the past, the video will be made public immediately.
Do not use the post `scheduleDate` field when using the `publishAt` field.
The `containsSyntheticMedia` field is used to disclose that a video contains realistic Altered or Synthetic (A/S) content: Make a real person appear to say or do something they didn't actually say or do, alter footage of a real event or place, generate a realistic-looking scene that did not actually occur. See [AI Content Disclosure](/docs/apis/post/social-networks/youtube#ai-content-disclosure).
`license` - Sets the video license type. Accepted values: `"youtube"` (Standard YouTube License, default) or `"creativeCommon"` (Creative Commons - Attribution).
`embeddable` - Boolean (or the strings `"true"` / `"false"`). Controls whether the video can be embedded on third-party websites. Default: `true`.
`publicStatsViewable` - Boolean (or the strings `"true"` / `"false"`). Controls whether the **extended statistics panel** on the video's watch page is publicly viewable. The basic view count and like count remain publicly visible regardless of this setting. Default: `true`. See [`status.publicStatsViewable` in the YouTube Data API reference](https://developers.google.com/youtube/v3/docs/videos#status.publicStatsViewable) for details.
See [YouTube Media Guidelines](/docs/media-guidelines/youtube) for more information.
> [!NOTE]
>
> **Monetization & Content Controls**
>
> The fields `madeForKids`, `license`, `embeddable`, and `publicStatsViewable` are settable via the Ayrshare API at upload time. However, direct monetization toggles (enabling/disabling ads), ad type selection (pre-roll, mid-roll, post-roll), revenue sharing, and Content ID require YouTube CMS credentials — available only to MCNs and enterprise content owners — and are **not** available through standard YouTube OAuth or the Ayrshare API.
## AI Content Disclosure
Self-disclose realistic Altered or Synthetic (A/S) content so YouTube applies its AI disclosure to the published video.
```json YouTube AI Content Disclosure theme={"system"}
{
"post": "Generated with a little help from a model",
"platforms": ["youtube"],
"mediaUrls": ["https://img.ayrshare.com/012/vid.mp4"],
"youTubeOptions": {
"title": "Your Best Title",
"containsSyntheticMedia": true
}
}
```
`containsSyntheticMedia`: Boolean set to `true` to disclose that the video
contains realistic Altered or Synthetic content. Default `false`. Omitting the
parameter means Ayrshare sends no disclosure with the upload. YouTube may still apply its own
label (see below).
YouTube requires the disclosure when a video does any of the following: makes a real person
appear to say or do something they didn't actually say or do, alters footage of a real event or
place, or generates a realistic-looking scene that did not actually occur.
Only `true` and `"true"` turn the disclosure on. Any other value,
including `false`, `"yes"`, or `1`, sends no disclosure and
returns no warning.
It sets the **AI use** question on the video's Details page in YouTube Studio to
**Yes**, so you can confirm it there after publishing.
This parameter is a self-disclosure only, and it is not the only way a label can appear.
Ayrshare does not detect or infer whether your media is AI-generated. YouTube may also label a
video on its own, for example content made with its GenAI tools or files carrying C2PA Content
Credentials.
> [!NOTE]
>
> Ayrshare cannot add the disclosure after a video publishes. Set `containsSyntheticMedia` at post
> time. A **self-declared** disclosure can still be changed afterwards in YouTube Studio — but a
> label YouTube applied itself, whether from its GenAI tools, from qualifying C2PA metadata, or
> after manual review, is not editable there.
## YouTube Shorts
A YouTube Short is a short-form vertical video that can be up to three minutes long.
These are similar to TikTok videos and Instagram or Facebook Reels.
### Posting YouTube Shorts
You can post a YouTube Shorts video of up to 3 minutes by adding the `shorts` parameter to the `youTubeOptions` object.
```json YouTube Shorts Post theme={"system"}
{
"youTubeOptions": {
"shorts": true
}
}
```
The *#shorts* hashtag will be added to the YouTube description.
### Important Information on YouTube Shorts
Sending a video as a Short is an indication to YouTube that you would like the video to appear
as a Short, but does not guarantee it will appear as a Short. The video must meet the [Short
video](/docs/media-guidelines/youtube#shorts) requirements, such as being 3 minutes or less and
vertical aspect ratio of 9:16 to be considered a Short by YouTube.
YouTube Shorts do not support thumbnails.
Additional information on using the [API to post YouTube
Shorts](https://www.ayrshare.com/blog/post-youtube-shorts-with-an-api/).
## YouTube Thumbnails
> [!NOTE]
>
> Custom thumbnails require a **verified YouTube channel**. The most common reason a thumbnail fails to apply (while the video still posts) is an unverified channel — verify at [https://www.youtube.com/verify](https://www.youtube.com/verify) (phone verification). Your `thumbNail` must also be a **PNG or JPG/JPEG**, **2MB or less**, and served from a **reachable URL**; Ayrshare validates these before publishing where possible. When a video posts but the thumbnail fails, the YouTube result keeps `status: "success"` and adds a `warnings` array (`feature: "thumbnail"`, `code: 307`) describing the failure. See [YouTube Thumbnail Not Applied (Unverified Channel)](/docs/help-center/technical-support/youtube-thumbnail-unverified-channel) for the full fix.
### Adding YouTube Thumbnails
YouTube Thumbnails and other features, such as uploading 15 minute videos, require verification of your phone number.
A `thumbNail` is a URL of a JPEG or PNG and less than 2MB in size. The file extension must end in png, jpg, or jpeg.
```json YouTube Thumbnail theme={"system"}
{
"youTubeOptions": {
"thumbNail": "https://img.ayrshare.com/012/gb.jpg"
}
}
```
YouTube Shorts do not currently support thumbnails.
### Enable Thumbnails in YouTube Studio
You must be granted YouTube permissions to post thumbnails. In [YouTube Studio](https://studio.youtube.com/) go to *Settings->Channel*. Select "*Feature Eligibility*" and click "*Features that require phone verification*". Enter your phone number to enable.

> [!WARNING]
>
> YouTube may take up to 24 hours to enable thumbnails after you verify your phone number. Please note, YouTube determines eligibility for adding thumbnails. "Enabled" phone verification does not guarantee YouTube will allow thumbnail uploads.
>
> If you have been verified for 24 hours and still have issues check that:
>
> 1. You are able to manually upload thumbnails in YouTube Studio.
> 2. If you're working with a Brand Content Owner Account (often used for business or organization channels), make sure you have the necessary permissions. We recommend "Owner" rights.
> 3. If you are still having issues, please see this video on [solving the Thumbnail issue](https://www.youtube.com/watch?v=1bFmX2uQk0Y).
## Video: Posting to YouTube via the API
[Posting to YouTube API](https://www.youtube.com/embed/UkisNgVxubg)
## Upload Limits
YouTube limits how many videos a channel can upload in a 24-hour period via the YouTube API.
Depending on a creator's location, a channel might be able to increase their daily limit by
getting access to advanced features. To learn more visit this
[article](https://notifications.google.com/g/p/ACnX6LbjUElgT-OfRLVjenkpdldv6pIJ5JYGZyPGTdJwRYmo3fXFkfXJsPGnswgns0BjJ6Bjxn2bqpqfBt6gBdkLoqESKA7mqLNllzcR2qnYUJn5KlJN5jPqCWl6DGr58cZEt94mMvxXATN6_PaBR1RYEpNMTYWN).
Limits may vary by country/region or channel history. Copyright strikes may impact channel
history eligibility and [Community Guidelines
strikes](https://support.google.com/youtube/answer/2802032) will affect how much a channel can
upload.
If you receive an upload limit error please wait and try again in 24 hours.
## Description Links
You must activate **Advanced Features** in YouTube Studio to have clickable links in the YouTube video description.
Go to **YouTube Studio -> Settings -> Channel -> Feature Eligibility -> Advanced Features -> Access Features** to activate the advanced features.

## Playlists
You can add videos to a YouTube playlist by including the `playListId` in the `youTubeOptions` object.
Be sure that the authenticated user and channel are the owner of the playlist.
```json YouTube Playlist theme={"system"}
{
"youTubeOptions": {
"playListId": "PLrav6EfwgDX5"
}
}
```
## Subtitles / Captions for Videos
You can add YouTube subtitles, also known as YouTube captions, to videos by including an [SRT file](https://en.wikipedia.org/wiki/SubRip) or a YouTube [SBV file](https://support.google.com/youtube/answer/2734698). Use the `subTitleUrl` field in the `youTubeOptions` object to specify the URL to your SRT or SBV file.
```json YouTube Subtitles theme={"system"}
{
"youTubeOptions": {
"title": "My new post from Ayrshare to Youtube",
"subTitleUrl": "https://img.ayrshare.com/012/captions.srt",
"subTitleLanguage": "en",
"subTitleName": "English"
}
}
```
`subTitleUrl`: A valid SRT or SBV file. The URL must start with `https://` and end in `.srt` or
`.sbv` and be a valid SRT or SBV file. The file must be under 100 MB.
`subTitleLanguage`: Optional: The language of the subtitles. Must be a valid [language
code](/docs/iso-codes/language). Default: "en".
`subTitleName`: Optional: The name of the caption track. The name is intended to be visible to
the user as an option during playback. The maximum name length supported is 150 characters.
Default: "English".
> [!NOTE]
>
> **What are SRT and SBV files?**
>
> SRT (SubRip Subtitle) and SBV (YouTube SubViewer) are subtitle file formats used for displaying
> timed text in videos. The main difference is that SRT uses timestamps in HH:MM:SS,MS format with
> arrow separators, while SBV uses HH:MM:SS.MS format with commas.
>
> ```text theme={"system"}
> ## SRT Format Example
> 1
> 00:00:01,000 --> 00:00:04,000
> Welcome to our tutorial on subtitle formats.
>
> 2
> 00:00:04,500 --> 00:00:08,000
> Today we'll learn about SRT and SBV files.
>
> ## SBV Format Example
> 0:00:01.000,0:00:04.000
> Welcome to our tutorial on subtitle formats.
>
> 0:00:04.500,0:00:08.000
> Today we'll learn about SRT and SBV files.
> ```
## Tags
You can add YouTube tags to your videos by including the `tags` array in the `youTubeOptions` object.
Tags must be at least 2 characters in length each and the total length of all tags must be 400 characters or less.
```json YouTube Tags theme={"system"}
{
"youTubeOptions": {
"tags": ["dancing", "dogs"]
}
}
```
## YouTube Mentions
While you can add a `@handle` to a YouTube post, YouTube does not support resolving mentions in the post text.
The `@handle` will remain as plain text.
## Character Limits
Please see [YouTube Character Limits](/docs/help-center/technical-support/character-limits#youtube-character-limits) for more information.
## Unlinking and Revoked Access
If a user removes Ayrshare's access in their Google account, the next YouTube request returns error **250** and Ayrshare unlinks YouTube from that profile. You receive a [social webhook](/docs/apis/webhooks/actions#social-action) with `"type": "unlink"` and `"source": "system"`, and an email is sent. Relink YouTube to continue posting.
When YouTube is unlinked, whether by you, your user, or automatically, Ayrshare:
* Revokes its access to the Google account, unless the same Google account is still linked to YouTube on another profile, or Ayrshare cannot confirm that it isn't.
* Deletes the YouTube data it stored for that profile 5 days later: YouTube analytics, records of comments sent through Ayrshare, and the YouTube video IDs and URLs in your post history. Your posts stay in your post history without their YouTube links, and the videos stay on YouTube.
YouTube's API policies require this deletion within 7 days. If the same Google account is linked to YouTube on that profile again before then, the data is kept. Relinking after the deletion does not restore it.
## Additional Endpoints
> [card] [Get YouTube Categories](/docs/apis/utils/youtube-categories)
> [card] [Set YouTube Watermark](/docs/apis/utils/set-youtube-watermark)
> [card] [Remove YouTube Watermark](/docs/apis/utils/remove-youtube-watermark)
# Update a Post
Source: https://www.ayrshare.com/docs/apis/post/update-post
PATCH /post
Reschedule, pause, or add notes to a pending post, approve a post awaiting approval, or disable comments and change YouTube visibility once published.
Update the `scheduleDate` of a post, `approval` status of a post, `notes`, or `visibility` of a posted YouTube video.
The post must originally have a `scheduleDate` and be in a "pending" `status`. The `status`
can be checked with the /history or GET /post endpoints.
The YouTube video must have been successfully posted to change visibility. Send `visibility`
inside `youTubeOptions`, not at the top level of the body.
Besides `id`, the request must include at least one of the parameters below. A request with
only `id` returns `code: 101`.
The [approval workflow](/docs/apis/post/overview#approval-workflow) requires the current status of the
post be in "awaiting approval".
Other parameters cannot be updated. You will need to delete the post and repost.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**id** `string` *required*
Ayrshare Post ID of the post to update. Ayrshare Post ID returned from [/post](/docs/apis/post/post).
**approved** `boolean` (default: false)
The original post requires approval and has a status of "awaiting approval", set to `true` to
approve and publish the post.
**disableComments** `boolean` (default: false)
Enable or disable comments on a post. Setting to `true` will disable comments. Setting to `false` will enable comments.
Supported platforms: Instagram and LinkedIn.
The post must be published. Otherwise the request returns error `305`.
Disabling comments on a published post will not delete existing comments.
Disabling LinkedIn comments will delete all existing comments on the thread.
Instagram comments will not be deleted.
TikTok comments cannot be changed after publishing.
**notes** `string`
Set notes on a post that can be retrieved via the [/history](/docs/apis/history/overview) endpoint. Notes are
for reference only and do not affect the post.
**scheduleDate** `string`
The `datetime` to schedule a future post. Accepts a UTC date time.
For example, use format `YYYY-MM-DDThh:mm:ssZ` and send as `2026-07-08T12:30:00Z`.
Please see [utctime](https://www.utctime.net/) for more examples.
> [!WARNING]
>
> If the datetime is in the past, the post will immediately be sent.
**scheduledPause** `boolean` (default: false)
Pause or unpause a scheduled post. If a post is unpaused and the `scheduleDate` is in the past,
the post will immediately be publish. Consider updating the `scheduleDate` before unpausing.
**youTubeOptions** `object`
Update the visibility of the YouTube video with the `visibility` field and values `unlisted`, `private`, or `public`.
Update the `description`, `title`, or `categoryId`. If no `description` or `categoryId` originally set, the default values are `""` and `24` (Entertainment), respectively.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"id": "s8k2jsk0pl", "scheduleDate": "2023-07-08T12:30:00Z", scheduledPause: true}' \
-X PATCH https://api.ayrshare.com/api/post
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
id: "s8k2jsk0pl", // required
scheduleDate: "2023-07-08T12:30:00Z",
scheduledPause: true
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'id': 's8k2jsk0pl',
'scheduleDate': '2023-07-08T12:30:00Z'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.patch('https://api.ayrshare.com/api/post',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
's8k2jsk0pl', // Replace with your actual post ID
'scheduleDate' => '2023-07-08T12:30:00Z'
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace PostUpdatePOSTRequest_csharp
{
class PostUpdate
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post";
using (var httpClient = new HttpClient())
{
try
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string json = "{\"id\":\"s8k2jsk0pl\"," +
"\"scheduleDate\":\"2023-07-08T12:30:00Z\"}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PatchAsync(url, content);
var responseBody = await response.Content.ReadAsStringAsync();
response.EnsureSuccessStatusCode();
Console.WriteLine(responseBody);
}
catch (HttpRequestException ex)
{
Console.WriteLine("Error: " + ex.Message);
if (ex.InnerException != null)
{
Console.WriteLine("Error details: " + ex.InnerException.Message);
}
}
}
}
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"id": "s8k2jsk0pl",
"scheduleDate": "2023-07-08T12:30:00Z"
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("PATCH", "https://api.ayrshare.com/api/post",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"id": "ZSU1tnnuykDy25wA6kvX", // Ayrshare Post ID
"scheduleDate": "2025-07-08T12:30:00Z",
"scheduledPaused": true
}
```
```json 400: ID Not Found theme={"system"}
{
"action": "update",
"status": "error",
"code": 305,
"message": "Error updating post. Post ID not found."
}on
```
```json 400: Invalid scheduleDate theme={"system"}
{
"action": "post",
"status": "error",
"code": 104,
"message": "Invalid schedule date format for scheduleDate. .../ayrshare.com/rest-api/endpoints/post#send-a-post"
}
```
# Create a Link Session
Source: https://www.ayrshare.com/docs/apis/profiles/create-link-session
POST /profiles/link-sessions
Create a social-linking URL for a user profile, without sending a private key.
Create a social-linking URL for a User Profile. Send the returned `url` to your user, and
they open it to connect their social accounts.
This is the recommended way to create a linking URL. It needs only your API key and a
`Profile-Key` — there is no private key to send and nothing to sign. Unlike a linking
URL created before, a link session is stored, so you can check whether it has been used
and revoke it before it expires.
The returned `url` signs your user into their profile, so treat it like a password and send
each one to a single user. See
[Sending the Linking URL](/docs/apis/profiles/social-linking-overview#sending-the-linking-url).
> [!NOTE]
>
> The URL is valid for **5 minutes** by default. Use `expiresIn` to set a different
> window, up to 2880 minutes (48 hours).
> [!NOTE]
>
> [Generate a Linking URL](/docs/apis/profiles/generate-jwt) performs the same operation and keeps
> working unchanged. It accepts the legacy `privateKey`, `base64` and `verify` parameters
> and ignores them. `domain` is not ignored on either endpoint - it stays optional and is
> still validated. New integrations should use this endpoint.
>
> One difference in the response: `generateJWT` returns a top-level `token` for backwards
> compatibility, and this endpoint does not return one **alongside** a `url` — the token in a
> `url` lives inside it. If you are migrating and your code reads `token`, read `url` instead.
> ([Connect mode](#connect-mode) for the embedded widget is the one shape that returns a bare
> `token`, because it returns no URL for the token to live in.)
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
> [!NOTE]
>
> The `Profile-Key` is a header on this endpoint — there is no `profileKey` body
> parameter. If it is missing you get `code: 188`, whose message lists `privateKey`,
> `profileKey` and other legacy field names because it is shared with
> [Generate a Linking URL](/docs/apis/profiles/generate-jwt). Read it as "the Profile-Key header is
> missing or wrong"; none of the other names in it are parameters of this endpoint.
**X-Twitter-OAuth1-Api-Key** `string`
Your X API Key (Consumer Key) from the X Developer Portal. When provided, the linking
URL will use your own X Developer App for OAuth linking.
**X-Twitter-OAuth1-Api-Secret** `string`
Your X API Secret (Consumer Secret) from the X Developer Portal. Required when
`X-Twitter-OAuth1-Api-Key` is provided.
## Body Parameters
**mode** `string` (default: grid)
Which linking surface this session drives.
* `grid` — the hosted linking page, showing every network you permit. This is the default, so a
request that omits `mode` creates one.
* `connect` — one network at a time, opened from your own dashboard. See
[Connect Mode](#connect-mode) below and [Direct Mode](/docs/multiple-users/connect-direct-mode).
Never inferred: passing `origin` or `network` does not put you in connect mode, so a grid
session cannot become a gated one by accident. Any other value returns `code: 188` with
`details` naming the two.
**expiresIn** `number` (default: 5)
Longevity of the link in minutes. Range: 1 to 2880 minutes.
Requires the Max Pack.
See [Link Expiry](/docs/apis/profiles/social-linking-overview#jwt-expires-in) for more information.
**logout** `boolean` (default: false)
Automatically log out the current session. Not recommended in production, since it
affects performance.
See [Automatic Logout of a Profile Session](/docs/multiple-users/api-integration-business#automatic-logout-of-a-profile-session).
**redirect** `string`
A URL to redirect to when the "Done" button or logo image is clicked. Add the query
parameter `origin=true` to redirect the opener window.
**allowedSocial** `array`
The social networks to display on the linking page. Overrides the networks configured
on the [Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access)
page.
```json Only display Facebook, X/Twitter, LinkedIn, and TikTok theme={"system"}
{
"allowedSocial": ["facebook", "twitter", "linkedin", "tiktok"]
}
```
**network** `string`
Connect mode only. The single social network this session connects, which is what makes it a
**direct-mode** session. Omit it for a session your own dashboard drives across several
networks.
One of `bluesky`, `facebook`, `gmb`, `instagram`, `instagramApi`, `linkedin`, `pinterest`,
`reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, `whatsapp`, `x`, `youtube`.
Anything else returns `code: 508` — including `fbg`, which is not a link target here.
Cannot be combined with `allowedSocial` (`code: 507`): a single-network session is already its
own allowlist. A network your account has not enabled returns `code: 509`, which is a different
answer from 508 because it is fixable on your
[Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access) page.
In grid mode it is ignored.
**instagramLinkMethod** `string`
Override which Instagram linking flow is used for this link. Valid values:
* `instagram`: Direct Instagram Login, no Facebook Page required.
* `facebook`: Link Instagram via a connected Facebook Page.
When omitted, the linking page uses your account-wide
[Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) setting.
**origin** `string`
The exact origin of the page that opened the linking window, so it can be told when
linking finishes.
When set, the linking page posts events to that origin with `window.postMessage` as
your user connects each account, and your page can react without polling. Events are
only ever sent to this exact value, so it must match your page's origin character for
character, including the scheme and any port.
Three shapes are accepted: an `https` origin (`https://app.example.com`),
`http://localhost:3000` for local development, and a native custom scheme
(`myapp://connected`). Anything else — a plain `http://` origin other than localhost, or
something that is not an origin at all — is ignored on the links this endpoint creates:
the link still works, it simply sends no events. It is optional, so omitting it is not an
error either.
Of the three, only the first two receive events. A custom scheme is a return target for
a mobile app and **cannot receive them**, because there is no browser window to post to;
native apps poll [Get a Link Session](/docs/apis/profiles/get-link-session) instead.
See [Link Completion Events](/docs/multiple-users/link-completion-events).
**In connect mode `origin` is required, and it is checked.** The leniency above is grid-mode
behaviour. With `mode: "connect"`, omitting it returns `code: 505` and a value that is not one
of the three accepted shapes returns `code: 506`, whose `details` repeat the shape you sent.
**domain** `string`
Optional. Your linking domain, when your account has more than one. When omitted, your
account's own domain is used. A domain not registered to your account is rejected.
**email** `object`
Send a Connect Accounts email carrying the link, so your user can reach their linking
page directly. Requires a `to` address.
Requires the Max Pack. The response reports the outcome in `emailSent`. If the email can't be
sent, you still get the link: the response has `emailSent: false` and an `emailError` object, so
you can send the link to your user another way.
See [Connect Accounts Email](/docs/apis/profiles/social-linking-overview#connect-accounts-email).
## Connect Mode
`mode: "connect"` creates a session for a linking surface you host yourself, rather than for the
hosted linking page. Which of the two connect shapes you get depends on one thing — whether you
pass `network`:
| You send | You get back | What you do with it |
| - | - | - |
| `mode: "connect"` and a `network` | a `url` pointing at a single-network connect page | open it in a popup — this is [direct mode](/docs/multiple-users/connect-direct-mode) |
| `mode: "connect"` and no `network` | a `token`, and no URL at all | hand it to your own front end |
> [!NOTE]
>
> **The response carries the secret exactly once.** A response has either a `url` or a `token`,
> never both and never two URLs. The token in a direct-mode session lives inside the `url`, the
> same way it does in grid mode; a session with no URL to carry it returns the bare `token`
> instead. Everything else is the same in all three modes: `sessionId`, `expiresAt`, `emailSent`,
> and `title` when the User Profile has one.
### What Connect Mode Requires
Neither of these is a body field of its own — the first is an account entitlement and the second is
the [`origin`](#body-parameters) parameter above, which connect mode makes mandatory.
**The [Max Pack](/docs/additional/maxpack).** Without it the call returns `code: 504`, checked before
the connect-mode parameters, so correcting `origin` or `network` will not change the answer. Contact support if you
need connect mode enabled on an account without the Max Pack.
**An `origin`, on every session.** There is no allowlist and no registration step — you send it on
each call and it is stored on the session, so a new environment needs no setup on our side. Three
shapes are accepted:
* an `https` origin — `https://app.example.com`
* a native custom scheme — `myapp://connected`
* `http://localhost` or `http://localhost:3000`, for local development
Origin only: no path, query or fragment, and no credentials in it. Omitting it returns `code: 505`,
and anything that is not one of the three shapes returns `code: 506`.
> [!WARNING]
>
> `email` cannot be used with a session that has no `network`, because there is no link to put in
> the email — that shape returns a token for your own front end. The call returns `code: 510`.
> Add a `network` for a direct-mode session, which does have a URL, or omit `email`.
Every code named above is in the [Link Session Errors](/docs/errors/errors-ayrshare#link-session-errors)
reference.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-H 'Profile-Key: PROFILE_KEY' \
-d '{"expiresIn": 60}' \
-X POST https://api.ayrshare.com/api/profiles/link-sessions
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const PROFILE_KEY = "PROFILE_KEY";
fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
"Profile-Key": PROFILE_KEY,
},
body: JSON.stringify({ expiresIn: 60 }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'expiresIn': 60}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY',
'Profile-Key': 'PROFILE_KEY'}
response = requests.post('https://api.ayrshare.com/api/profiles/link-sessions',
json=payload, headers=headers)
print(response.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/profiles/link-sessions',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY',
'Profile-Key' => 'PROFILE_KEY'
],
'json' => [
'expiresIn' => 60,
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace CreateLinkSession_csharp
{
class CreateLinkSession
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string PROFILE_KEY = "PROFILE_KEY";
string url = "https://api.ayrshare.com/api/profiles/link-sessions";
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
client.DefaultRequestHeaders.Add("Profile-Key", PROFILE_KEY);
var sendData = new { expiresIn = 60 };
string jsonData = JsonConvert.SerializeObject(sendData);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"HTTP request error: {e.Message}");
}
}
}
}
```
```json 200 theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8", // Identifier for this link. Use it with Get and Revoke a Link Session.
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN", // Send this to your user exactly as returned. The token exists only in here.
"expiresAt": "2026-09-02T08:03:26.838Z", // When the link stops working, as an ISO 8601 timestamp.
"emailSent": false, // Whether the connect-accounts email was sent. false with no emailError means no email was requested.
"title": "Acme Client" // The User Profile's title. Omitted when the profile has none.
}
```
```json 200: Email Failed to Send theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN", // Still valid. Send it to your user yourself.
"expiresAt": "2026-09-02T08:03:26.838Z",
"emailSent": false,
"emailError": { // Only present when an email was requested and could not be sent.
"code": 333,
"message": "Email failed to send. Please confirm a valid email was provided."
},
"title": "Acme Client"
}
```
```json 200: Direct Mode (mode: "connect" with a network) theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"url": "https://profile.ayrshare.com/connect?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu", // Open this in a popup. One network, no domain parameter.
"expiresAt": "2026-09-02T08:03:26.838Z",
"emailSent": false,
"title": "Acme Client"
}
```
```json 200: Connect Mode Without a Network theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"token": "ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu", // No url: this shape returns the bare token instead. Treat it like a password.
"expiresAt": "2026-09-02T08:03:26.838Z",
"emailSent": false, // Always false here - email needs a link to send, so it returns code: 510.
"title": "Acme Client"
}
```
```json 401: Connect Mode Requires the Max Pack theme={"system"}
{
"action": "link session",
"status": "error",
"code": 504,
"message": "Connect mode requires the Max Pack. Activate it on your Account page: https://app.ayrshare.com/account"
}
```
```json 400: Missing Profile-Key Header theme={"system"}
{
"action": "JWT",
"status": "error",
"code": 188,
"message": "Missing or incorrect privateKey, profileKey, domain, email 'to', or expiresIn fields."
}
```
```json 400: Domain Not Registered to Your Account theme={"system"}
{
"action": "JWT",
"status": "error",
"code": 189,
"message": "Error generating JWT. Check the sent parameters.",
"details": "Missing or incorrect domain."
}
```
```json 401: expiresIn Requires the Max Pack theme={"system"}
{
"action": "authorization",
"status": "error",
"code": 340,
"message": "Max Pack is required. Please go to your Account page in the Ayrshare Dashboard to activate the Max Pack.",
"details": "email and expiresIn parameters are only available on the Max Pack."
}
```
# Create a User Profile
Source: https://www.ayrshare.com/docs/apis/profiles/create-profile
POST /profiles
Create a new User Profile under your Primary Profile. The response includes the Profile Key, which you need to securely store to make API calls for users.
Create a new profile under your Primary Profile. Upon successful creation of the User Profile, the API response will include the Profile Key associated with the newly created profile. Securely store the Profile Key in your system, as it will be required to make API calls on behalf of your user.
Please note that for security reasons, the Profile Keys are only returned in two specific cases:
1. User Profile Creation: When a new User Profile is created.
2. In the Ayrshare Dashboard: You can find the Profile Key for each profile within the Ayrshare Dashboard. Switch to the User Profile and then navigate to the Profile Key page.
We recommend you securely store the Profile Key in your system.
## Manage Social Networks
Use the disableSocial field to manage (add/remove) social networks from display. Please see [Manage Social Accounts](/docs/apis/profiles/overview#enable-or-disable-social-networks) for more information.
## Manage Active User Profiles
Please see here for some recommendations on [managing active User Profiles](/docs/multiple-users/manage-user-profiles#managing-active-user-profiles).
## Important Security Considerations
> [!WARNING]
>
>
>
Avoid sharing Profile Keys publicly or exposing them in client-side code or public repositories.
>
Profile Keys are sensitive credentials used to authenticate and authorize access to User Profiles.
>
It is crucial to store Profile Keys securely in your system with appropriate access controls to maintain the integrity and confidentiality of your users' data.
>
For security reasons, the Profile Key can't be retrieved again using the API. However, you can retrieve the Profile Key from the [dashboard](/docs/multiple-users/manage-user-profiles#get-the-profile-key).
>
The `refId` should also be stored to associate a profile to an endpoint return.
>
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**title** `string` *required*
Title of the new profile. Must be unique. This title will be displayed on the social account linking page.
**messagingActive** `boolean` (default: false)
Set to true to activate messaging for this user profile. Messaging must first be enabled for your Ayrshare account.
**hideTopHeader** `boolean` (default: false)
Hide the top header on the social accounts linkage page.
**hideLogo** `boolean` (default: false)
Hide the company logo on the social accounts linkage page for this user profile only. The account-wide logo on other profiles is unaffected. Useful for white-labeling individual partner profiles while keeping branding on the rest.
**topHeader** `string`
Change the header on the social accounts linkage page. If not set, then displays: "Social Accounts for "title" where "title" is the profile title.
**disableSocial** `array`
Array of social networks that are disabled for this user's profile. The primary profile's list of disabled social networks takes precedence.
Available networks: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, and `youtube`.
See [enable or disable social networks](/docs/apis/profiles/overview#enable-or-disable-social-networks) for more information.
**team** `boolean` (default: false)
Create a new user profile as a team member by setting `team: true`. The `email` field will be used to send an invite email. See [inviting a team member](/docs/multiple-users/manage-user-profiles#invite-a-team-member) for details on the requirements.
**email** `string`
A valid email address where the team member invite will be sent. Required if `team: true`
**subHeader** `string`
Change the sub header on the social accounts linkage page. Currently displays "Click an icon to link a social network". Set to an empty string to remove.
See how to change the [help link](/docs/multiple-users/manage-user-profiles#help-links-visible).
**tags** `array`
Tag user profiles using an array of strings. These tags serve as an internal organizational tool to categorize and manage your user profiles effectively.
**demo** `boolean` (default: false)
Create the profile as a [Demo User Profile](/docs/apis/profiles/overview#demo-user-profiles) — a 30-day evaluation profile that automatically converts to a standard User Profile after 30 days.
Demo User Profiles are available on the Enterprise plan and must be enabled for your account — [contact us](mailto:support@ayrshare.com) if you'd like to learn more. Returns HTTP 403 with error code 480 if not enabled.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"title": "ACME Profile"}' \
-X POST https://api.ayrshare.com/api/profiles
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/profiles", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
title: "ACME Profile", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'title': 'ACME Profile'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/profiles',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/profiles',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'title' => ['ACME Profile'],
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"log"
"net/http"
)
func main() {
message := map[string]interface{}{
"title": "ACME Profile"
}
bytesRepresentation, err := json.Marshal(message)
if err != nil {
log.Fatalln(err)
}
req, _ := http.NewRequest("POST", "https://api.ayrshare.com/api/profiles",
bytes.NewBuffer(bytesRepresentation))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
req.Header.Add("Authorization", "Bearer API_KEY")
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Error:", err)
}
res.Body.Close()
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CreateProfilePOSTRequest_csharp
{
class CreateProfile
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/profiles";
string json = "{\"title\": \"ACME Profile\"}";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"title": "Digg It",
"refId": "140b8709bd6ade099b242d895e268fb886130c53",
"profileKey": "7TVRLEZ-24A43C0-NJW0Z82-F11984N",
"messagingActive": true // true if messaging for this user profile is active
}
```
```json 400: Profile Title Already Exists theme={"system"}
{
"action": "create",
"status": "error",
"code": 146,
"message": "Profile title already exists."
}
```
```json 403: Demo Profiles Not Enabled theme={"system"}
{
"action": "create profiles",
"status": "error",
"code": 480,
"message": "Demo profiles are not enabled for this account. Demo profiles require an Enterprise plan and must be enabled by Ayrshare."
}
```
# Delete a User Profile
Source: https://www.ayrshare.com/docs/apis/profiles/delete-profile
DELETE /profiles
Permanently delete a User Profile you own and all associated posts by Profile Key or title. Learn about rate limits and how deletion affects billing.
Delete a user profile you are the owner of. The Profile Key in the header parameter is the User Profile to be deleted.
> [!WARNING]
>
> Deleting a user profile permanently removes the profile and all associated posts—this action is
> irreversible and cannot be undone. You can delete up to 8 user profiles per second, so please
> stagger your API calls when performing bulk deletions to stay within rate limits.
> [!NOTE]
>
> Each monthly billing period is charged on the highest number of user profiles reached on any
> single day of that period, and never on fewer than the minimum included in your plan — deleting
> profiles while you are at that minimum does not lower the charge. That high-water mark only ever
> rises: deleting a profile stops later days from pushing it higher, but it cannot bring down a
> figure the period has already reached, so once your peak is behind you the saving lands in your
> next billing cycle. This does not apply to the Launch plan, which has a flat price, or to custom
> and Enterprise agreements, whose own terms apply. To see your daily user profile counts, call
> [Get User Profiles](/docs/apis/profiles/get-profiles) with `actionLog` set to `true`.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**title** `string` *required* (default: 0)
Title of the User Profile to delete. Must be present if `profileKey` is not passed. `title` is
case-sensitive and must match the User Profile title.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-H 'Profile-Key: PROFILE_KEY' \
-X DELETE https://api.ayrshare.com/api/profiles
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const profileKey = "PROFILE_KEY";
fetch("https://api.ayrshare.com/api/profiles", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
"Profile-Key": profileKey
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY', 'Profile-Key': 'PROFILE_KEY'}
r = requests.delete('https://api.ayrshare.com/api/profiles',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
'Profile-Key: ' . $profileKey
],
]);
$response = curl_exec($curl);
if (curl_errno($curl)) {
echo 'Error:' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
var requestUri = "https://api.ayrshare.com/api/profiles";
var bearerToken = "Bearer API_KEY";
var profileKey = "PROFILE_KEY";
using var request = new HttpRequestMessage(HttpMethod.Delete, requestUri);
request.Headers.Add("Authorization", bearerToken);
request.Headers.Add("Profile-Key", profileKey);
try
{
using var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
```
```javascript 200: Success theme={"system"}
{
"status": "success",
"refId": "823nd82nd92jsnn2932"
}
```
```javascript 500: Error Deleting theme={"system"}
{
"action": "delete",
"status": "error",
"code": 147,
"message": "Error deleting profile."
}
```
```javascript 403: Profile Key Not Found theme={"system"}
{
"action": "post",
"status": "error",
"code": 144,
"message": "Some profiles not found. Please verify the Profile Keys."
}
```
# Generate a Linking URL (generateJWT)
Source: https://www.ayrshare.com/docs/apis/profiles/generate-jwt
POST /profiles/generateJWT
Create a social-linking URL for a User Profile with the generateJWT endpoint.
Create a social-linking URL for a User Profile. The endpoint is still named `generateJWT`, but the token it returns is opaque and is not a JWT.
See the [Social Linking Overview](/docs/apis/profiles/social-linking-overview) for more details.
> [!NOTE]
>
> The linking URL is valid for **5 minutes**. After 5 minutes you must generate a new linking URL.
> See the Max Pack `expiresIn` for [additional options](/docs/apis/profiles/social-linking-overview#jwt-expires-in).
> [!NOTE]
>
> **This endpoint is deprecated.** It is fully supported, has no removal date, and keeps working
> unchanged — it is simply no longer the recommended way to create a linking URL.
>
> The returned `token` is an **opaque string** beginning `ayr_ls_` — it is not a
> decodable JWT and carries no readable payload. Pass the `url` to your user as
> returned, and treat `token` as a credential you store rather than inspect.
> Links you created earlier still work.
>
> `POST /profiles/link-sessions` is the same operation without the legacy
> parameters, and it also lets you check or revoke a link. See
> [Create a Link Session](/docs/apis/profiles/create-link-session).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**X-Twitter-OAuth1-Api-Key** `string`
Your X API Key (Consumer Key) from the X Developer Portal. Alternative to the `twitterApiKey` body parameter. When provided, the generated linking URL will use your X Developer App for OAuth linking.
**X-Twitter-OAuth1-Api-Secret** `string`
Your X API Secret (Consumer Secret) from the X Developer Portal. Alternative to the `twitterApiSecret` body parameter. Required when `X-Twitter-OAuth1-Api-Key` is provided.
> [!NOTE]
>
> **Recommended:** Pass your X credentials via headers (`X-Twitter-OAuth1-Api-Key` and `X-Twitter-OAuth1-Api-Secret`) for consistency with all other Ayrshare API endpoints. The `twitterApiKey` and `twitterApiSecret` body parameters are still supported for backward compatibility.
## Body Parameters
**domain** `string`
Domain of app. Please use the exact domain given during onboarding.
Optional. When omitted, your account's own domain is used. If you do send it, it must be a domain registered to your account.
**privateKey** `string` *deprecated*
No longer used. Accepted and ignored, so existing integrations keep working without a change.
Linking URLs are no longer signed, so there is no key to send. You can remove this field, and stop storing the private key for this purpose.
If your integration also signs its own tokens, keep the private key. See [Self-Signed Tokens (Legacy)](/docs/apis/profiles/social-linking-overview#self-signed-tokens).
**profileKey** `string` *required*
User Profile Key. The API Key cannot be used in this field.
**logout** `boolean` (default: false)
Automatically logout the current session. Recommend not to use in production since it affects the performance.
See [Automatic Logout of a Profile Session](/docs/multiple-users/api-integration-business#automatic-logout-of-a-profile-session) for more information.
**redirect** `string`
Specify a URL to redirect to when the "Done" button or logo image is clicked. The URL will be automatically shortened in the returned linking URL. [Redirect the origin ](/docs/multiple-users/api-integration-business#opening-&-closing-the-social-linking-url)opener window by adding the query parameter `origin=true` to the redirect URL.
**allowedSocial** `array`
Specify the social networks to display in the linking page. This will override the social networks configured in the [Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access) page.
```json Only display Facebook, X/Twitter, LinkedIn, and TikTok theme={"system"}
{
"allowedSocial": ["facebook", "twitter", "linkedin", "tiktok"]
}
```
**instagramLinkMethod** `string`
Override which Instagram linking flow is used when a user clicks the Instagram button on the social linking page for this URL. Valid values:
* `instagram`: Direct Instagram Login, no Facebook Page required.
* `facebook`: Link Instagram via a connected Facebook Page.
```json Force direct Instagram Login for this linking session theme={"system"}
{
"instagramLinkMethod": "instagram"
}
```
When omitted, the linking page uses your account-wide [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) setting.
See [Instagram Link Method](/docs/apis/profiles/social-linking-overview#instagram-link-method) for more information.
**verify** `boolean` *deprecated*
No longer used. Accepted and ignored.
There is no signed token to verify — the returned `token` is validated by the linking page when your user opens the URL.
**base64** `boolean` *deprecated*
No longer used. Accepted and ignored, since `privateKey` is no longer read.
**expiresIn** `number` (default: 5)
Requires the Max Pack. Otherwise returns error `340` (HTTP 401).
Set the longevity of the token in minutes. Range: 1 minute to 2880 minutes.
See [Link Expiry](/docs/apis/profiles/social-linking-overview#jwt-expires-in) for more information.
**email** `object`
Requires the Max Pack. Otherwise returns error `340` (HTTP 401).
Send a Connect Accounts email with a link for users to directly access their social linkage page.
If the email can't be sent, you still get the link. The response has `emailSent: false` and an `emailError` object, so you can send the link to your user another way.
See [Connect Accounts Email](/docs/apis/profiles/social-linking-overview#connect-accounts-email) for more information.
> [!NOTE]
>
> When you include your X API credentials, the generated linking URL will initiate OAuth linking using your own X Developer App. Your end-users will see your app name on the X consent screen.
> [!WARNING]
>
> **Required:** Before using this feature, you must add these callback URLs to your X Developer App settings (under Authentication settings > Callback URI / Redirect URL):
>
> * `https://profile.ayrshare.com/social-accounts`
> * `https://app.ayrshare.com/social-accounts`
>
> Without these, the OAuth flow will fail with a `403 Callback URL not approved` error.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-H "X-Twitter-OAuth1-Api-Key: YOUR_CONSUMER_KEY" \
-H "X-Twitter-OAuth1-Api-Secret: YOUR_CONSUMER_SECRET" \
-d '{"profileKey": "PROFILE_KEY"}' \
-X POST https://api.ayrshare.com/api/profiles/generateJWT
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const PROFILE_KEY = "PROFILE_KEY";
fetch("https://api.ayrshare.com/api/profiles/generateJWT", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
"X-Twitter-OAuth1-Api-Key": "YOUR_CONSUMER_KEY",
"X-Twitter-OAuth1-Api-Secret": "YOUR_CONSUMER_SECRET"
},
body: JSON.stringify({
profileKey: PROFILE_KEY, // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'profileKey': 'PROFILE_KEY'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY',
'X-Twitter-OAuth1-Api-Key': 'YOUR_CONSUMER_KEY',
'X-Twitter-OAuth1-Api-Secret': 'YOUR_CONSUMER_SECRET'}
r = requests.post('https://api.ayrshare.com/api/profiles/generateJWT',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/profiles/generateJWT',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'json' => [
'profileKey' => 'PROFILE_KEY', // required
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace GenerateJWTRequest_csharp
{
class GenerateJWT
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/profiles/generateJWT";
try
{
var sendData = new
{
profileKey = "PROFILE_KEY"
};
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
string jsonData = JsonConvert.SerializeObject(sendData);
var content = new StringContent(jsonData, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"HTTP request error: {e.Message}");
}
catch (Exception e)
{
Console.WriteLine($"Unexpected error: {e.Message}");
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"title": "User Profile Title",
"token": "ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu",
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=PROVIDED_DOMAIN",
"emailSent": true,
"expiresIn": "5m"
}
```
```json 200: Email Failed to Send theme={"system"}
{
"status": "success",
"title": "User Profile Title",
"token": "ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu",
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=PROVIDED_DOMAIN",
"emailSent": false,
"emailError": {
"code": 333,
"message": "Email failed to send. Please confirm a valid email was provided."
},
"expiresIn": "5m"
}
```
```json 400: Domain Not Registered to Your Account theme={"system"}
{
"action": "JWT",
"status": "error",
"code": 189,
"message": "Error generating JWT. Check the sent parameters.",
"details": "Missing or incorrect domain."
}
```
```json 400: Partial Twitter API Keys theme={"system"}
{
"action": "JWT",
"status": "error",
"code": 188,
"message": "Both twitterApiKey and twitterApiSecret are required. You provided only one."
}
```
```json 400: Duplicate X/Twitter Credentials theme={"system"}
{
"action": "JWT",
"status": "error",
"code": 434,
"message": "X/Twitter API credentials were provided in both the request headers and the request body. Please use the headers only. See https://www.ayrshare.com/docs/apis/profiles/generate-jwt"
}
```
# Get a Link Session
Source: https://www.ayrshare.com/docs/apis/profiles/get-link-session
GET /profiles/link-sessions/{sessionId}
Check whether a social-linking URL has been opened, is still valid, or has been revoked.
Check the state of a linking URL you created with
[Create a Link Session](/docs/apis/profiles/create-link-session) — whether your user has
opened it, whether it is still valid, and how many times it has been used.
Useful when you want to know if a user has started connecting their accounts without
asking them, or to confirm a link is dead before creating a replacement.
> [!NOTE]
>
> A `sessionId` that does not exist, or that belongs to another account, returns the same
> `not found` response. This is deliberate, so the endpoint cannot be used to discover
> whether an identifier is real.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**sessionId** `string` *required*
The `sessionId` returned when the link was created.
## Link States
The `state` field is one of:
* `pending`: created, never opened.
* `active`: opened at least once, still within its window.
* `expired`: past `expiresAt`.
* `revoked`: killed with [Revoke a Link Session](/docs/apis/profiles/revoke-link-session).
A revoked link reports `revoked` even after its window passes, since that is the more
useful answer.
## Connected Accounts
When your user connects an account, the link records it. Three fields appear once
something has been connected, and are absent until then:
* `completedAt`: when the first account was connected.
* `lastCompletedAt`: when the most recent one was.
* `completedNetworks`: every network connected on this link.
These are separate from `state`, and both answer real questions: a link can be `active`
and have connected nothing yet, or `expired` and have connected three accounts before it
ran out. Polling for `completedNetworks` is how a server-rendered app or a mobile app
knows a connection finished, and it is the only signal for Telegram, which completes out
of band.
> [!WARNING]
>
> **`completedNetworks` is a history of this link session, not the profile's current
> state.** It is append-only: a network is added when it connects through this session and
> is never removed, so it stays listed after the account is unlinked, relinked through a
> different session, or disconnected on the network's side. To see which networks are
> linked to the User Profile **right now**, call
> [Get User Profiles](/docs/apis/profiles/get-profiles) with `include=state`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Profile-Key: PROFILE_KEY' \
-X GET https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const PROFILE_KEY = "PROFILE_KEY";
const SESSION_ID = "SESSION_ID";
fetch(`https://api.ayrshare.com/api/profiles/link-sessions/${SESSION_ID}`, {
headers: {
Authorization: `Bearer ${API_KEY}`,
"Profile-Key": PROFILE_KEY,
},
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY',
'Profile-Key': 'PROFILE_KEY'}
response = requests.get(
'https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID',
headers=headers)
print(response.json())
```
```php PHP theme={"system"}
request(
'GET',
'https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID',
[
'headers' => [
'Authorization' => 'Bearer API_KEY',
'Profile-Key' => 'PROFILE_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace GetLinkSession_csharp
{
class GetLinkSession
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string PROFILE_KEY = "PROFILE_KEY";
string url = "https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID";
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
client.DefaultRequestHeaders.Add("Profile-Key", PROFILE_KEY);
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"HTTP request error: {e.Message}");
}
}
}
}
```
```json 200: Opened Once, Still Valid theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"state": "active", // pending | active | expired | revoked - see Link States above.
"createdAt": "2026-09-01T08:03:26.838Z", // When the link was created, as an ISO 8601 timestamp.
"expiresAt": "2026-09-02T08:03:26.838Z", // When the link stops working.
"firstUsedAt": "2026-09-01T08:14:02.104Z", // When the link was first opened. Absent if it never has been.
"lastUsedAt": "2026-09-01T08:14:02.104Z", // When the link was most recently opened. Absent if it never has been.
"useCount": 1, // How many times the link has been opened. A reload or an OAuth retry increments this.
"completedAt": "2026-09-01T08:15:44.870Z", // When the first account was connected. Absent until one is.
"lastCompletedAt": "2026-09-01T08:16:20.412Z", // When the most recent was connected. Absent until one is.
"completedNetworks": ["reddit", "bluesky"] // Every network connected on this link, append-only. Absent until one is.
}
```
```json 200: Created, Never Opened theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"state": "pending", // Created but never opened, so firstUsedAt and lastUsedAt are absent.
"createdAt": "2026-09-01T08:03:26.838Z",
"expiresAt": "2026-09-02T08:03:26.838Z",
"useCount": 0
}
```
```json 404: Not Found theme={"system"}
{
"action": "link session",
"status": "error",
"code": 502,
"message": "Social linking session not found. Please request a new linking URL."
}
```
# Get User Profiles
Source: https://www.ayrshare.com/docs/apis/profiles/get-profiles
GET /profiles
Get the User Profiles under your Primary Profile, filtered by title, refId, social accounts, or BYOK status, or get the profile create and delete log.
Get all the profiles associated with the primary profile. The Primary Profile is not returned in the results.
For security, the Profile Keys are not returned via this GET call. Please see [here](/docs/apis/profiles/create-profile) for more information.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Query Parameters
**title** `string`
Return only the profile associated with the URL encoded title.
**refId** `string`
Return only the profile associated with the given `refId`. The refId was returned during the
profile creation or from the /user endpoint.
**hasActiveSocialAccounts** `boolean` (default: false)
If `true` return only profiles that have at least one connected social account
(`activeSocialAccounts` length greater than zero). If `false` return only profiles with zero
connected social accounts (`activeSocialAccounts` length is zero).
**includesActiveSocialAccounts** `array`
Filter profiles to include those whose `activeSocialAccounts` contain all of the social media platforms specified in the `includesActiveSocialAccounts` list. Profiles can have additional platforms in their `activeSocialAccounts` beyond those listed in `includesActiveSocialAccounts` and still be included in the filtered results.
Values: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, `youtube`.
**isByokLinked** `boolean`
Filter profiles by BYOK (Bring Your Own Key) migration status. If `true`, return only profiles that have completed BYOK migration. If `false`, return only profiles that have a BYOK-eligible platform connected but have not yet migrated. Profiles without a BYOK-eligible platform are excluded when this filter is set. When omitted, all profiles are returned regardless of BYOK status. Currently applies to [X/Twitter BYOK](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys).
**actionLog** `boolean` (default: false)
Return the User Profile create and delete action log history for the past 60 days and the daily User Profile count for the past 60 days. Note:
Action Log: Title and tags will not be returned for action log history prior to March 2025.
User Profile Report: The time period might not correspond to your billing period. For example, your billing period might start and end on the 10th of the month.
Please see your invoices for the billing period and other details.
You may specify a different time period by setting the actionLog to a number of days. For example,
`actionLog=10` query parameter will return the action log for the past 10 days and the daily User Profile count
for the past 10 days. The time period allowed is 1 day to 365 days.
**limit** `number` (default: 5000)
Limit the number of profiles returned. The default and maximum is 5000.
**cursor** `string`
If there are additional profiles to return and the `hasMore` flag is `true`, the `nextCursor` will be returned in the response.
Pass this cursor to the `cursor` query parameter to return the next set of profiles.
**include** `string | array`
Return extended profile data for troubleshooting. Requires `refId` parameter (single profile lookup).
Values (comma-separated string or array): `suspension`, `socialHealth`, `linkingErrors`, `activity`, `quota`, `unlinkHistory`, `actionLog`, `state`.
`unlinkHistory` - Last unlink: platform, source (user/system), details, createdAt
`actionLog` - Profile action history (create/update/delete)
`state` - Per-network: a single normalized `state` value, plus `since`, `actionable`, `message`, and `helpUrl`
Every social network is always present in the `state` block, so a network
that has never been linked reports `unlinked` rather than being omitted.
Exactly one `state` value is returned per network, resolved in this order
when more than one condition applies:
| `state` | Meaning | `actionable` |
| - | - | - |
| `suspended` | The account is suspended, so the network cannot be used or linked. | `false` |
| `unlinked` | The network is not linked to this profile. | `true` |
| `identityVerificationRequired` | Meta requires the account holder to re-verify their identity. Posting returns `code: 326`. | `true` |
| `restricted` | The access token is no longer valid. Relink to restore access. | `true` |
| `rateLimited` | The account exceeded its API rate limit. Resets automatically at the end of the day. | `false` |
| `linked` | Linked and healthy. | `false` |
`actionable` tells you whether the end user can act to clear the state, so
it is what decides whether your row shows a button. `since` is included
only where a timestamp is recorded for that state, and is omitted
otherwise - notably for `restricted`, which records no date.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/profiles
```
```javascript cURL (with include) theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET "https://api.ayrshare.com/api/profiles?refId=160c8700bd6ade&include=suspension,socialHealth,activity,quota"
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/profiles", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/profiles', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPGET => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey
],
]);
$response = curl_exec($curl);
if (curl_errno($curl)) {
echo 'Error:' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace ProfilesGETRequest_csharp
{
class Profiles
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/profiles";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```javascript 200: Success theme={"system"}
{
"profiles": [
{
"status": "active",
"title": "Digg It Title",
"displayTitle": "Your title",
"created": {
"_seconds": 1604094099,
"_nanoseconds": 530000000
},
"createdUTC": "2022-03-02T16:11:00.839Z",
"refId": "160c8700bd6ade099b242d845e268fb986130c53",
"activeSocialAccounts": [
"twitter",
"facebook",
"linkedin",
"instagram"
],
"isByokLinked": true
},
{
"status": "active",
"title": "Super Profile",
"created": {
"_seconds": 1604377627,
"_nanoseconds": 252000000
},
"createdUTC": "2022-03-02T16:11:00.839Z",
"refId": "170a8700bd6ade099b242d845e268fb986130c53"
},
{
"status": "suspended",
"title": "Good Fun Title",
"created": {
"_seconds": 1605107864,
"_nanoseconds": 96000000
},
"createdUTC": "2022-03-02T16:11:00.839Z",
"refId": "180s8700bd6ade099b242d845e268fb986130c53",
"activeSocialAccounts": [
"facebook",
"linkedin",
"youtube"
],
"suspended": true
}
],
"count": 100,
"lastUpdated": "2025-06-14T15:53:26.016Z",
"nextUpdate": "2025-06-14T15:58:26.016Z",
"pagination": {
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkIjoiMjAyNS0wNi0xNFQwMjo1",
"limit": 100
}
}
```
```json 200: Action Log theme={"system"}
{
"profiles": {
"actionLog": [
{
"action": "create",
"refId": "2d83hd839282ehd892d2999912d1dsdgldfkepw",
"title": "Profile 1",
"created": "2025-05-29T14:10:33.709Z"
},
{
"action": "create",
"refId": "fmm02nd9c3nm9djjffdfsfshfihvp848jcsf222s",
"title": "Profile 2",
"created": "2025-05-28T20:33:32.186Z"
}
],
"userProfileReport": [
{
"reported": "2025-04-01T08:00:16.632Z",
"userProfilesCount": 135
},
{
"reported": "2025-03-31T08:00:22.651Z",
"userProfilesCount": 135
}
]
},
"lastUpdated": "2025-06-14T15:56:02.260Z",
"nextUpdate": "2025-06-14T16:01:02.260Z"
}
```
```json 200: Include Extended Data theme={"system"}
{
"profiles": [
{
"title": "Brand Marketing",
"refId": "160c8700bd6ade099b242d845e268fb986130c53",
"status": "suspended",
"activeSocialAccounts": ["twitter", "instagram"],
"suspension": {
"isSuspended": true,
"reason": "Too many duplicate posts",
"suspendedAt": "2026-03-01T10:00:00.000Z",
"unsuspendAt": "2026-03-03T10:00:00.000Z",
"suspensionCount": 2
},
"socialHealth": {
"twitter": {
"linked": true,
"linkedAt": "2026-01-15T10:30:00.000Z",
"relinkRecommended": false,
"messagingEnabled": true,
"tokenExpiresAt": null
},
"instagram": {
"linked": true,
"linkedAt": "2026-02-01T08:00:00.000Z",
"relinkRecommended": true,
"messagingEnabled": false,
"tokenExpiresAt": "2026-04-01T08:00:00.000Z"
}
},
"activity": {
"lastApiCall": "2026-03-05T09:15:00.000Z",
"lastPost": "2026-03-04T14:30:00.000Z"
},
"quota": {
"used": 450,
"limit": 1000
}
}
],
"count": 1,
"pagination": {
"hasMore": false,
"nextCursor": null,
"limit": 5000
}
}
```
# Profiles API Overview
Source: https://www.ayrshare.com/docs/apis/profiles/overview
Create and manage User Profiles, call endpoints on their behalf with the Profile Key, enable or disable social networks, and use Demo User Profiles.
> [!NOTE]
>
> Specify a User Profile in an API call by [adding the Profile Key in the header](/docs/apis/overview#profile-key-format).
User Profiles are one of the core concepts of Ayrshare that allows you to create and manage multiple users. Each one of the users of your platform will have one or more Ayrshare User Profiles. Each User Profile can have one connection to every supported social network.
## Profile Key
Many endpoints, such as /user, /analytics, or /delete, can be called on behalf of a Profile account by [adding the "Profile-Key" parameter](/docs/apis/overview#profile-key-format) in the Header.
For example, the /delete endpoint can be called to delete a Profile account's post for the given *post* *id* for the provided *Profile Key*.
```javascript theme={"system"}
const API_KEY = "API_KEY";
const PROFILE_KEY = "PROFILE_KEY";
const id = "Post ID";
fetch("https://api.ayrshare.com/api/delete", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
"Profile-Key": PROFILE_KEY
},
body: JSON.stringify({
id: id
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
## Enable or Disable Social Networks
You can disable and enable social networks at either the global level or at the User Profile level.
[Globally manage social account access](/docs/multiple-users/manage-user-profiles#set-social-networks-access) in the dashboard.
Manage social account access for a User Profile when [creating](/docs/apis/profiles/create-profile) or [updating](/docs/apis/profiles/update-profile).
It is important to note that disabled social networks in the global setting, set via the dashboard, override User Profile settings.
For example, if you disable TikTok access in the dashboard, then no User Profile can access TikTok. On the other hand, if you turn on TikTok access in the dashboard but disable it for a specific User Profile, all User Profiles will have access to TikTok except that particular one.
## Get the Linked Social Accounts
Retrieve the social accounts a User Profile has linked with either the [/profiles endpoint](/docs/apis/profiles/get-profiles) or the [/user endpoint](/docs/apis/user/profile-details).
## Post to a User Profile
With the [/post](/docs/apis/post/post) endpoint, you can post to a User Profile by adding the Profile Key in the header. The return will be an array of posts or error results, with an overall status. If all posts are successful, "success", or if any post failed, "error".
You may obtain a user's Profile Key when the profile is created with the [/create-profile](/docs/apis/profiles/create-profile) endpoint or in Ayrshare's Web Dashboard GUI by switching to the profile and going to **Profile Key** page.
## Demo User Profiles
Demo User Profiles let you offer a prospective end user a 30-day evaluation of your platform's social features before committing them to a standard User Profile.
Create one by setting `demo: true` when [creating a profile](/docs/apis/profiles/create-profile). A Demo User Profile works just like a standard User Profile — your user can link social accounts, post, and retrieve analytics using its Profile Key.
After 30 days, the Demo User Profile automatically converts to a standard User Profile. If you have a `demo` [webhook](/docs/apis/webhooks/overview) registered, you'll receive an `upgradeWarning` event two days before the conversion and an `upgrade` event when the conversion happens, so you can prompt your user (or remove the profile) beforehand.
> [!NOTE]
>
> Demo User Profiles are available on the Enterprise plan and must be enabled for your account. Please [contact us](mailto:support@ayrshare.com) if you'd like to learn more.
# Revoke a Link Session
Source: https://www.ayrshare.com/docs/apis/profiles/revoke-link-session
DELETE /profiles/link-sessions/{sessionId}
Kill a social-linking URL before it expires so it can no longer be used.
Kill a linking URL before it expires. Once revoked, opening it does nothing — your user
sees an expired-link page instead of their profile.
Use this when a link went to the wrong person, was sent over a channel you no longer
trust, or is simply no longer wanted. Revoking is the reason a link session is worth
creating over the older signed URLs, which could not be recalled once sent.
> [!NOTE]
>
> Revoking is idempotent. Revoking an already-revoked link succeeds and returns the same
> response, so a retry is always safe.
> [!NOTE]
>
> Revoking also removes any X/Twitter credentials you supplied for this link. Accounts
> your user already connected through the link stay connected — revoking kills the link,
> not the social accounts.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**sessionId** `string` *required*
The `sessionId` returned when the link was created.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Profile-Key: PROFILE_KEY' \
-X DELETE https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const PROFILE_KEY = "PROFILE_KEY";
const SESSION_ID = "SESSION_ID";
fetch(`https://api.ayrshare.com/api/profiles/link-sessions/${SESSION_ID}`, {
method: "DELETE",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Profile-Key": PROFILE_KEY,
},
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY',
'Profile-Key': 'PROFILE_KEY'}
response = requests.delete(
'https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID',
headers=headers)
print(response.json())
```
```php PHP theme={"system"}
request(
'DELETE',
'https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID',
[
'headers' => [
'Authorization' => 'Bearer API_KEY',
'Profile-Key' => 'PROFILE_KEY'
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace RevokeLinkSession_csharp
{
class RevokeLinkSession
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string PROFILE_KEY = "PROFILE_KEY";
string url = "https://api.ayrshare.com/api/profiles/link-sessions/SESSION_ID";
try
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
client.DefaultRequestHeaders.Add("Profile-Key", PROFILE_KEY);
HttpResponseMessage response = await client.DeleteAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"HTTP request error: {e.Message}");
}
}
}
}
```
```json 200 theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"state": "revoked", // Always "revoked" on success.
"revokedAt": "2026-09-01T09:22:11.507Z" // When the link was revoked. On a repeat revoke, the ORIGINAL time - not the retry.
}
```
```json 404: Not Found theme={"system"}
{
"action": "link session",
"status": "error",
"code": 502,
"message": "Social linking session not found. Please request a new linking URL."
}
```
# Social Linking Overview
Source: https://www.ayrshare.com/docs/apis/profiles/social-linking-overview
How your users connect their social accounts — the three surfaces, the link session behind all of them, and the options they share.
Your users connect their own social accounts themselves — they authenticate directly with each
network, and you never see or store their credentials. Everything on this page is about getting
them to that moment and finding out how it went.
One thing is common to every route: a **link session**, created with
[Create a Link Session](/docs/apis/profiles/create-link-session) from your API key and a `Profile-Key`.
Nothing is signed on your side and there is no private key in the flow. The one exception is older
integrations that sign their own tokens. See [Self-Signed Tokens (Legacy)](#self-signed-tokens).
## Three Ways to Connect
Three shapes, and the first two are the same integration. Pick per flow — they are not
exclusive, and plenty of integrations use the hosted page for onboarding and the widget inside
the app afterwards.
| Surface | Your user sees | White-labelling | Choose it when |
| - | - | - | - |
| **Widget — embedded frames** ([mount](/docs/multiple-users/connect-widget#mount-a-slot)) | our buttons inline in your own layout; no popup until the network's own | **Strongest.** Your page, your fonts and colours, and your user never leaves it | you have a dashboard with a row per network and want linking to happen in place. Max Pack. |
| **Widget — your own button** ([popup](/docs/multiple-users/connect-widget#your-own-button)) | your button, then one popup for the network | **Strong.** The popup is ours, but it is brief and inherits your appearance | you want your own button and styling, and no frames in your layout. Max Pack. |
| **Hosted linking page** ([how to](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | a page we host, carrying your logo, colours and custom CSS | **Weakest.** It is our page, and your user leaves yours to use it | you want one link to hand out, or you are onboarding by email. Nothing to build, no Max Pack. |
> [!NOTE]
>
> The two widget rows are **one integration**, not two. A single `init` gives you both: mount frames
> where you want our buttons, and call `popup()` from your own button everywhere else. They share
> one session and report on the same handlers.
>
> [Direct mode](/docs/multiple-users/connect-direct-mode) is the same popup **without** our script — for
> a page with a strict Content-Security-Policy, a server-rendered page, or a native app. There you
> open and watch the popup yourself.
>
> **Not sure?** Start with the hosted linking page. It needs no Max Pack and no extra parameters,
> and it is the fastest route to something working — moving to the widget later does not change how
> sessions are created.
## Creating a Link
Call [Create a Link Session](/docs/apis/profiles/create-link-session) with the user's `Profile-Key` in
the header. For the hosted page that is the whole request:
```bash cURL theme={"system"}
curl -X POST https://api.ayrshare.com/api/profiles/link-sessions \
-H "Authorization: Bearer $AYRSHARE_API_KEY" \
-H "Profile-Key: $PROFILE_KEY" \
-H "Content-Type: application/json" \
-d '{}'
```
You get back a `url` carrying a short-lived, opaque token:
```javascript Linking URL theme={"system"}
https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN
```
You can also [check whether a link has been opened](/docs/apis/profiles/get-link-session) and
[revoke it](/docs/apis/profiles/revoke-link-session) before it expires.
> [!NOTE]
>
> This one-minute video walks through creating a link. It predates link sessions, so it still shows
> a Private Key being sent — that step is no longer needed, and everything else it shows is
> unchanged.
>
> [Create a linking URL](https://www.youtube.com/embed/JI232HBWHWc)
### Sending the Linking URL
> [!WARNING]
>
> A linking URL signs your user into their profile, so treat it like a password. Send it over a
> channel you trust, don't log it, and don't pass it on to a third party. It stays usable for its
> whole window, so a reload or an OAuth retry works — but send each link to one user only, and
> create a separate link per person.
### Opening the Linking URL
Open it in a new browser tab, window, or view controller. You may control the
[closing or redirecting](/docs/multiple-users/api-integration-business#opening-&-closing-the-social-linking-url)
of that window.
> [!NOTE]
>
> The social networks do not allow the hosted linking page to be opened inside an iFrame, or the
> approved partner origin `profile.ayrshare.com` to be obfuscated. If you want linking to happen
> inside your own page, that is what the [embedded widget](/docs/multiple-users/connect-widget) is for:
> its frames are served from an Ayrshare origin and are the supported way to do it.
### Knowing When It Is Done
Two signals, and you can use either:
* **[Link completion events](/docs/multiple-users/link-completion-events)** — set an `origin` when you
create the link and the linking window posts `connect:success`, `connect:error` and
`connect:cancelled` to your page as they happen. No polling.
* **[Get a Link Session](/docs/apis/profiles/get-link-session)** — reports `completedAt`,
`lastCompletedAt` and `completedNetworks`. This is the signal for native apps, and the only one
for Telegram, which completes out of band.
## Link Expiry
A link is valid for **5 minutes** by default. After that, create a new one.
With the Max Pack, set `expiresIn` in minutes to widen that window — up to **2880 minutes
(48 hours)**, which is the maximum the API accepts:
```json Expires In theme={"system"}
{
"expiresIn": 30
}
```
A longer window is what makes [emailing the link](#connect-accounts-email) practical — a user
reconnecting a dropped account can go straight from your email to the network, without visiting
your app first.
> [!WARNING]
>
> Review with your security team how long a link should stay alive. A longer window is a longer
> period in which an intercepted link still works. If one gets out, you can
> [revoke it](/docs/apis/profiles/revoke-link-session) rather than waiting for it to expire.
## Security
The session token travels in the linking URL's query string. Anything that records URLs can record
it: the user's browser history, proxy and server logs, analytics tools, and redirect services.
To limit the risk:
* **Don't log the full URL** in your own systems. Log the profile and the time instead.
* **Send the URL straight to the user.** Don't pass it through link shorteners, redirect trackers
or email click-tracking.
The [Connect Accounts Email](#connect-accounts-email) sends the link without click-tracking.
* **Keep the window short.** Use the shortest [`expiresIn`](#jwt-expires-in) that works for you (Max Pack).
* **Revoke a link** you think has leaked, using
[Revoke a Link Session](/docs/apis/profiles/revoke-link-session).
## Profile Key
The `Profile-Key` identifies which User Profile the link is for. Find it in the Ayrshare developer
dashboard by switching to that profile.
> [!NOTE]
>
> **You don't need the Private Key to create links.** Links from the API aren't signed, so there's
> nothing to read from a file or paste into your code. The legacy `privateKey` parameter is still
> accepted and ignored, so existing integrations keep working.
>
> If your integration signs its own tokens, keep your `private.key`. See
> [Self-Signed Tokens (Legacy)](#self-signed-tokens).
## Switching Profiles
If a profile is already signed in, opening another profile's link does not switch profiles — that
is deliberate, and it keeps the experience fast for a user who is already there. To force a switch,
see
[Automatic Logout of a Profile Session](/docs/multiple-users/api-integration-business#automatic-logout-of-a-profile-session).
## Instagram Link Method
Instagram accounts can be linked in [two ways](/docs/dashboard/connect-social-accounts/instagram):
directly with **Instagram Login**, or via a **connected Facebook Page**. Which flow starts when a
user clicks the Instagram button is normally controlled by the account-wide
[Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) setting.
The `instagramLinkMethod` body parameter overrides that setting for a single link:
| Value | Instagram Linking Flow |
| - | - |
| `instagram` | Direct Instagram Login. No Facebook Page required. |
| `facebook` | Link via a connected Facebook Page. |
```json Instagram Link Method theme={"system"}
{
"instagramLinkMethod": "instagram"
}
```
The override applies for the life of that link, including across the Instagram/Facebook
authorization redirect. A few things to know:
* It does not change your account-wide setting or affect any other link.
* Omit it and the account-wide setting applies, exactly as before.
* An invalid value returns `400` listing the valid values (`instagram`, `facebook`).
* Review the
[feature differences](/docs/multiple-users/manage-user-profiles#direct-instagram-login-vs-facebook-page-authentication)
before choosing — some Instagram features, such as hashtag search and collaborations, are only
available with Facebook Page authentication.
## Connect Accounts Email
Ayrshare can email the link to your user for you, so they can reach their linking page without
visiting your app. Pair it with a longer [`expiresIn`](#jwt-expires-in) — the default five minutes
rarely survives an inbox.
### Connect Accounts JSON
**Every field inside `email` is required.** A missing one fails the send.
```json Example Contact Email Request theme={"system"}
{
"expiresIn": 60,
"email": {
"to": "john@user.com",
"contactEmail": "support@mycompany.com",
"company": "ACME",
"termsUrl": "https://www.ayrshare.com/terms",
"privacyUrl": "https://www.ayrshare.com/privacy"
}
}
```
> [!WARNING]
>
> `expiresIn` is a **top-level** parameter, not part of the `email` object. Nested inside `email` it
> is ignored, and your user gets a link that expires in five minutes.
The response reports the outcome in `emailSent`:
```json Example Contact Email Response theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN",
"expiresAt": "2026-09-02T09:03:26.838Z",
"emailSent": true,
"title": "Acme Client"
}
```
If the email can't be sent, the request still succeeds and you still get the link. The response
has `emailSent: false` plus an `emailError` object, so you can send the link to your user another
way:
```json Example Failed Email Response theme={"system"}
{
"status": "success",
"sessionId": "c7a2434e72e91bde27579efde0fd6dd0b74ceee29a471fd368407f273708c2e8",
"url": "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN",
"expiresAt": "2026-09-02T09:03:26.838Z",
"emailSent": false,
"emailError": {
"code": 333,
"message": "Email failed to send. Please confirm a valid email was provided."
},
"title": "Acme Client"
}
```
`emailError` appears whenever you asked for an email and it was not sent: either the send failed,
or the `email` object was missing a required field, in which case `details` names the missing
fields. `emailSent: false` with no `emailError` means no email was requested.
A missing `to` is different: the whole request is rejected with `code: 188`.
### Connect Accounts Email Example
Here is an example of the email that opens the social linking page:

The email comes from:
`Social Connect Hub `
## Mobile Apps
Open the linking URL in the **system browser**, never an embedded webview: Google rejects sign-in
in one with `disallowed_useragent`, and Meta blocks it outright. Your user would see the network's
own error page, and nothing on your side fixes it.
* **iOS** — `ASWebAuthenticationSession`, or `SFSafariViewController`.
* **Android** — Chrome Custom Tabs.
Because a native app has no browser window to post events to, get the result from
[Get a Link Session](/docs/apis/profiles/get-link-session) instead. Set `origin` to your custom scheme
(`myapp://connected`) so the page has a way back into your app.
### Mobile Code Examples
Replace `linkingURL` with the `url` returned from
[Create a Link Session](/docs/apis/profiles/create-link-session).
```swift Swift theme={"system"}
import UIKit
import SafariServices
class ViewController: UIViewController, SFSafariViewControllerDelegate {
var linkingURL = "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme"
override func viewDidLoad() {
super.viewDidLoad()
setupButton()
}
func setupButton() {
let button = UIButton(type: .system)
button.frame = CGRect(x: (view.bounds.width - 200) / 2, y: (view.bounds.height - 50) / 2, width: 200, height: 50)
button.setTitle("Open URL", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
view.addSubview(button)
}
@objc func buttonTapped() {
openURLInInAppBrowser()
}
func openURLInInAppBrowser() {
if let url = URL(string: linkingURL) {
let safariVC = SFSafariViewController(url: url)
safariVC.delegate = self
present(safariVC, animated: true, completion: nil)
}
}
// Optional: If you want to handle when the in-app browser is closed
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
```
```dart Flutter theme={"system"}
/** yaml dependencies
dependencies:
flutter:
sdk: flutter
url_launcher: ^6.2.1
*/
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'URL Launcher Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final String linkingURL = "https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('URL Launcher Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
openURLInBrowser(context);
},
child: Text('Open URL'),
),
),
);
}
void openURLInBrowser(BuildContext context) async {
if (await canLaunch(linkingURL)) {
await launch(linkingURL);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Could not launch $linkingURL'),
),
);
}
}
}
```
```jsx React Native theme={"system"}
/**
• Using the API provided by expo-web-browser,
• which opens a URL in a modal browser window that shares cookies
• with the system browser.
• Learn more about expo: https://reactnative.dev/docs/environment-setup?guide=quickstart
• and running the following command:
• expo install expo-web-browser
*/
import React from 'react';
import { StyleSheet, Button, View } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
export default function App() {
const linkingURL = 'https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=acme';
const openURLInBrowser = async () => {
try {
await WebBrowser.openBrowserAsync(linkingURL);
// Optional: WebBrowser.openBrowserAsync returns a promise that resolves with an object containing
// 'type' that can be 'cancelled' or 'dismissed'. You can use this to handle when the browser is closed.
} catch (error) {
console.error(error);
}
};
return (
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
```
## Testing
It is **recommended** to first create a link in [Postman](/docs/testing/postman). Your Integration
Package — on the Primary Profile API Key page of the dashboard — includes a sample Postman config.
Import it, fill in your Profile Key in the `profileKey` *body* field, and click *Send*.
The sample config still pre-fills `privateKey` and `domain`. `privateKey` is ignored, and you can
clear `domain` unless your account has more than one linking domain.
You can also [generate the code from Postman](/docs/testing/postman#auto-generate-api-code-with-postman).
### Bubble.io
> [card] [Bubble linking URL](/docs/packages-guides/bubble#generate-a-linking-url-in-bubble)
## Legacy: generateJWT
> [!NOTE]
>
> [Generate a Linking URL](/docs/apis/profiles/generate-jwt) (`generateJWT`) does the same job and is
> **deprecated** — fully supported, no removal date, and unchanged for links you have already
> handed out. Its own page documents its parameters, including the three that are now accepted and
> ignored.
>
> Both endpoints run the same validator, so everything on this page applies to either. The one
> difference worth knowing when you migrate: `generateJWT` tolerates three things Create a Link
> Session rejects — an unrecognised `allowedSocial` network, a lone X credential, and a non-string
> `redirect`.
### Self-Signed Tokens (Legacy)
Some older integrations build the linking URL themselves. They sign a token with the `private.key`
from their Integration Package and pass it as `?jwt=`.
**This still works, and there is no removal date.** It's not recommended for new integrations. Use
[Create a Link Session](/docs/apis/profiles/create-link-session) instead.
If your integration signs its own tokens:
* Keep using your `private.key`, and keep it secret.
* Self-signed links can't be checked or revoked with the link-session endpoints.
* Don't reset your Integration Package. A reset retires your current domain and its keys, so links
you've already handed out stop working and you can't sign new ones.
* A self-signed link expires only if its token sets an `exp` time. `expiresIn` and the
link-session default don't apply to it, so keep that time short.
* Treat self-signed link URLs like a password. If one may have leaked, reset your API Key on the
dashboard's API page, then update your integration to use the new key.
This protects your account, but it may not stop the leaked link from working. Contact Support
to block the link.
# Unlink a Social Network
Source: https://www.ayrshare.com/docs/apis/profiles/unlink-social-network
DELETE /profiles/social
Unlink a social network such as TikTok from a user profile. Returns 200 even if not linked. Without a Profile-Key, the Primary Profile is unlinked.
Unlink a social network for a given user profile. For example, if a user profile is linked to TikTok, unlink TikTok by making this endpoint request. A successful 200 response will be returned even if the platform is not linked. If the `Profile-Key` is not provided the Primary Profile's social account will be unlinked.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**platform** `string` *required*
Allow platforms to unlink: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `reddit`, `telegram`, `threads`, `tiktok`, `twitter`, `youtube`.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-H 'Profile-Key: PROFILE_KEY' \
-d '{"platform": "twitter"}' \
-X DELETE https://api.ayrshare.com/api/profiles/social
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/profiles/social", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`,
"Profile-Key": PROFILE_KEY
},
body: JSON.stringify({ platform: "twitter" }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'platform': 'twitter' }
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY',
'Profile-Key': PROFILE_KEY }
r = requests.delete('https://api.ayrshare.com/api/profiles/social',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
'twitter']; // Data to be sent
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
'Profile-Key: ' . $profileKey
],
]);
$response = curl_exec($curl);
if (curl_errno($curl)) {
echo 'Error:' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace UnlinkSocialPOSTRequest_csharp
{
class UnlinkSocial
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string PROFILE_KEY = "PROFILE_KEY"; // Make sure this is defined
string url = "https://api.ayrshare.com/api/profiles/social";
string json = "{\"platform\": \"twitter\"}";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
client.DefaultRequestHeaders.Add("Profile-Key", PROFILE_KEY);
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage
{
Method = HttpMethod.Delete,
RequestUri = new Uri(url),
Content = content
};
HttpResponseMessage response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: OK Successful unlink theme={"system"}
{
"status": "success",
"platform": "twitter",
"refId": "13a9wa9e0df1183b7a6a1fc2c61b8023fa9a32a1"
}
```
```json 400: Bad Request Error response theme={"system"}
{
"action": "post",
"status": "error",
"code": 163,
"message": "Missing, empty, or not valid platforms parameter. Please verify sending an array of valid platforms. .../ayrshare.com/rest-api/endpoints/post"
}
```
# Update a User Profile
Source: https://www.ayrshare.com/docs/apis/profiles/update-profile
PATCH /profiles
Change a User Profile's title, disabled social networks, messaging status, tags, and linkage page header or logo settings, using its Profile Key.
Update an existing profile's title, hide title, list of disabled social platforms, or display the title.
The Profile Key in the header parameter is the User Profile to be updated.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string` *required*
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**disableSocial** `array`
Array of social networks that are disabled for this user's profile. The primary profile's list of disabled social networks takes precedence.
Available networks: `bluesky`, `facebook`, `gmb`, `instagram`, `linkedin`, `pinterest`, `reddit`, `snapchat`, `telegram`, `threads`, `tiktok`, `twitter`, and `youtube`.
See [here](/docs/apis/profiles/overview#enable-or-disable-social-networks) for more information.
**title** `string`
Title of the profile. Must be unique. This title will be displayed on the social account linking page.
**messagingActive** `boolean` (default: false)
Set to true to activate messaging for this user profile. Messaging must first be enabled for your Ayrshare account.
**hideTopHeader** `boolean` (default: false)
Hide the top header on the social accounts linkage page.
**hideLogo** `boolean` (default: false)
Hide the company logo on the social accounts linkage page for this user profile only. The account-wide logo on other profiles is unaffected. Useful for white-labeling individual partner profiles while keeping branding on the rest.
**topHeader** `string`
Change the header on the social accounts linkage page. If not set, then displays: "Social Accounts for "title" where "title" is the profile title.
**tags** `array`
Tag user profiles using an array of strings. These tags serve as an internal organizational tool to categorize and manage your user profiles effectively.
```javascript cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"profileKey": "Jokdf-903Js-j9sd0-Pow02-QS9n3", "title": "ACME Profile"}' \
-X PATCH https://api.ayrshare.com/api/profiles
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/profiles", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
"Profile-Key": "PROFILE_KEY";
},
body: JSON.stringify({
title: "ACME Profile"
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'title': 'ACME Profile'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY',
'Profile-Key': 'PROFILE_KEY'}
r = requests.patch('https://api.ayrshare.com/api/profiles',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
'ACME Profile']; // Your data to be sent
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
'Profile-Key: ' . $profileKey
],
]);
$response = curl_exec($curl);
if (curl_errno($curl)) {
echo 'Error:' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace UpdateProfilePOSTRequest_csharp
{
class UpdateProfile
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string PROFILE_KEY = "PROFILE_KEY"; // Make sure this is defined
string url = "https://api.ayrshare.com/api/profiles";
string json = "{\"title\": \"ACME Profile\"}";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
client.DefaultRequestHeaders.Add("Profile-Key", PROFILE_KEY);
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
var request = new HttpRequestMessage
{
Method = new HttpMethod("PATCH"),
RequestUri = new Uri(url),
Content = content
};
HttpResponseMessage response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"refId": "b1eb30ce50607a40000b220c01c20a88a49fe76f"
}
```
```json 400: Profile Key not found theme={"system"}
{
"action": "post",
"status": "error",
"code": 144,
"message": "Some profiles not found. Please verify the Profile Keys."
}
```
# Delete a Review Reply
Source: https://www.ayrshare.com/docs/apis/reviews/delete-review-reply
DELETE /reviews
Remove a reply you posted on a Google Business Profile review by sending its reviewId and the gmb platform value. Only the reply owner can delete it.
Delete a reply you made on a review. You must be the owner of the reply to delete.
Currently only Google Business Profile reviews are supported.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**platform** `string` *required*
Values: `gmb`
**reviewId** `string` *required*
The review id of the reply to delete.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-d '{"reviewId": "Ut1fWU6XkqkMayHGnJZ", "platform": "gmb"}' \
-X DELETE https://api.ayrshare.com/api/reviews
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/reviews", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
reviewId: "Ut1fWU6XkqkMayHGnJZ", // required
platform: "gmb", // required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'reviewId': 'Ut1fWU6XkqkMayHGnJZ',
'platform': 'gmb'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.delete('https://api.ayrshare.com/api/reviews',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$apiUrl = 'https://api.ayrshare.com/api/reviews';
$apiKey = 'API_KEY'; // Replace 'API_KEY' with your actual API key
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
];
$data = json_encode([
'reviewId' => 'Ut1fWU6XkqkMayHGnJZ', // Replace with your actual review ID
'platform' => 'gmb'
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_DELETE => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace DeleteReviewReplyPOSTRequest_csharp
{
class DeleteReviewReply
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/reviews";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
var json = @"{
""reviewId"": ""Ut1fWU6XkqkMayHGnJZ"",
""platform"": ""gmb""
}";
var content = new StringContent(
json,
Encoding.UTF8,
"application/json"
);
try
{
var response = await httpClient.DeleteAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Reply Deleted theme={"system"}
{
"gmb": {
"action": "delete",
"status": "success",
"id": "AbFvOqmQJ4xMTl2mjZl0h83TcOj_tbjK742M9xobS_MCTLgDjplzi4dBGAsn-71ASv_RGwO5JQOtRA"
}
}
```
```json 400: Delete Error theme={"system"}
{
"gmb": {
"action": "reviews",
"status": "error",
"code": 349,
"message": "Error adding review. Please verify the review is still available.",
"details": "Requested entity was not found."
}
}
```
# Get a Single Review
Source: https://www.ayrshare.com/docs/apis/reviews/get-one-review
GET /reviews/:id
Fetch one Google Business Profile review by its ID, including the rating, review text, reviewer name and profile, and any reply posted by the business.
Retrieve a single review. Currently only Google Business Profile reviews.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**:id** `string` *required*
The review ID.
## Query Parameters
**platform** `string` *required*
Values: `gmb`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/reviews/AbFvOqmQJ4xMTl2mjZl0h83TcOj?platform=gmb
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/reviews/AbFvOqmQJ4xMTl2mjZl0h83TcOj?platform=gmb", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/reviews/AbFvOqmQJ4xMTl2mjZl0h83TcOj?platform=gmb', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
namespace ReviewsGetOne_csharp
{
class ReviewsGetOne
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string reviewId = "AbFvOqmQJ4xMTl2mjZl0h83TcOj";
// Build URL with query parameters
var uriBuilder = new UriBuilder("https://api.ayrshare.com/api/reviews/" + reviewId);
var query = HttpUtility.ParseQueryString(string.Empty);
query["platform"] = "gmb";
uriBuilder.Query = query.ToString();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(uriBuilder.Uri);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Retrieved Review theme={"system"}
{
"gmb": [
{
"created": "2023-07-11T21:23:57.707819Z",
"rating": "FIVE", // Rating ONE, TWO, THREE, FOUR, FIVE
"review": "Been a great experience - like the documentation, example coding, and help desk.",
"reviewReply": {
"reply": "Thank you for the great thoughts.",
"updated": "2024-02-05T22:36:17.989625Z"
},
"reviewer": {
"name": "Mads Max",
"profile": "https://lh3.googleusercontent.com/a-/ALV-UjV-MlTLJh9CdosuaS"
},
"updated": "2023-07-11T21:23:57.707819Z"
}
],
"lastUpdated": "2024-02-05T23:27:03.147Z",
"nextUpdate": "2024-02-05T23:38:03.147Z"
}
```
```json 400: Bad Request theme={"system"}
{
"action": "analytics",
"status": "error",
"code": 323,
"message": "Error getting Google Business Profile analytics. Please try again later or contact us."
}
```
# Get All Reviews
Source: https://www.ayrshare.com/docs/apis/reviews/get-reviews
GET /reviews
Retrieve all reviews for a specified platform. Set the platform query parameter to facebook for Facebook Page ratings or gmb for Google Business Profile.
Retrieve all the reviews for the specified platform. Currently only Facebook Page rating reviews and Google Business Profile reviews.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**platform** `string` *required*
Values: `facebook`, `gmb`
### Request Examples
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/reviews?platform=gmb
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/reviews?platform=gmb", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/reviews?platform=gmb', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
namespace ReviewsGETRequest_csharp
{
class Reviews
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
// Build URL with query parameters
var uriBuilder = new UriBuilder("https://api.ayrshare.com/api/reviews");
var query = HttpUtility.ParseQueryString(string.Empty);
query["platform"] = "gmb";
uriBuilder.Query = query.ToString();
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(uriBuilder.Uri);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Retrieved Reviews theme={"system"}
{
"facebook": [
{
"created": "2023-07-11T21:14:41.000Z",
"id": "376602035206384",
"rating": "positive", // Rating positive, negative
"review": "Makes things easy for connecting social accounts and publishing.",
"reviewer": {
"name": "Sue Smith",
"id": "7283511115003",
"profile": {
"height": 50,
"isSilhouette": false,
// The URL of the profile picture valid for at least 7 days
"url": "https://platform-lookaside.fbsbx.com/platform/profilepic/",
"width": 50
}
}
},
{
"created": "2023-04-19T02:45:05.000Z",
"id": "376602035206385",
"rating": "positive", // Rating positive, negative
"review": "What an amazing product",
"reviewer": {
"name": "John Smith",
"id": "7501289573254",
"profile": {
"height": 50,
"isSilhouette": false,
// The URL of the profile picture valid for at least 7 days
"url": "https://platform-lookaside.fbsbx.com/platform/profilepic/",
"width": 50
}
}
}
],
"lastUpdated": "2024-02-05T22:47:42.996Z",
"nextUpdate": "2024-02-05T22:58:42.996Z"
},
{
"gmb": [
{
"created": "2023-07-11T21:23:57.707819Z",
"id": "AbFvOqkkVAAQYEe2o5asfICQAGTTG",
"rating": "FIVE", // Rating ONE, TWO, THREE, FOUR, FIVE
"review": "Been a great experience - like the documentation, example coding, and help desk.",
"reviewReply": {
"reply": "Thank you for the great thoughts.",
"updated": "2024-02-05T22:36:17.989625Z"
},
"reviewer": {
"name": "Mads Max",
"profile": "https://lh3.googleusercontent.com/a-/ALV-UjV-MlTLJh9CdosuaS_28Dx2aOZi"
},
"updated": "2023-07-11T21:23:57.707819Z"
},
{
"created": "2023-05-09T19:27:04.839089Z",
"id": "FFFvOqkkVAAQYEe2o5asfICQAGTTG",
"rating": "FIVE", // Rating ONE, TWO, THREE, FOUR, FIVE
"review": "Great service, really useful product, well-crafted",
"reviewReply": {},
"reviewer": {
"name": "V the Man",
"profile": "https://lh3.googleusercontent.com/a-/ALV-UjULaIDBWwT"
},
"updated": "2023-05-09T19:27:04.839089Z"
},
{
"created": "2023-05-02T19:04:54.814104Z",
"id": "BBBOqkkVAAQYEe2o5asfICQAGTTG",
"rating": "FIVE",
"review": "Ayrshare has been a top-notch product and much needed for CRM. We integrated the API a while back and the code samples in the docs provided by the team have been great. I've also been happy with the new features they've added like on TikTok and Instagram as well as with their support during the integration process.",
"reviewReply": {},
"reviewer": {
"name": "David Manning",
"profile": "https://lh3.googleusercontent.com/a-/ALV-UjXph_y96HMlmDJMJaEHJ1vGkFseinq1UasKOy"
},
"updated": "2023-05-02T19:04:54.814104Z"
},
{
"created": "2023-04-16T22:30:14.481018Z",
"id": "CCCvOqkkVAAQYEe2o5asfICQAGTTG",
"rating": "FIVE",
"review": "I've been using Ayrshare's API for my real estate SaaS platform for a while now and it has been great. Appreciated the help on my coding bug and really excited try the TikTok direct publishing with my users.",
"reviewReply": {
"reply": "Thank you.",
"updated": "2023-04-16T22:41:10.885472Z"
},
"reviewer": {
"name": "Jason Gould",
"profile": "https://lh3.googleusercontent.com/a-/ALV-UjUnpfGdErOQUC4zz6Bg3HwFHZz4Lly9gMOJA"
},
"updated": "2023-04-16T22:30:14.481018Z"
}
],
"averageRating": 4.12, // Facebook will be 1, 0, or -1 corresponding to positive, neutral, or negative
"totalReviewCount": 5,
"lastUpdated": "2024-02-05T23:05:10.091Z",
"nextUpdate": "2024-02-05T23:16:10.091Z"
}
```
```json 400: Reviews Not Found theme={"system"}
{
"action": "reviews",
"status": "error",
"code": 350,
"message": "Error getting reviews. Please verify there are reviews for this social network."
}
```
# Reviews API Overview
Source: https://www.ayrshare.com/docs/apis/reviews/overview
Use the Review API to get, reply to, and delete reviews on your connected accounts, including Facebook page ratings and Google Business Profile reviews.
Get, reply, and delete reviews on your connected social accounts including Facebook page ratings and Google Business Profile reviews.
See here for [Review API implementation details.](https://www.ayrshare.com/blog/reviews-api-review-management-with-an-api/)
# Reply to a Review
Source: https://www.ayrshare.com/docs/apis/reviews/reply-review
POST /reviews
Post a text reply to a Facebook or Google Business Profile review by sending the platform and review ID, and see the success and error responses returned.
Add a reply to a review. Currently only Facebook and Google Business Profile reviews are supported.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
#### Body Parameters
**platform** `string` *required*
Values: `gmb` or `facebook`
**reviewId** `string` *required*
The review ID.
**reply** `string` *required*
The String text for the review reply.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{"reviewId": "Ut1fWU6XkqkMayHGnJZ", "platform": "gmb", "reply": "An amazing review!"}' \
-X POST https://api.ayrshare.com/api/reviews
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/reviews", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
reviewId: "Ut1fWU6XkqkMayHGnJZ", // required
platform: "gmb", // required
reply: "An amazing review!", //required
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'reviewId': 'Ut1fWU6XkqkMayHGnJZ',
'platform': 'gmb',
'reply': 'An amazing review!'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/reviews',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
'Ut1fWU6XkqkMayHGnJZ', // Replace with your actual review ID
'platform' => 'gmb',
'reply' => 'An amazing review!'
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace ReplyReviewPOSTRequest_csharp
{
class ReplyReview
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/reviews";
// Create JSON payload with proper formatting
string json = @"{
""reviewId"": ""Ut1fWU6XkqkMayHGnJZ"",
""platform"": ""gmb"",
""reply"": [""An amazing review!""]
}";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Added Reply theme={"system"}
{
"gmb": {
"action": "reply",
"status": "success",
"id": "AbFvOqmQJ4xMTl2mjZl0h83TcOj",
"reply": "This one is great",
"platform": "gmb",
}
},
{
"facebook": {
"status": "success",
"platform": "facebook",
"id": "376602035206384_737529881909727",
"reply": "This one is great too",
"action": "reply"
}
}
```
```json 400: Bad Request theme={"system"}
{
"gmb": {
"action": "reviews",
"status": "error",
"code": 349,
"message": "Error adding review. Please verify the review is still available.",
"details": "Requested entity was not found."
}
}
```
# Batch Get All
Source: https://www.ayrshare.com/docs/apis/user/batch-all-users
GET /user/batch
Retrieve user data for all user profiles as a pre-signed URL file, instead of calling the /user endpoint for each user and risking rate-limits.
Retrieve the user data for all user profiles. Use the batch endpoint as an alternative to calling the /user endpoint for each of your users in rapid succession, which may be restricted by rate-limits.
The endpoint will return a pre-signed URL for the file containing all the user profile data. Please note the `urlAvailable` field time for when the file will be accessible.
The pre-signed URL will expire in 7 days after creation. A new file may be generated every 3 minutes.
You may also be notified when the file is ready via the [Batch Action webhook](/docs/apis/webhooks/actions#batch-action).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
```json 200: Success theme={"system"}
{
"success": true,
"url": "https://storage.googleapis.com/batch.ayrshare.com/users/9iskiedwtOddd/users-batch-2024-01-11-22-42.json",
"urlAvailable": "2024-01-11T22:47:36Z",
"urlExpires": "2024-01-18T22:42:36Z",
"lastUpdated": "2024-01-11T22:42:36.808Z",
"nextUpdate": "2024-01-11T22:45:36.808Z"
}
```
```json 403: Forbidden theme={"system"}
{
"action": "authorization",
"status": "error",
"code": 102,
"message": "API Key not valid. Please be sure to send a Header Authorization containing 'Bearer API_KEY'. .../ayrshare.com/rest-api/overview#authorization"
}
```
# User Profile API Overview
Source: https://www.ayrshare.com/docs/apis/user/overview
Get details on a user profile including linked social networks and social usernames
The user endpoint gives you details on all your users' social media accounts. You can also manage user profiles in the Ayrshare [dashboard](/docs/multiple-users/manage-user-profiles).
> [card] [Learn more about managing user profiles in the dashboard](/docs/multiple-users/manage-user-profiles#using-the-dashboard)
# Pinterest Boards
Source: https://www.ayrshare.com/docs/apis/user/pinterest-board
GET /user/details/pinterest
Get the details of a connected platform for your account. Pinterest Boards are currently supported, and pinterest is the only supported platform value.
Get the details of a connected platform. Current support for Pinterest Boards.
Currently supported platform values: `pinterest`
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/user/details/pinterest
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/user/details/pinterest", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/user/details/pinterest', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace PinterestBoardGETRequest_csharp
{
class PinterestBoard
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/user/details/pinterest";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```javascript 200: Pinterest Boards theme={"system"}
{
"pinterest": [
{
"privacy": "PUBLIC",
"owner": {
"username": "ayrshare"
},
"id": "718465015493014420",
"name": "Coworking",
"description": ""
},
{
"privacy": "PUBLIC",
"owner": {
"username": "ayrshare"
},
"id": "718465015493634450",
"name": "Social Media Networks",
"description": ""
}
]
}
```
# User Profile Details
Source: https://www.ayrshare.com/docs/apis/user/profile-details
GET /user
Get linked social accounts, usernames, account status, and usage metrics for the user or a User Profile, including response caching and monthly post quota.
This endpoint retrieves detailed information about the authenticated user or user profile, including:
A comprehensive list of all linked social media accounts
Social media usernames and profile information
Account status and usage metrics
The returned data reflects the state of connected social networks as of the last refresh, and updates whenever users connect or disconnect their social media accounts.
> [!NOTE]
>
> **Response caching.** Responses are cached for up to **60 seconds**. The `lastUpdated` and `nextUpdate` fields describe that cache window: `lastUpdated` is when the snapshot was built, `nextUpdate` is when it expires.
>
> `nextUpdate` is a **cache-expiry marker, not a readiness signal.** It is stamped on every response, including one whose `displayNames` does not yet list an account you were just notified about, so it cannot tell you when a newly linked account has finished propagating.
>
> If you are reconciling a [`social` webhook](/docs/apis/webhooks/actions) for a brand-new link, poll until the account you expect actually appears in `displayNames` — do not treat a single response, or the arrival of `nextUpdate`, as confirmation that the link is absent or complete.
> [!NOTE]
>
> **Threads `username` vs `displayName`.** In a Threads `displayNames` entry, `username` is the account handle (for example `ayrshare`). `displayName` is the name shown on the profile (for example `Ayrshare`). They can be different, so use `username` or `profileUrl` to find the Threads account.
> [!NOTE]
>
> **Instagram `id` vs `userId`.** In an Instagram `displayNames` entry, `id` is the Instagram professional account ID. It is the same value as `recipientId` on incoming messages and `senderId` on outgoing messages in [messaging webhooks](/docs/apis/webhooks/actions#messages-action). `userId` is the Facebook user ID of the person who connected the account. It is only returned for Instagram accounts connected through Facebook Login.
If no social accounts are linked, `activeSocialAccounts` will not be returned.
Get data for a particular User Profile by adding the [Profile-Key in the header](/docs/apis/overview#profile-key-format).
> [!WARNING]
>
>
>
If your business requires gathering all of your user profile data at once, please use the [/user/batch endpoint](/docs/apis/user/batch-all-users).
>
If you need a notification when a user links and unlinks a social account, please see the [/webhooks endpoint](/docs/apis/webhooks/overview).
>
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**instagramDetails** `boolean` (default: false)
Returns additional Instagram details such as account type (business or creator) and used quota.
Not recommended unless you need the additional data since it will slow down the response time.
## Monthly Post Quota
The response shows how much of the profile's monthly post quota is used:
* `monthlyPostCount`: posts counted against the quota this month.
* `monthlyPostQuota`: the monthly limit. It is omitted when the quota is 10,000 or more, for example on Business plans. The limit still applies.
The count resets on the 1st of each month at 00:07 UTC, not on your billing date. Failed post attempts count too. `monthlyApiCalls` is a different counter and is not the post quota. See [How Many Posts & API Calls Can I Make?](/docs/help-center/product/how-many-posts-and-api-calls-can-i-make) for what counts and what happens when you run out.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/user
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/user", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/user', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
$apiUrl = 'https://api.ayrshare.com/api/user';
$apiKey = 'API_KEY'; // Replace 'API_KEY' with your actual API key
$headers = [
'Content-Type: application/json',
'Authorization: Bearer ' . $apiKey,
];
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace UserGETRequest_csharp
{
class User
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/user";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```javascript 200: Response theme={"system"}
{
"activeSocialAccounts": [
"bluesky",
"facebook",
"gmb",
"instagram",
"linkedin",
"pinterest",
"reddit",
"snapchat",
"telegram",
"threads",
"tiktok",
"twitter",
"youtube"
],
"created": {
"_seconds": 1667351022,
"_nanoseconds": 814000000,
"utc": "2022-11-02T01:03:42Z"
},
"displayNames": [
{ // Bluesky
"created": "2025-01-06T21:30:19.756Z",
"description": "Ayrshare's Social APIs provide the core infrastructure for social media posting, management, and analytics.",
"displayName": "Ayrshare",
"id": "did:plc:62musrcyanhro2lydyhl", // Bluesky Id
"platform": "bluesky",
"userImage": "https://cdn.bsky.app/img/avatar/plain/did:plc:62musrcyanhro2lydyhlw7ci/bafkreie",
"username": "ayrshare.com"
},
{ // Facebook
"created": "2022-11-14T16:18:49.110Z",
"displayName": "Ayrshare",
"id": "106638148652329", // Facebook Page Id
"messagingActive": true, // Messaging active for the social network
"pageName": "Ayrshare",
"platform": "facebook",
"profileUrl": "https://www.facebook.com/ayrshare",
"userId": "283748192833", // Facebook User Id
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/facebook.jpeg" // The image at the time of linking
},
{ // Google Business Profile
"created": "2024-03-27T20:47:46.251Z",
"description": "Easy to integrate Social Media APIs allow you to manage all your users' social accounts right from your product. Post, Auto Schedule, and Analytics. Great for SaaS, CMS, DAM, Agencies, and Apps.",
"displayName": "Ayrshare",
"mapsUrl": "https://maps.google.com/maps?cid=5229466225881728772",
"placeId": "ChIJN53jw8BZwokRBEeVVtPLkkg",
"platform": "gmb",
"profileUrl": "https://www.ayrshare.com/",
"reviewUrl": "https://search.google.com/local/writereview?placeid=ChIJN53jw8BZwokRBEeVVtPLkkg"
},
{ // Instagram
"created": "2022-11-09T20:36:58.659Z",
"displayName": "Ayrshare",
"id": "17841401430400299", // Instagram professional account ID. Same value as recipientId (incoming) and senderId (outgoing) in messaging webhooks
"messagingActive": true, // Messaging active for the social network
"pageName": "Social Media API", // Linked Facebook Page name. Only returned for Instagram connected through Facebook Login
"platform": "instagram",
"profileUrl": "https://www.instagram.com/ayrshare",
"userId": "283748192833", // Facebook user ID. Only returned for Instagram connected through Facebook Login
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/instagram.jpeg", // The image at the time of linking
"username": "ayrshare",
// Additional Instagram details with instagramDetails=true
"type": "business", // business or creator. Creator accounts can also return business
"usedQuota": 20 // Posts used in the rolling 24-hour period. Instagram allows 50. Returns 0 if the lookup fails, sometimes with an error field
},
{ // LinkedIn
"created": "2022-11-17T18:52:29.830Z",
"displayName": "Ayrshare",
"id": "72157",
"platform": "linkedin",
"profileUrl": "https://www.linkedin.com/company/ayrshare",
"refreshDaysRemaining": 364, // Days until link auth must be refreshed
"refreshRequired": "2023-11-17T18:52:29.830Z", // Date and time when link auth must be refreshed
"type": "corporate", // corporate or personal
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/linkedin.jpeg", // The image at the time of linking
"username": "ayrshare" // logged in username
},
{ // Pinterest
"created": "2022-12-06T03:16:52.642Z",
"displayName": "Ayrshare",
"id": "42995790741",
"platform": "pinterest",
"profileUrl": "https://www.pinterest.com/ayrshare",
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/pinterest.jpeg", // The image at the time of linking
"username": "ayrshare"
},
{ // Reddit
"created": "2022-11-17T18:55:34.419Z",
"displayName": "funone",
"platform": "reddit",
"profileUrl": "https://www.reddit.com/user/funone",
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/reddit.png", // The image at the time of linking
"username": "funone"
},
{ // Snapchat
"created": "2025-05-19T19:23:40.925Z",
"displayName": "ayrshare",
"id": "43548e97-edf1-44f9-984a-0a384703333",
"platform": "snapchat",
"profileUrl": "https://www.snapchat.com/add/username",
"userImage": "https://img.ayrshare.com/EuMQpMIgcPZYuwHnWeVBCXyftb52/social/snapchat",
"username": "ayrshare"
},
{ // Telegram
"created": "2022-11-17T18:55:16.320Z",
"displayName": "Ayrshare",
"id": -10017122,
"platform": "telegram",
"profileUrl": "https://web.telegram.org/z/#-17122",
"type": "channel",
"userImage": "https://img.ayrshare.com/nclMLxaIzmXHxOi4KEggA5gQ1T82/social/telegram.octo-stream" // The image at the time of linking
},
{ // Threads
"created": "2025-04-24T20:26:28.311Z",
"displayName": "Ayrshare",
"id": "9273292656113202",
"isEligibleForGeoRestrictions": false,
"isVerified": false,
"platform": "threads",
"profileUrl": "https://www.threads.com/@ayrshare",
"userImage": "https://scontent-dfw5-2.cdninstagram.com/v/t51.2885-15/357665262", // The image at the time of linking
"username": "ayrshare"
},
{ // TikTok
"created": "2022-11-02T02:11:53.452Z",
"displayName": "Ayrshare",
"id": "5ebc6f39-7900-421e-bf9",
"platform": "tiktok",
"profileUrl": "https://www.tiktok.com/@ayrshare",
"refreshDaysRemaining": 234, // Days until link auth must be refreshed
"refreshRequired": "2026-04-24T15:25:32.344Z", // Date and time when link auth must be refreshed
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/tiktok.jpeg", // The image at the time of linking
"username": "@ayrshare"
},
{ // Twitter
"created": "2022-11-02T01:38:42.326Z",
"displayName": "ayrshare",
"id": "1194881472",
"messagingActive": true,
"platform": "twitter", // Messaging active for the social network
"profileUrl": "https://twitter.com/ayrshare",
"subscriptionType": "Premium", // Premium, PremiumPlus, None
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/twitter.png", // The image at the time of linking
"username": "ayrshare",
"verifiedType": "blue" // Values "blue", "business", or "none". Both "blue" and "business" are considered Premium. This will be updated when an X post is done via Ayrshare.
},
{ // YouTube
"channelId": "UCcMBjBksQnhCn5NNLtGKphw", // YouTube channel ID (starts with "UC") — different from "id" below
"channelTitle": "Ayrshare", // The channel's actual title
"created": "2022-11-17T18:54:09.954Z",
"displayName": "Kenneth Chen", // The connected Google account holder's name — NOT the channel title
"handle": "@ayrshare", // The @handle — omitted when the channel has no custom URL
"id": "106891058521430758565", // Google account ID — different from "channelId"
"platform": "youtube",
"profileUrl": "https://www.youtube.com/@ayrshare", // Falls back to https://www.youtube.com/channel/ when the channel has no custom URL
"userImage": "https://img.ayrshare.com/ndfdfJ239s/social/youtube.png", // The image at the time of linking
"username": "Kenneth Chen" // The connected Google account holder's name — NOT the channel title
}
],
"email": "me@ayrshare.com", // null if a User Profile
"lastApiCall": "2024-08-14T15:18:11Z", // Time of the last recorded API call for this user
"messagingConversationMonthlyCount": 7, // Monthly conversation count out of 1,000 - contact us if you need more per user profile
"messagingEnabled": true, // Messaging enabled for the account
"monthlyApiCalls": 120, // API calls of all types this month. Not the post quota.
"monthlyPostCount": 49, // Posts counted against the monthly quota. Resets on the 1st (UTC).
"monthlyPostQuota": 500, // Quota of monthly API post calls. Omitted when the quota is 10,000 or more.
"refId": "13a9da9e0df1183a7a6a1fc2c60b8023fa9a32a0", // User Profile reference ID
"title": "Primary Profile", // User Profile Title - Business Plan only
"lastUpdated": "2024-01-04T15:51:17.775Z", // When this cached snapshot was built
"nextUpdate": "2024-01-04T15:52:17.775Z" // When the cache expires. Not a readiness signal — see Response caching above
}
```
```json 403: Profile Key Not Found theme={"system"}
{
"action": "post",
"status": "error",
"code": 144,
"message": "Some profiles not found. Please verify the Profile Keys."
}
```
# Update User
Source: https://www.ayrshare.com/docs/apis/user/update-user
PATCH /user/:platform
Update account or user data for Google Business Profile or WhatsApp, including phone numbers, website URL, and labels. Note Google's 24-hour update limits.
Update the account or user data of the social network. Currently Google Business Profile and WhatsApp are supported.
> [!NOTE]
>
> Google limits updating some Google Business Profile location fields to being updated 5 times
> within a rolling 24-hour period. For example updating `phoneNumbers` more than 5 times in a
> rolling 24-hour period will return a 400 Response. This limit is specific to Google Business
> Profile and does not apply to WhatsApp.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**:platform** `string` *required*
Values: `gbp`, `whatsapp`
## Body Parameters
**phoneNumbers** `object`
Object containing the different phone numbers that customers can use to get in touch with the business.
```json theme={"system"}
"phoneNumbers": {
"primaryPhone": "212-123-4567",
"additionalPhones": [
"212-432-2342"
]
}
```
**name** `string`
Google identifier for this location in the form.
**adPhone** `string`
An alternate phone number to display on AdWords location extensions instead of the location's
primary phone number.
**storeCode** `string`
External identifier for this location, which must be unique within a given account. This is a
means of associating the location with your own records.
**title** `string`
Location name should reflect your business's real-world name, as used consistently on your storefront, website, and stationery, and as known to customers.
**websiteUrl** `string`
A URL for this business.
**labels** `array`
A collection of free-form strings to allow you to tag your business. These labels are not user facing; only you can see them. Must be between 1-255 characters per label.
**latlng** `object`
An object that represents a latitude/longitude pair. This is expressed as a pair of doubles to represent degrees latitude and degrees longitude. Unless specified otherwise, this object must conform to the WGS84 standard.
```json theme={"system"}
{
"latitude": number,
"longitude": number
}
```
Values must be within normalized ranges.
The latitude in degrees. It must be in the range \[-90.0, +90.0].
The longitude in degrees. It must be in the range \[-180.0, +180.0].
**profile** `object`
This field contains a description of the location in your own voice. The description is not editable by anyone else.
```json theme={"system"}
{
"description": string
}
```
### WhatsApp
> [!NOTE]
>
> **WhatsApp is in Private Beta.** Updating a WhatsApp business profile requires an account
> approved for WhatsApp. If you'd like early access, email
> [lotty@ayrshare.com](mailto:lotty@ayrshare.com).
Send only the fields you want to change. At least one is required, and sending a field that this
endpoint does not recognize rejects the entire request.
To **clear** a field, send it as an empty string: `{"description": ""}` removes the description,
and the field then stops appearing in the profile at all. This works for `description`, `address`
and `email`. It does not work for `about`, which WhatsApp requires to be at least one character —
to change that one, overwrite it. `vertical` cannot be cleared either, once it has been set.
The response fields `messaging_product` and `profile_picture_url` (see [WhatsApp Business
Profile](/docs/apis/user/whatsapp-profile)) are read-only: sending either one back is rejected as an
unrecognized field. Use `profilePictureUrl` to set the photo.
**about** `string`
Text shown beneath the profile image. 1–139 characters. This is the one text field WhatsApp
will not let you clear — overwrite it instead.
**description** `string`
Business description. Up to 512 characters. Send `""` to clear it.
**address** `string`
Business address. Up to 256 characters. Send `""` to clear it.
**email** `string`
Contact email address. Up to 128 characters, and must be a valid address unless you are
sending `""` to clear it.
**websites** `array`
A non-empty array of up to 2 URLs, each starting with `http://` or `https://`.
**vertical** `string`
Business category. WhatsApp currently accepts: `OTHER`, `AUTO`, `BEAUTY`, `APPAREL`,
`EDU`, `ENTERTAIN`, `EVENT_PLAN`, `FINANCE`, `GROCERY`, `GOVT`, `HOTEL`, `HEALTH`,
`NONPROFIT`, `PROF_SERVICES`, `RETAIL`, `TRAVEL`, `RESTAURANT`, `ALCOHOL`,
`ONLINE_GAMBLING`, `PHYSICAL_GAMBLING`, `OTC_DRUGS`, `MATRIMONY_SERVICE`. This list is
WhatsApp's and may grow over time — Ayrshare does not restrict the value, so any
category WhatsApp accepts works. Normalized to uppercase, so a value sent as `retail`
is returned as `RETAIL`. WhatsApp does not allow the category to be cleared once it has
been set, and rejects a value it does not accept — the rejection is returned as a `101`
with WhatsApp's own message naming the currently accepted values.
**profilePictureUrl** `string`
Publicly reachable JPEG or PNG, 5 MB or smaller. The image must be square and at least
192×192 — 640×640 is recommended. There is no maximum resolution: a larger image is
accepted and scaled down by WhatsApp. An image that is not square or is below 192×192
is rejected with a `400` naming the problem, before anything is sent to WhatsApp. See the
[WhatsApp media guidelines](/docs/media-guidelines/whatsapp) for the full requirements.
```json 200: Success theme={"system"}
{
"gmb": {
"status": "success",
"data": {
"name": "locations/4913369732395328466",
"languageCode": "en",
"title": "Ayrshare",
"phoneNumbers": {},
"categories": {
"primaryCategory": {
"name": "categories/gcid:software_company",
"displayName": "Software company",
"serviceTypes": [
{
"serviceTypeId": "job_type_id:application_development",
"displayName": "Application development"
},
{
"serviceTypeId": "job_type_id:big_data_consulting_and_implementation",
"displayName": "Big data consulting & implementation"
},
{
"serviceTypeId": "job_type_id:data_center_management",
"displayName": "Data center management"
},
{
"serviceTypeId": "job_type_id:data_quality_management",
"displayName": "Data quality management"
},
{
"serviceTypeId": "job_type_id:enterprise_software_development",
"displayName": "Enterprise software development"
},
{
"serviceTypeId": "job_type_id:it_consulting",
"displayName": "It consulting"
},
{
"serviceTypeId": "job_type_id:mobile_app_development",
"displayName": "Mobile app development"
},
{
"serviceTypeId": "job_type_id:platform_consulting",
"displayName": "Platform consulting"
},
{
"serviceTypeId": "job_type_id:security_services_management",
"displayName": "Security services management"
},
{
"serviceTypeId": "job_type_id:software_consulting",
"displayName": "Software consulting"
},
{
"serviceTypeId": "job_type_id:software_development",
"displayName": "Software development"
},
{
"serviceTypeId": "job_type_id:software_development_outsourcing",
"displayName": "Software development outsourcing"
},
{
"serviceTypeId": "job_type_id:solution_consulting",
"displayName": "Solution consulting"
}
],
"moreHoursTypes": [
{
"hoursTypeId": "ACCESS",
"displayName": "Access",
"localizedDisplayName": "Access"
},
{
"hoursTypeId": "BREAKFAST",
"displayName": "Breakfast",
"localizedDisplayName": "Breakfast"
},
{
"hoursTypeId": "BRUNCH",
"displayName": "Brunch",
"localizedDisplayName": "Brunch"
},
{
"hoursTypeId": "DELIVERY",
"displayName": "Delivery",
"localizedDisplayName": "Delivery"
},
{
"hoursTypeId": "DINNER",
"displayName": "Dinner",
"localizedDisplayName": "Dinner"
},
{
"hoursTypeId": "DRIVE_THROUGH",
"displayName": "Drive through",
"localizedDisplayName": "Drive through"
},
{
"hoursTypeId": "HAPPY_HOUR",
"displayName": "Happy hours",
"localizedDisplayName": "Happy hours"
},
{
"hoursTypeId": "KITCHEN",
"displayName": "Kitchen",
"localizedDisplayName": "Kitchen"
},
{
"hoursTypeId": "LUNCH",
"displayName": "Lunch",
"localizedDisplayName": "Lunch"
},
{
"hoursTypeId": "ONLINE_SERVICE_HOURS",
"displayName": "Online service hours",
"localizedDisplayName": "Online service hours"
},
{
"hoursTypeId": "PICKUP",
"displayName": "Pickup",
"localizedDisplayName": "Pickup"
},
{
"hoursTypeId": "TAKEOUT",
"displayName": "Takeout",
"localizedDisplayName": "Takeout"
},
{
"hoursTypeId": "SENIOR_HOURS",
"displayName": "Senior hours",
"localizedDisplayName": "Senior hours"
}
]
},
"additionalCategories": [
{
"name": "categories/gcid:automation_company",
"displayName": "Automation company",
"moreHoursTypes": [
{
"hoursTypeId": "ACCESS",
"displayName": "Access",
"localizedDisplayName": "Access"
},
{
"hoursTypeId": "BREAKFAST",
"displayName": "Breakfast",
"localizedDisplayName": "Breakfast"
},
{
"hoursTypeId": "BRUNCH",
"displayName": "Brunch",
"localizedDisplayName": "Brunch"
},
{
"hoursTypeId": "DELIVERY",
"displayName": "Delivery",
"localizedDisplayName": "Delivery"
},
{
"hoursTypeId": "DINNER",
"displayName": "Dinner",
"localizedDisplayName": "Dinner"
},
{
"hoursTypeId": "DRIVE_THROUGH",
"displayName": "Drive through",
"localizedDisplayName": "Drive through"
},
{
"hoursTypeId": "HAPPY_HOUR",
"displayName": "Happy hours",
"localizedDisplayName": "Happy hours"
},
{
"hoursTypeId": "KITCHEN",
"displayName": "Kitchen",
"localizedDisplayName": "Kitchen"
},
{
"hoursTypeId": "LUNCH",
"displayName": "Lunch",
"localizedDisplayName": "Lunch"
},
{
"hoursTypeId": "ONLINE_SERVICE_HOURS",
"displayName": "Online service hours",
"localizedDisplayName": "Online service hours"
},
{
"hoursTypeId": "PICKUP",
"displayName": "Pickup",
"localizedDisplayName": "Pickup"
},
{
"hoursTypeId": "TAKEOUT",
"displayName": "Takeout",
"localizedDisplayName": "Takeout"
},
{
"hoursTypeId": "SENIOR_HOURS",
"displayName": "Senior hours",
"localizedDisplayName": "Senior hours"
}
]
}
]
},
"storefrontAddress": {
"regionCode": "US",
"languageCode": "en",
"postalCode": "10019",
"administrativeArea": "NY",
"locality": "New York",
"addressLines": [
"142 W 57th St"
]
},
"websiteUri": "https://www.ayrshare.com/",
"regularHours": {
"periods": [
{
"openDay": "SUNDAY",
"openTime": {},
"closeDay": "SUNDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "MONDAY",
"openTime": {},
"closeDay": "MONDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "TUESDAY",
"openTime": {},
"closeDay": "TUESDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "WEDNESDAY",
"openTime": {},
"closeDay": "WEDNESDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "THURSDAY",
"openTime": {},
"closeDay": "THURSDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "FRIDAY",
"openTime": {},
"closeDay": "FRIDAY",
"closeTime": {
"hours": 24
}
},
{
"openDay": "SATURDAY",
"openTime": {},
"closeDay": "SATURDAY",
"closeTime": {
"hours": 24
}
}
]
},
"serviceArea": {
"businessType": "CUSTOMER_AND_BUSINESS_LOCATION",
"places": {
"placeInfos": [
{
"placeName": "United States",
"placeId": "ChIJCzYy5IS16lQRQrfeQ5K5Oxw"
}
]
},
"regionCode": "US"
},
"openInfo": {
"status": "OPEN",
"canReopen": true
},
"metadata": {
"hasPendingEdits": true,
"canDelete": true,
"canModifyServiceList": true,
"placeId": "ChIJN53jw8BZwokRBEeVVtPLkkg",
"mapsUri": "https://maps.google.com/maps?cid=5229466225881728772",
"newReviewUri": "https://search.google.com/local/writereview?placeid=ChIJN53jw8BZwokRBEeVVtPLkkg",
"hasVoiceOfMerchant": true
},
"profile": {
"description": "Easy to integrate Social Media APIs allow you to manage all your users' social accounts right from your product. Post, Auto Schedule, and Analytics. Great for SaaS, CMS, DAM, Agencies, and Apps."
}
}
}
}
```
```json 400: Too Many Updates theme={"system"}
{
"action": "authorization",
"status": "error",
"code": 227,
"message": "Error updating GBP location data. This can occur if you attempt more than 5 updates within a rolling 24 hours period. Please wait and try again."
}
```
```json 200: Success (WhatsApp) theme={"system"}
{
"whatsapp": {
"status": "success",
"data": {
"messaging_product": "whatsapp",
"about": "Open 9am-6pm",
"description": "Bike shop since 1998",
"address": "123 Example St, Springfield",
"email": "hello@example.com",
"websites": ["https://example.com"],
"vertical": "RETAIL",
"profile_picture_url": "https://scontent.whatsapp.net/..."
}
}
}
```
```json 400: Missing or Incorrect Parameters (WhatsApp) theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "`about` must be 139 characters or fewer (received 150)."
}
```
```json 400: WhatsApp Not Linked theme={"system"}
{
"action": "post",
"status": "error",
"code": 156,
"message": "WhatsApp is not linked. Please confirm the linkage on the Social Accounts page in the dashboard. https://www.ayrshare.com/docs/dashboard/connect-social-accounts/overview"
}
```
```json 403: WhatsApp Not Enabled theme={"system"}
{
"action": "request",
"status": "error",
"code": 472,
"message": "This feature is not yet available on your account. Please contact us if you'd like early access."
}
```
```json 429: Too Many Requests (WhatsApp) theme={"system"}
{
"action": "rate limit",
"status": "error",
"code": 364,
"message": "You are making too many requests. Please reduce the number of requests.",
"details": "Meta rejected the WhatsApp profile request."
}
```
```json 500: Connection Issue (WhatsApp) theme={"system"}
{
"action": "authorization",
"status": "error",
"code": 141,
"message": "There was an issue connecting. Please try linking the social account again.",
"details": "Network error updating the WhatsApp business profile."
}
```
# WhatsApp Business Profile
Source: https://www.ayrshare.com/docs/apis/user/whatsapp-profile
GET /user/details/whatsapp
Get the current WhatsApp business profile for the linked WhatsApp account
Get the current WhatsApp business profile for the linked WhatsApp account: `about`, `description`, `address`, `email`, `websites`, `vertical`, and the profile picture URL (returned as `profile_picture_url`).
> [!NOTE]
>
> **WhatsApp is in Private Beta.** Reading a WhatsApp business profile requires an account
> approved for WhatsApp. If you'd like early access, email
> [lotty@ayrshare.com](mailto:lotty@ayrshare.com).
A field you have never set is omitted from `data` rather than returned as an empty value. If the WhatsApp Business Account has no profile configured yet, `data` is an empty object.
To change these fields, see [Update User](/docs/apis/user/update-user).
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-X GET https://api.ayrshare.com/api/user/details/whatsapp
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/user/details/whatsapp", {
method: "GET",
headers: {
"Authorization": `Bearer ${API_KEY}`
}
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
headers = {'Authorization': 'Bearer API_KEY'}
r = requests.get('https://api.ayrshare.com/api/user/details/whatsapp', headers=headers)
print(r.json())
```
```php PHP theme={"system"}
true,
CURLOPT_HTTPHEADER => $headers
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace WhatsAppProfileGETRequest_csharp
{
class WhatsAppProfile
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/user/details/whatsapp";
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
try
{
HttpResponseMessage response = await client.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: Success theme={"system"}
{
"whatsapp": {
"status": "success",
"data": {
"messaging_product": "whatsapp",
"about": "Open 9am-6pm",
"description": "Bike shop since 1998",
"address": "123 Example St, Springfield",
"email": "hello@example.com",
"websites": ["https://example.com"],
"vertical": "RETAIL",
"profile_picture_url": "https://scontent.whatsapp.net/..."
}
}
}
```
```json 200: No Profile Configured theme={"system"}
{
"whatsapp": {
"status": "success",
"data": {}
}
}
```
```json 400: WhatsApp Not Linked theme={"system"}
{
"action": "get",
"status": "error",
"code": 103,
"message": "Missing social account. Please link the social account and try again."
}
```
```json 400: Missing or Incorrect Parameters theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. https://www.ayrshare.com/docs/apis",
"details": "Meta rejected the WhatsApp profile request."
}
```
```json 429: Too Many Requests theme={"system"}
{
"action": "rate limit",
"status": "error",
"code": 364,
"message": "You are making too many requests. Please reduce the number of requests.",
"details": "Meta rejected the WhatsApp profile request."
}
```
```json 500: Connection Issue theme={"system"}
{
"action": "authorization",
"status": "error",
"code": 141,
"message": "There was an issue connecting. Please try linking the social account again.",
"details": "Network error fetching the WhatsApp business profile."
}
```
# Collaborator Request Status
Source: https://www.ayrshare.com/docs/apis/utils/instagram-get-collaborator
GET /post/collaborators/:id
Check whether invited collaborators accepted, declined or are pending on Instagram Reels, images and carousels, or on Facebook Reels.
When you [invite an Instagram collaborator to a post](/docs/apis/post/social-networks/instagram#collaboration)
or a [Facebook Page collaborator to a Facebook Reel](/docs/apis/post/social-networks/facebook#facebook-reels),
you can check the request status using this endpoint.
Use this endpoint for:
Instagram collaborator invitations on Reels, images, and carousels.
Facebook collaborator invitations on Facebook Reels.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**id** `string` *required*
In the path parameter, use an [ID Type](/docs/apis/overview#id-types)
## Query Parameters
**searchPlatformId** `boolean` *required*
Set to `true`. Required if using a Social Post ID.
**platform** `string`
Set to `facebook` when the `id` is a Facebook Social Post ID. Required in that case — without
it, the request looks up the ID on Instagram instead. Also use it to force Facebook collaborator
lookup for an Ayrshare post that has multiple platform IDs.
```json 200 theme={"system"}
{
"collaborators": [
{
"id": "17841401319910272",
"username": "ayrshare",
"inviteStatus": "accepted"
},
{
"id": "17841401319910273",
"username": "ayrshare1",
"inviteStatus": "declined"
},
{
"id": "17841401319910274",
"username": "ayrshare2",
"inviteStatus": "pending"
}
],
"lastUpdated": "2024-03-08T04:08:18.979Z",
"nextUpdate": "2024-03-08T04:09:18.979Z"
}
```
```json 200 Facebook Reels theme={"system"}
{
"collaborators": [
{
"id": "123456789012345",
"username": "collaboratorpage",
"inviteStatus": "pending"
}
],
"lastUpdated": "2026-08-24T04:08:18.979Z",
"nextUpdate": "2026-08-24T04:09:18.979Z"
}
```
```json 400 theme={"system"}
{
"action": "analytics",
"status": "error",
"code": 294,
"message": "Error getting analytics."
}
```
# Reddit Flair
Source: https://www.ayrshare.com/docs/apis/utils/reddit-get-flair
GET /post/redditFlair/:subreddit
Some subreddits require flair on posts. Retrieve the flair id for a subreddit by passing a valid subreddit name, then use that id in your Reddit post.
Some subreddits require flair to be added to posts. Retrieve the flair id for a subreddit to be used with a Reddit post.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Path Parameters
**subreddit** `string` *required*
A valid subreddit name.
```json 200: OK Subreddit flair theme={"system"}
{
"flairs": [
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "General",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "General"
}
],
"backgroundColor": "#24a0ed",
"id": "835693e2-f4c6-11e8-9b68-0ef1d31d47f4"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Model S",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Model S"
}
],
"backgroundColor": "#ff5c52",
"id": "d65a3bf8-2e3f-11ea-b1df-0e48b758f1d1"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Model 3",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Model 3"
}
],
"backgroundColor": "#ff5c52",
"id": "dcd4dfba-2e3f-11ea-ae0a-0e08fd0921a5"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Model X",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Model X"
}
],
"backgroundColor": "#ff5c52",
"id": "e3b80334-2e3f-11ea-9319-0ef0666f4955"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Model Y",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Model Y"
}
],
"backgroundColor": "#ff5c52",
"id": "e8d3c3e4-2e3f-11ea-8503-0e7d8ba95ee7"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Roadster",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Roadster"
}
],
"backgroundColor": "#ff5c52",
"id": "9b5a153c-2fe8-11ea-84b3-0e3dc676291d"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Cybertruck",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Cybertruck"
}
],
"backgroundColor": "#ff5c52",
"id": "f7bd2d96-2e3f-11ea-8af4-0e84bb2efd99"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Vehicles - Semi",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Vehicles - Semi"
}
],
"backgroundColor": "#ff5c52",
"id": "08e0e25c-2e40-11ea-8503-0e7d8ba95ee7"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Energy - General",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Energy - General"
}
],
"backgroundColor": "#349e48",
"id": "300915fc-2e40-11ea-bf20-0e5f8d7121e9"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Energy - Charging",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Energy - Charging"
}
],
"backgroundColor": "#349e48",
"id": "26c0bde0-4ada-11ea-a6c2-0e37bd75c65b"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Energy - Residential",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Energy - Residential"
}
],
"backgroundColor": "#349e48",
"id": "3c552d28-2e40-11ea-91af-0efac8321a7d"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Energy - Commercial",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Energy - Commercial"
}
],
"backgroundColor": "#349e48",
"id": "6c3da4e4-3f71-11ed-bf3a-e2b38500a359"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "$TSLA Investing - Financials/Earnings",
"maxEmojis": 10,
"textColor": "dark",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "$TSLA Investing - Financials/Earnings"
}
],
"backgroundColor": "#94e044",
"id": "260deb6a-fb68-11ed-b8f9-fe172b851044"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "$TSLA Investing - Bullish",
"maxEmojis": 10,
"textColor": "dark",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "$TSLA Investing - Bullish"
}
],
"backgroundColor": "#94e044",
"id": "35318c0a-fb68-11ed-ac39-ae5f0d523a9a"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "$TSLA Investing - Bearish",
"maxEmojis": 10,
"textColor": "dark",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "$TSLA Investing - Bearish"
}
],
"backgroundColor": "#94e044",
"id": "4739d7ea-fb68-11ed-825c-dee75c150cc6"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - General",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - General"
}
],
"backgroundColor": "#646d73",
"id": "971ac264-3f71-11ed-a80d-be048f065daa"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Fremont, California",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Fremont, California"
}
],
"backgroundColor": "#646d73",
"id": "9f03fc98-3f71-11ed-a464-22492ba9dd17"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Austin, Texas",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Austin, Texas"
}
],
"backgroundColor": "#646d73",
"id": "a3cfad80-3f71-11ed-a86b-b6b3d0e0d7bd"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Buffalo, New York",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Buffalo, New York"
}
],
"backgroundColor": "#646d73",
"id": "a941fe4e-3f71-11ed-8f4f-6ef3febda094"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Sparks, Nevada",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Sparks, Nevada"
}
],
"backgroundColor": "#646d73",
"id": "aeabb0d2-3f71-11ed-97b0-22ecff9f5113"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Shanghai, China",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Shanghai, China"
}
],
"backgroundColor": "#646d73",
"id": "b65cf386-3f71-11ed-a39c-c293bd1546c0"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Berlin, Germany",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Berlin, Germany"
}
],
"backgroundColor": "#646d73",
"id": "155d7b10-578e-11ed-a0d2-d2bc46bf7da2"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Lathrop, California",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Lathrop, California"
}
],
"backgroundColor": "#646d73",
"id": "f15c7f0a-8874-11ed-ab66-96069cfc53e3"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Factories - Monterrey, Mexico",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Factories - Monterrey, Mexico"
}
],
"backgroundColor": "#646d73",
"id": "097e95ca-b9d3-11ed-94a1-7a457264f9ca"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Software - General",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Software - General"
}
],
"backgroundColor": "#0079d3",
"id": "e1df8ffa-3f71-11ed-bc64-2aaa808ece5c"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Software - Tesla App",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Software - Tesla App"
}
],
"backgroundColor": "#0079d3",
"id": "f5d33e68-2197-11ee-97e0-9e68d8ed8435"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Software - Autopilot",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Software - Autopilot"
}
],
"backgroundColor": "#0079d3",
"id": "e71da20e-3f71-11ed-a4ea-d2f3dc1b4cf7"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Software - Full Self-Driving",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Software - Full Self-Driving"
}
],
"backgroundColor": "#0079d3",
"id": "fa5e9a58-3f71-11ed-b0e0-1a8e6d6659f3"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Software - AI / Optimus / Dojo",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Software - AI / Optimus / Dojo"
}
],
"backgroundColor": "#0079d3",
"id": "00b3b44c-3f72-11ed-911c-ea0c960aabdf"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Hardware - General",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Hardware - General"
}
],
"backgroundColor": "#005ba1",
"id": "4d9ddd0a-3f72-11ed-9dcb-362c5016d5b6"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Hardware - Autopilot",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Hardware - Autopilot"
}
],
"backgroundColor": "#005ba1",
"id": "56876012-3f72-11ed-a892-2271fd05c94a"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Hardware - Full Self-Driving",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Hardware - Full Self-Driving"
}
],
"backgroundColor": "#005ba1",
"id": "5c26f334-3f72-11ed-a350-7acd016cd487"
},
{
"type": "richtext",
"textEditable": false,
"allowableContent": "all",
"text": "Hardware - AI / Optimus / Dojo",
"maxEmojis": 10,
"textColor": "light",
"modOnly": false,
"cssClass": "",
"richtext": [
{
"e": "text",
"t": "Hardware - AI / Optimus / Dojo"
}
],
"backgroundColor": "#005ba1",
"id": "70f05e86-3f72-11ed-8307-d25f6730a03f"
}
],
"lastUpdated": "2023-11-06T21:35:11.119Z",
"nextUpdate": "2023-11-06T21:46:11.119Z"
}
```
```json 400: Bad Request No flair found theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints",
"details": "The subreddit does not exist or is private.",
"subreddit": "teslamotors12",
"lastUpdated": "2023-11-06T21:58:42.392Z",
"nextUpdate": "2023-11-06T22:09:42.392Z"
}
```
# Remove YouTube Watermark
Source: https://www.ayrshare.com/docs/apis/utils/remove-youtube-watermark
DELETE /post/youTubeWatermark
Remove the existing watermark from your YouTube channel. The watermark is removed from all existing and future videos, and no body parameters are required.
Remove the existing watermark from your YouTube channel. This will remove the
watermark from all existing and future videos uploaded to the channel.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
No body parameters required for this endpoint.
```json 200 theme={"system"}
{
"status": "success",
"message": "Watermark removed successfully"
}
```
```json 403 - Insufficient Permissions theme={"system"}
{
"status": "error",
"code": 250,
"message": "Insufficient permissions to remove watermark from this channel",
"details": "Original YouTube API error message"
}
```
```json 403 - Channel Not Found or No Watermark Exists theme={"system"}
{
"status": "error",
"code": 226,
"message": "Channel not found or no watermark exists to remove",
"details": "Requested entity was not found."
}
```
# Set YouTube Watermark
Source: https://www.ayrshare.com/docs/apis/utils/set-youtube-watermark
POST /post/youTubeWatermark
Set a watermark image on your YouTube channel for all videos. Configure when it shows during playback with the timingType, offsetMs, and durationMs fields.
Set a watermark image on your YouTube channel that will appear on all your videos. The watermark can be configured to appear at specific times during video playback.
Position is automatically set to bottom-right corner (YouTube API default)
All timing parameters `timingType`, `offsetMs`, and `durationMs` are required when setting a watermark
Watermarks apply to all videos on the channel (existing and future)
The watermark image must meet YouTube's [watermark media guidelines](/docs/media-guidelines/youtube#watermark)
You must have proper permissions to manage the YouTube channel
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**mediaUrls** `array` *required*
Array containing the watermark image URL. Only the first URL will be used.
Use a PNG or JPEG image. The URL must contain `.png`, `.jpg`, or `.jpeg`, otherwise the request
returns error `101`. YouTube rejects WebP images with error `307`.
The image must be square (1:1), 150 to 1920 pixels wide and high, and 10 MB or smaller.
**timingType** `string` *required*
When to show the watermark during video playback.
Available timingType options:
* `offsetFromStart` - Show watermark starting from a specific time after video begins
* `offsetFromEnd` - Show watermark starting from a specific time before video ends
**offsetMs** `number` *required*
Time offset in milliseconds. Must be a positive number.
* For `offsetFromStart`: Time after video starts to show watermark
* For `offsetFromEnd`: Time before video ends to show watermark
**durationMs** `number` *required*
How long to display the watermark in milliseconds. Must be a positive number.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"mediaUrls": ["https://example.com/watermark.png"],
"timingType": "offsetFromStart",
"offsetMs": 15000,
"durationMs": 30000
}' \
-X POST https://api.ayrshare.com/api/post/youTubeWatermark
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
fetch("https://api.ayrshare.com/api/post/youTubeWatermark", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${API_KEY}`
},
body: JSON.stringify({
mediaUrls: ["https://example.com/watermark.png"],
timingType: "offsetFromStart",
offsetMs: 15000,
durationMs: 30000
}),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {
'mediaUrls': ['https://example.com/watermark.png'],
'timingType': 'offsetFromStart',
'offsetMs': 15000,
'durationMs': 30000
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
}
r = requests.post('https://api.ayrshare.com/api/post/youTubeWatermark',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
['https://example.com/watermark.png'],
'timingType' => 'offsetFromStart',
'offsetMs' => 15000,
'durationMs' => 30000
]);
$curl = curl_init($apiUrl);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => $data
]);
$response = curl_exec($curl);
if ($response === false) {
echo 'Curl error: ' . curl_error($curl);
} else {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
}
curl_close($curl);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace SetYouTubeWatermark_csharp
{
class SetYouTubeWatermark
{
private static readonly HttpClient client = new HttpClient();
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post/youTubeWatermark";
// Set up request headers
client.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
// Prepare JSON content
string json = @"{
""mediaUrls"": [""https://example.com/watermark.png""],
""timingType"": ""offsetFromStart"",
""offsetMs"": 15000,
""durationMs"": 30000
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// Send POST request
HttpResponseMessage response = await client.PostAsync(url, content);
response.EnsureSuccessStatusCode();
// Read response
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
```
```json 200 theme={"system"}
{
"status": "success",
"message": "Watermark set successfully",
"mediaUrls": [
"https://example.com/watermark.png"
],
"timingType": "offsetFromStart",
"offsetMs": 1000,
"durationMs": 5000
}
```
```json 400 - Invalid Image Dimensions theme={"system"}
{
"action": "post",
"status": "error",
"code": 140,
"message": "The image aspect ratio must be between 1 and 1. Current image aspect ratio: 1085/723 (1.50). https://www.ayrshare.com/docs/media-guidelines/youtube "
}
```
```json 400 - Unsupported Format theme={"system"}
{
"status": "error",
"code": 307,
"message": "Media type 'image/webp' is not supported. "
}
```
```json 403 - Permission Error theme={"system"}
{
"status": "error",
"code": 250,
"message": "The watermark cannot be set for this channel. Please check that you have proper permissions and the channel is valid."
}
```
# User Lookups
Source: https://www.ayrshare.com/docs/apis/utils/user-lookups
GET /user/lookups
Retrieve the users (handles) looked up this month. Users are tracked when you pass the userId or userName fields to the analytics or history endpoints.
Retrieve the users (handles) looked up for the current month.
Users tracked when using the `userId` or `userName` fields for the analytics or history endpoints.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
```json 200: Current Month User Lookups theme={"system"}
{
"x": [
{
"userId": "1322691",
"userName": "neilpatel",
"name": "Neil Patel"
},
{
"userId": "20536157",
"userName": "Google",
"name": "Google"
}
]
}
```
```json 403: Unauthorized theme={"system"}
{
"action": "request",
"status": "error",
"code": 3,
"message": "You do not have permission to access this resource. Please contact us to get access."
}
```
# YouTube Categories
Source: https://www.ayrshare.com/docs/apis/utils/youtube-categories
GET /post/youtubeCategories/:region
Retrieve YouTube category IDs for a region by country code. Only categories marked assignable can be set in the POST or PATCH /post endpoints.
Retrieve the YouTube categories ids for a given region. The category ID can be set in the POST or PATCH /post endpoints.
For example, `GET https://api.ayrshare.com/api/post/youtubeCategories/US` for the U.S. video categories.
> [!WARNING]
>
> YouTube only allows certainly categories to be assigned by a user.
> Only categories where assignable is `true` can be set in the POST or PATCH /post endpoints when creating a YouTube post.
>
> Other categories, which are not assignable and have `assignable: false`, will result in an error.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Path Parameters
**region** `string` *required*
Two letter [country code](/docs/iso-codes/country) of the region.
```json 200: OK Retrieved Categories theme={"system"}
{
"region": "US",
"categories": [
{
"id": "1",
"title": "Film & Animation",
"assignable": true
},
{
"id": "10",
"title": "Music",
"assignable": true
},
{
"id": "15",
"title": "Pets & Animals",
"assignable": true
},
{
"id": "17",
"title": "Sports",
"assignable": true
},
{
"id": "18",
"title": "Short Movies",
"assignable": false
},
{
"id": "19",
"title": "Travel & Events",
"assignable": true
},
{
"id": "2",
"title": "Autos & Vehicles",
"assignable": true
},
{
"id": "20",
"title": "Gaming",
"assignable": true
},
{
"id": "21",
"title": "Videoblogging",
"assignable": false
},
{
"id": "22",
"title": "People & Blogs",
"assignable": true
},
{
"id": "23",
"title": "Comedy",
"assignable": true
},
{
"id": "24",
"title": "Entertainment",
"assignable": true
},
{
"id": "25",
"title": "News & Politics",
"assignable": true
},
{
"id": "26",
"title": "Howto & Style",
"assignable": true
},
{
"id": "27",
"title": "Education",
"assignable": true
},
{
"id": "28",
"title": "Science & Technology",
"assignable": true
},
{
"id": "29",
"title": "Nonprofits & Activism",
"assignable": true
},
{
"id": "30",
"title": "Movies",
"assignable": false
},
{
"id": "31",
"title": "Anime/Animation",
"assignable": false
},
{
"id": "32",
"title": "Action/Adventure",
"assignable": false
},
{
"id": "33",
"title": "Classics",
"assignable": false
},
{
"id": "34",
"title": "Comedy",
"assignable": false
},
{
"id": "35",
"title": "Documentary",
"assignable": false
},
{
"id": "36",
"title": "Drama",
"assignable": false
},
{
"id": "37",
"title": "Family",
"assignable": false
},
{
"id": "38",
"title": "Foreign",
"assignable": false
},
{
"id": "39",
"title": "Horror",
"assignable": false
},
{
"id": "40",
"title": "Sci-Fi/Fantasy",
"assignable": false
},
{
"id": "41",
"title": "Thriller",
"assignable": false
},
{
"id": "42",
"title": "Shorts",
"assignable": false
},
{
"id": "43",
"title": "Shows",
"assignable": false
},
{
"id": "44",
"title": "Trailers",
"assignable": false
}
]
}.
.
```
```json 400: Incorrect Region Code theme={"system"}
{
"action": "request",
"status": "error",
"code": 101,
"message": "Missing or incorrect parameters. Please verify with the docs. .../ayrshare.com/rest-api/endpoints"
}
```
# Check Post Length
Source: https://www.ayrshare.com/docs/apis/validate/check-post-length
POST /validate/postLength
Calculate the weighted length of a post string, check if the post length is valid, and get the maximum length allowed for each social network.
Calculate the weighted length, or character count, of a post string for Bluesky, Facebook, Google Business Profile, Instagram, LinkedIn, Pinterest, Reddit, Threads, TikTok, X/Twitter, and YouTube. Checks if the post length is valid and returns the maximum length allowed for each social network.
Special characters, such as ü, ø, or 😊, have a higher character count value (unicode).
This check is automatically done with the /post endpoint.
> [!NOTE]
>
> The weighted length is an estimate calculated by Ayrshare. Social networks may have slightly different character counts based on their own calculation methods.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**post** `string` *required*
Post string to calculate the length.
```bash cURL theme={"system"}
curl \
-H "Authorization: Bearer API Key" \
-H 'Content-Type: application/json' \
-d '{"post": "🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩"}' \
-X POST https://api.ayrshare.com/api/post/checkPostWeight
```
```javascript JavaScript theme={"system"}
const API_KEY = "API_KEY";
const post = "🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩";
fetch("https://api.ayrshare.com/api/post/checkPostWeight", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${API_KEY}`,
},
body: JSON.stringify({ post }),
})
.then((res) => res.json())
.then((json) => console.log(json))
.catch(console.error);
```
```python Python theme={"system"}
import requests
payload = {'post': '🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩'}
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'}
r = requests.post('https://api.ayrshare.com/api/post/checkPostWeight',
json=payload,
headers=headers)
print(r.json())
```
```php PHP theme={"system"}
request(
'POST',
'https://api.ayrshare.com/api/post/checkPostWeight',
[
'headers' => [
'Content-Type' => 'application/json',
'Authorization' => 'Bearer API_KEY'
],
'json' => [
'post' => ['🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩']
]
]
);
echo json_encode(json_decode($res->getBody()), JSON_PRETTY_PRINT);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace CheckPostWeightRequest_csharp
{
class CheckPostWeight
{
static async Task Main(string[] args)
{
string API_KEY = "API_KEY";
string url = "https://api.ayrshare.com/api/post/checkPostWeight";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + API_KEY);
var content = new StringContent(
"{\"post\" : \"🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩🍩\"}",
Encoding.UTF8,
"application/json"
);
try
{
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseBody);
}
catch (HttpRequestException e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
}
}
}
```
```json 200: OK Character length results theme={"system"}
{
"twitterWeightedLength": 2932,
"blueskyWeightedLength": 199,
"instagramWeightedLength": 3015,
"tikTokWeightedLength": 3017,
"linkedInWeightedLength": 3015,
"facebookWeightedLength": 3015,
"pinterestWeightedLength": 3015,
"youtubeWeightedLength": 3015,
"gmbWeightedLength": 3015,
"twitterValid": false,
"twitterLongValid": true,
"facebookValid": true,
"gmbValid": false,
"instagramValid": false,
"linkedInValid": false,
"pinterestValid": false,
"redditValid": true,
"tikTokValid": false,
"youtubeValid": true,
"maxCharLimits": {
"bluesky": 300,
"facebook": 63206,
"gmb": 1500,
"instagram": 2200,
"linkedin": 3000,
"pinterest": 500,
"reddit": 10000,
"tiktok": 2200,
"twitter": 280,
"twitterLong": 25000,
"youtube": 5000
}
}
```
# Check Subreddit Exists
Source: https://www.ayrshare.com/docs/apis/validate/check-subreddit
GET /validate/redditExists/:subreddit
Pass a subreddit name as a path parameter to verify it. The response returns a success status, the subReddit name, and an exists value of true or false.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Path Parameters
**subreddit** `string` *required*
The name of the subReddit to check if exists.
```json 200: Exists theme={"system"}
{
"status": "success",
"subReddit": "tesla",
"exists": true
}
```
```json 200: Does Not Exist theme={"system"}
{
"status": "success",
"subReddit": "jkhjkhjkhkjhj",
"exists": false
}
```
# Content Moderation
Source: https://www.ayrshare.com/docs/apis/validate/moderation
POST /validate/moderation
Use the content moderation API to analyze text with OpenAI models and see if it falls into harmful categories such as hate, harassment, or violence.
The content moderation API is designed to help developers identify potentially harmful or inappropriate text content.
This endpoint analyzes text input and categorizes it based on various types of concerning content.
## Key Features
Automatic detection of harmful content.
Multiple categories of problematic text.
Easy integration for content filtering.
## How It Works
When you submit text to the moderation endpoint, it uses OpenAI models to analyze the content. The API then returns results indicating whether the text falls into any of the defined problematic categories.
## Categories of Harmful Content
The API classifies text into the following categories:
| CATEGORY | DESCRIPTION |
| - | - |
| hate | Content that expresses, incites, or promotes hate based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. Hateful content aimed at non-protected groups (e.g., chess players) is harassment. |
| hate/threatening | Hateful content that also includes violence or serious harm towards the targeted group based on race, gender, ethnicity, religion, nationality, sexual orientation, disability status, or caste. |
| harassment | Content that expresses, incites, or promotes harassing language towards any target. |
| harassment/threatening | Harassment content that also includes violence or serious harm towards any target. |
| self-harm | Content that promotes, encourages, or depicts acts of self-harm, such as suicide, cutting, and eating disorders. |
| self-harm/intent | Content where the speaker expresses that they are engaging or intend to engage in acts of self-harm, such as suicide, cutting, and eating disorders. |
| self-harm/instructions | Content that encourages performing acts of self-harm, such as suicide, cutting, and eating disorders, or that gives instructions or advice on how to commit such acts. |
| sexual | Content meant to arouse sexual excitement, such as the description of sexual activity, or that promotes sexual services (excluding sex education and wellness). |
| sexual/minors | Sexual content that includes an individual who is under 18 years old. |
| violence | Content that depicts death, violence, or physical injury. |
| violence/graphic | Content that depicts death, violence, or physical injury in graphic detail. |
## Usage and Best Practices
1. **Optimal Text Length**: For best results, we recommend splitting long text into smaller chunks. Aim for segments of less than 2,000 characters each.
2. **Integration**: Use this API to automatically flag potentially problematic content in your applications, forums, or user-generated content platforms.
3. **Action on Results**: Based on the API's output, you can implement appropriate actions such as content filtering, user warnings, or further review processes.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
**text** `string` *required*
The text to analyze for moderation.
**imageUrl** `string`
The URL of the image. Must begin with `https://`.
```bash cURL theme={"system"}
curl --location 'https://api.ayrshare.com/api/validate/moderation' \
--header 'Authorization: Bearer API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"text": "Let'\''s kill '\''em all"
}'
```
```javascript JavaScript theme={"system"}
const url = "https://api.ayrshare.com/api/validate/moderation";
const apiKey = "API_KEY"; // Replace with your actual API key
const data = {
text: "Let's kill 'em all",
};
fetch(url, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
},
body: JSON.stringify(data),
})
.then((response) => response.json())
.then((result) => console.log(result))
.catch((error) => console.error("Error:", error));
```
```python Python theme={"system"}
import requests
url = 'https://api.ayrshare.com/api/validate/moderation'
api_key = 'API_KEY' # Replace with your actual API key
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
data = {
'text': "Let's kill 'em all"
}
response = requests.post(url, json=data, headers=headers)
if response.status_code == 200:
result = response.json()
print(result)
else:
print(f"Error: {response.status_code}")
print(response.text)
```
```php PHP theme={"system"}
"Let's kill 'em all"
];
$headers = [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
} else {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode == 200) {
$result = json_decode($response, true);
print_r($result);
} else {
echo "Error: HTTP Code " . $httpCode . "\n";
echo $response;
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
url := "https://api.ayrshare.com/api/validate/moderation"
apiKey := "API_KEY" // Replace with your actual API key
// Create the request body
requestBody, err := json.Marshal(map[string]string{
"text": "Let's kill 'em all",
})
if err != nil {
fmt.Println("Error creating request body:", err)
return
}
// Create a new request
req, err := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
if err != nil {
fmt.Println("Error creating request:", err)
return
}
// Set headers
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending request:", err)
return
}
defer resp.Body.Close()
// Read the response
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response:", err)
return
}
// Check the status code
if resp.StatusCode == http.StatusOK {
fmt.Println("Response:")
fmt.Println(string(body))
} else {
fmt.Printf("Error: Status Code %d\n", resp.StatusCode)
fmt.Println(string(body))
}
}
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
class Program
{
static async Task Main(string[] args)
{
string url = "https://api.ayrshare.com/api/validate/moderation";
string apiKey = "API_KEY"; // Replace with your actual API key
var data = new
{
text = "Let's kill 'em all"
};
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
var content = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json");
try
{
HttpResponseMessage response = await client.PostAsync(url, content);
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
Console.WriteLine("Response:");
Console.WriteLine(result);
}
else
{
Console.WriteLine($"Error: {response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());
}
}
catch (HttpRequestException e)
{
Console.WriteLine($"Request error: {e.Message}");
Console.WriteLine($"InnerException: {e.InnerException?.Message}"); // Include inner exception details for more information
}
}
}
}
```
```json 200: Response theme={"system"}
{
"status": "success",
"text": "Let's kill 'em all",
"moderation": [
{
"flagged": true,
"categories": {
"sexual": false,
"hate": false,
"harassment": false,
"self-harm": false,
"sexual/minors": false,
"hate/threatening": false,
"violence/graphic": false,
"self-harm/intent": false,
"self-harm/instructions": false,
"harassment/threatening": false,
"violence": true
},
"categoryScores": {
"sexual": 0.00002128273445123341,
"hate": 0.027735227718949318,
"harassment": 0.08523011207580566,
"self-harm": 0.0000021838018255948555,
"sexual/minors": 1.924875903114298e-7,
"hate/threatening": 0.0063302298076450825,
"violence/graphic": 0.00024857991957105696,
"self-harm/intent": 7.833968993509188e-7,
"self-harm/instructions": 8.686130570367823e-8,
"harassment/threatening": 0.07459623366594315,
"violence": 0.9833663702011108
}
}
]
}
```
```json 400: Bad Request theme={"system"}
{
"action": "generate",
"status": "error",
"code": 331,
"message": "There was an issue with the AI processing. Please try again and if the issue persists, contact us."
}
```
# Validate API Overview
Source: https://www.ayrshare.com/docs/apis/validate/overview
The Validate endpoints check posts for issues before publishing, such as text length, media formats, platform-specific rules, and moderation issues.
The Validate endpoints validate social posts and a JSON object.
The validate endpoints allow developers to check posts for potential issues or violations before publishing them to social media platforms.
This helps prevent failed posts by validating text length, media formats, and platform-specific rules.
For example, the endpoints can verify if a post passes validation, check if images meet size requirements, or see if there is a moderation issue.
This pre-validation helps improve post success rates and provides early feedback about potential issues.
# Validate JSON
Source: https://www.ayrshare.com/docs/apis/validate/validate-json
POST /validate/json
Send JSON as plain text to check if it is correctly formatted. Useful for debugging requests from no-code tools such as Bubble or Make, or 500 errors.
Send JSON to validate if correctly formatted.
You can validate your JSON by using either an online linter, such as [https://jsonlint.com/](https://jsonlint.com/) or using [Postman](/docs/testing/postman).
If you use a no-code tool such as Bubble or Make or receive a 500 "Bad Request" response, this endpoint is useful to debug the JSON request.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
Send data as text. If using Postman, please be sure to select "Text" as the type.
Set the `"Content-Type": "text/plain"`.
## Request Examples
Send any json to this endpoint to validate it.
Below is an example of invalid JSON using Postman. Can you spot it? A missing comma at the end on the second parameter `platforms`.
Be sure to set the `Content-Type` in the header as `text/plain` and select "Text" when sending.

```json 200: OK Valid JSON theme={"system"}
{
"status": "success",
"message": "Valid JSON. Nice Job!"
}
```
```json 400: Bad Request Invalid JSON theme={"system"}
{
"status": "error",
"message": "JSON is not valid. Unexpected end of JSON input while parsing near '{\"test\": \"happy}'"
}
```
# Validate Media
Source: https://www.ayrshare.com/docs/apis/validate/validate-media
Check if a media URL is valid and the media file exists and is accessible, or get metadata about a media file, using the media URL validation endpoints.
Please see here for more information on media URLs:
> [card] [Media URL Validation](/docs/apis/media/verify-media-url)
>
> Verify that the media file exists and is accessible
> [card] [Media Metadata](/docs/apis/media/get-media-metadata)
>
> Get metadata about a media file
# Validate a Post
Source: https://www.ayrshare.com/docs/apis/validate/validate-post
POST /validate/post
Send the same JSON you use for the /post endpoint to /validate/post to verify content and parameters before publishing. Posts are not sent to networks.
Before publishing a post, you can verify the content and parameters are correct.
Send the exact same JSON you normally send to the `/post` endpoint to the `/validate/post` endpoint and it will return a response with any issues found.
Validation tests based on Ayrshare's own internal algorithms.
Posts are not sent to the social networks.
Posts may still return an error by the social networks at the time of publishing, even with a
passed validation.
Please see the [/post endpoint](/docs/apis/post/overview) for sending a post.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
## Body Parameters
Send the same body as you would send to the [/post endpoint](/docs/apis/post/post).
```bash cURL theme={"system"}
curl --location "https://api.ayrshare.com/api/validate/post" \
--header "Content-Type: application/json" \
--header "Authorization: Bearer API_KEY" \
--data '{
"post": "This is amazing",
"platforms": [
"facebook"
],
"mediaUrls": ["https://img.ayrshare.com/012/gb.jpg"]
}'
```
```javascript JavaScript theme={"system"}
const apiKey = 'API_KEY'; // Replace with your actual API key
const requestData = {
post: "This is amazing",
platforms: ["facebook"],
mediaUrls: ["https://img.ayrshare.com/012/gb.jpg"]
};
fetch('https://api.ayrshare.com/api/validate/post', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
```
```python Python theme={"system"}
import requests
payload = {
'post': 'This is amazing',
'platforms': ['facebook'],
'mediaUrls': ['https://img.ayrshare.com/012/gb.jpg']
}
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer API_KEY'
}
r = requests.post(
'https://api.ayrshare.com/api/validate/post',
json=payload,
headers=headers
)
print(r.json())
```
```php PHP theme={"system"}
'This is amazing',
'platforms' => ['facebook'],
'mediaUrls' => ['https://img.ayrshare.com/012/gb.jpg']
];
$headers = [
'Content-Type: application/json',
'Authorization: Bearer API_KEY'
];
$ch = curl_init('https://api.ayrshare.com/api/validate/post');
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
```
```csharp C# theme={"system"}
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
var payload = new
{
post = "This is amazing",
platforms = new[] { "facebook" },
mediaUrls = new[] { "https://img.ayrshare.com/012/gb.jpg" }
};
using var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer API_KEY");
try
{
var response = await client.PostAsJsonAsync(
"https://api.ayrshare.com/api/validate/post",
payload
);
response.EnsureSuccessStatusCode();
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine(result);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
```
```go Go theme={"system"}
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
type PostRequest struct {
Post string `json:"post"`
Platforms []string `json:"platforms"`
MediaUrls []string `json:"mediaUrls"`
}
func main() {
// Create the request payload
payload := PostRequest{
Post: "This is amazing",
Platforms: []string{"facebook"},
MediaUrls: []string{"https://img.ayrshare.com/012/gb.jpg"},
}
// Convert payload to JSON
jsonData, err := json.Marshal(payload)
if err != nil {
log.Fatalf("Error marshaling JSON: %v", err)
}
// Create the request
req, err := http.NewRequest(
"POST",
"https://api.ayrshare.com/api/validate/post",
bytes.NewBuffer(jsonData),
)
if err != nil {
log.Fatalf("Error creating request: %v", err)
}
// Set headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer API_KEY")
// Make the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error making request: %v", err)
}
defer resp.Body.Close()
// Read the response
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error reading response: %v", err)
}
// Print the response
fmt.Println(string(body))
}
```
```json 200: Success theme={"system"}
{
"status": "success",
"message": "No validation issues were found with this post. This is only an Ayrshare validation test and the post may still return an error by the social networks.",
}
```
```json 200: Successful Post with Non-Fatal Warnings theme={"system"}
{
"status": "success",
"message": "No validation issues were found with this post. This is only an Ayrshare validation test and the post may still return an error by the social networks.",
"warnings": [
{
"action": "post",
"status": "error",
"code": 156,
"message": "Twitter is not linked with Ayrshare. Please confirm the linkage on the Social Accounts page in your dashboard."
}
]
}
```
```json 400: Error Post theme={"system"}
{
"action": "post",
"status": "error",
"code": 136,
"message": "Media URLs invalid. Please verify the media is an externally accessible URL."
}
```
# Webhook Actions
Source: https://www.ayrshare.com/docs/apis/webhooks/actions
Reference for the JSON payloads Ayrshare sends for scheduled posts, social linking, messages, comments, mentions, batch, feed, automations, and ads events.
There are several types of webhooks available and categorized by Action type.
For example, a scheduled post will trigger a `scheduled` action webhook.
Please see the [webhook overview](/docs/apis/webhooks/overview) for more details.
After registering a webhook URL, you will receive a POST request to your URL when an event occurs.
The POST request will include a JSON payload with the event details.
## Scheduled Action
You will receive this webhook notification when a scheduled post is processed — whether it succeeds or fails — and published to the selected social networks.
For example, if you schedule a post for 12:00 PM on August 1, 2026, the webhook will be sent at the exact time the post is published.
Webhook notifications are only sent for posts scheduled in the future using the `scheduleDate` field in the [/post](/docs/apis/post/post) endpoint.
### Scheduled Event
```json theme={"system"}
{
"action": "scheduled", // The action taken
"subAction": "tikTokPublished", // Only present when TikTok video publishing complete
"created": "2023-01-05T01:18:47Z",
"code": 200, // HTTP response code
"refId": "140b8700bd6ade089b242d845e268fb886130c53", // User Reference ID
"status": "success", // success or error
"id": "TBAAAqAMMpoweA9wKHUp", // Ayrshare id of post
"errors": [], // List of errors if any occurred
"postIds": [
// Individual successful posts status
{
"postUrl": "https://www.facebook.com/102775127855689_361718068618052",
"platform": "facebook",
"status": "success",
"id": "102775127855689_361718068618052"
}
],
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
> [!NOTE]
>
> You will not receive a webhook notification for immediate posts, because the API returns the success or failure response instantly in the JSON reply.
> Webhook notifications are only sent for scheduled posts, since these are processed asynchronously and require a separate notification to inform you of their status.
### TikTok Publishing Webhook
When working with TikTok via Ayrshare, you might receive two different webhooks for a scheduled post.
If your post was scheduled rather than immediate, you'll receive the standard **Scheduled Action** webhook first.
This indicates that the media has been successfully sent to TikTok for processing and posting.
Afterwards, you will receive the `subAction: tikTokPublished` webhook.
This is triggered once TikTok has completed processing the media and the media is made public.
This webhook is activated for both immediate posts and scheduled posts.
In the Ayrshare dashboard, this event is labeled as **tikTok (pub)**.
> [!WARNING]
>
> The `tikTokPublished` webhook is not sent until the [media is made public](/docs/apis/post/social-networks/tiktok#visibility-options).
> If the media is set to private, followers, or friends, the webhook will not be sent.
>
> If you do not receive the `tikTokPublished` webhook and the post status remains `pending`, check the TikTok mobile app to ensure the media has been accepted by TikTok.
## Social Action
Notification when a user's profile links or unlinks a social network.
### Social Action Event
```json theme={"system"}
{
"account": {
// Optional: identity of the linked social account. Sent for YouTube only today,
// not for other platforms.
"id": "UCxyz123", // Optional: the social network's own account id. For YouTube, the channel id
"handle": "@socialdept", // Optional: the @ handle, present when the account has one, such as a YouTube custom URL
"title": "The Social Dept.", // Optional: the social account's own title, such as the YouTube channel name
"profileUrl": "https://www.youtube.com/@socialdept" // Optional: the public URL of the social account
},
"action": "social", // The action taken
"created": "2023-01-05T01:18:47Z",
"code": 200, // HTTP response code
"details": {
// Optional: sent on unlink, if details are available
"status": "error",
"code": 349,
"message": "Account locked"
},
"displayName": "Google Account Name", // If a user account name is present at the social network
"hookId": "TKLc30192HLGw5UeJ46",
"platform": "youtube", // The social platform the action occurred on
"refId": "140b8700bd6ade089b242d845e268fb886130c53", // User Reference ID
"refreshBy": "2022-11-05T12:21:29Z", // Optional: If type is refresh, the date the social network authorization must be refreshed on the social account linkage page
"source": "system", // Optional: sent on unlink and refresh. Initiated by "system" or "user".
"title": "User Profile Name", // The user profile's account title
"type": "unlink", // Type of action: link, unlink, or refresh
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
The `account` object is sent when Ayrshare holds account-level identity for the linked platform, currently YouTube only, and is included on both `link` and `unlink` actions. Every key inside `account` is optional (a YouTube channel without a custom URL has no `handle`). When no identity is available, the `account` key is omitted entirely rather than sent as empty, so check that a key is present before using it.
Note that `account.title` is the social account's own title, such as the YouTube channel name, while the top-level `title` is your Ayrshare user profile's title. They are different values.
A `source` of `system` means Ayrshare automatically unlinked the account, such as when the social network connection is no longer valid. We recommend you notify your user so they can continue posting. Details of the unlinking are in the `details` field. An email will also be sent to the Primary Account email address, or [alt emails](/docs/multiple-users/manage-user-profiles#alternative-emails-for-alerts) if they have been set up.
A `source` of `user` means the user initiated the action themselves, such as they manually unlinked an account. An email will not be sent when a user initiated action occurs.
## Messages Action
The Messaging Add-On is required to access all messages endpoints and webhooks.
For Facebook and Instagram, receive notifications when a direct message arrives, is read, or has
a reaction created or deleted. WhatsApp currently sends a notification to your registered webhook
URL only when a new incoming message is stored.
X/Twitter webhooks are available as an option for Enterprise clients.
Please contact your account representative for more information about becoming an Enterprise client.
WhatsApp does not emit reaction or delivery-status webhooks to your registered URL today. Stored
outbound messages can have a `status` value of `sent`, `delivered`, `read`, or `failed` — see
[WhatsApp Message Status](#whatsapp-message-status) below.
### Standby coverage for Facebook Pages with multiple apps
Ayrshare subscribes to Facebook's `standby` webhook field in addition to the standard messaging fields. This means Messenger events are delivered to your webhook **even when another app on the same Facebook Page is currently holding thread control** — for example, when a chatbot platform is set as your Page's primary receiver, or when Meta's Page Inbox is actively handling a conversation.
There is no schema change for these events. They arrive as the same `messageCreated` / `messageRead` / `reactionCreated` / `messageEdited` payloads documented in the sections below. Two things to be aware of for Pages with a competing Messenger app installed:
* **Inbound message volume may increase** compared to the previous behavior, where standby events were silently dropped before being subscribed to. The new traffic represents messages your Page received that were being handled by the other app.
* **You may receive `messageCreated` events with `type: "sent"` that do not correspond to messages you sent through Ayrshare.** These are echoes of messages sent by the other Messenger app on your Page (Meta delivers a copy of every send to every subscribed app). If your integration reconciles outbound traffic against your own send history, you can use that history to distinguish your sends from a competing app's sends.
For Pages with only Ayrshare installed (no competing Messenger app), the only observable change is the new [Message Edit Event](#message-edit-event) — everything else looks identical to the previous behavior.
### New Message Events
Notification when a new message is sent or received.
> [!WARNING]
>
> **Some fields are optional when we cannot reach the social network.**
>
> `conversationId` and `senderDetails` are resolved from the social network at
> the moment the message arrives. When that lookup fails, rather than withhold
> the message, we deliver it with whatever we could resolve and set
> `connectionDegraded: true`.
>
> The usual cause is the profile's Instagram or Facebook connection having
> expired or been invalidated, but **any** failure of that lookup sets the flag,
> a transient Graph API error included. Tell them apart by whether it persists:
> on a single message it was a blip and needs nothing from you, while on every
> message from a profile the credential is dead and only re-linking clears it.
>
> ```json theme={"system"}
> {
> "action": "messages",
> "connectionDegraded": true, // present only when the lookup failed
> "id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlE...",
> "message": "This is an amazing message",
> "platform": "instagram",
> "recipientId": "17841401430400299",
> "senderId": "7270633706358444",
> "subAction": "messageCreated"
> // conversationId and senderDetails may be absent
> }
> ```
>
> When it does persist, treat it as a signal to re-link that profile's social
> account. A dead credential also blocks replying and fetching conversation
> history through the API, since both need the same credential. Messages keep
> arriving throughout, so no inbound data is lost.
>
> An Instagram account connected through a Facebook Page depends on that Page's
> credential, so re-link **Facebook first, then Instagram** — re-linking
> Instagram on its own will not clear the flag.
>
> Build your integration to tolerate `conversationId` and `senderDetails` being
> absent on any message. `id`, `senderId`, `recipientId` and `platform` are
> always present.
>
> Note that `message` is not guaranteed either, independently of connection
> health: an attachment-only message carries `attachments` with no `message`
> field, and an [unsupported message](#unsupported-messages) carries neither,
> with `unsupported: true` instead.
```json Facebook New Message theme={"system"}
{
"action": "messages",
"conversationId": "t_10161117434308936",
"created": "2024-06-07T11:58:44Z",
"hookId": "JC6IgqFjvDliTJ8MLqzE",
"id": "m_aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDUyMjEyNzA",
"message": "This is an amazing message",
"platform": "facebook",
"recipientId": "106638148652329",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"scheduleDate": "2024-06-07T11:58:44Z",
"senderDetails": {
// recipientDetails if type is sent
"id": "7270633706358444",
"picture": "https://scontent-ord5-2.cdninstagram.com/v/t51.jpg",
"username": "SweetMessage",
"name": "Sweet"
},
"senderId": "7270633706358444",
"subAction": "messageCreated",
"timeStamp": 1735189325, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "received", // received, sent, or deleted
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
```json Instagram New Message theme={"system"}
{
"action": "messages",
"conversationId": "aWdfZAG06MTpJR01lc3NhZA2VUaHJlYWQ6MTc4",
"created": "2024-06-07T11:58:44Z",
"hookId": "JC6IgqFjvDliTJ8MLqzE",
"id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDUyMjEyNzA",
"message": "This is an amazing message",
"platform": "instagram",
"recipientId": "17841401430400299",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"scheduleDate": "2024-06-07T11:58:44Z",
"senderDetails": {
// recipientDetails if type is sent
"id": "7270633706358444",
"picture": "https://scontent-ord5-2.cdninstagram.com/v/t51.jpg",
"username": "SweetMessage",
"name": "Sweet"
},
"senderId": "7270633706358444",
"subAction": "messageCreated",
"timeStamp": 1735189325, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "received", // received, sent, or deleted
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
```json WhatsApp New Message theme={"system"}
{
"action": "messages",
"attachments": [
{
"type": "image",
"url": "https://images.ayrshare.com/abc123/whatsapp_image_x9aB2pK7.jpg"
}
],
"conversationId": "14155551234",
"created": "2026-05-18T17:08:41Z",
"hookId": "JC6IgqFjvDliTJ8MLqzE",
"id": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI4OUYxRkExNzE0M0EwQTYwM0EA",
"message": "Here's the photo you asked for.",
"platform": "whatsapp",
"recipientId": "123456789012345",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"senderDetails": {
"id": "14155551234",
"name": "Jane Customer"
},
"senderId": "14155551234",
"timeStamp": 1747588121,
"title": "Primary Profile",
"url": "https://mysite.com/webhook"
}
```
For WhatsApp, `conversationId` and `senderId` are the correspondent's phone number in E.164
digits-only format. `recipientId` is Meta's phone-number ID for your linked WhatsApp account.
`timeStamp` is present when Webhook Security is enabled.
On every network, a message that carries media carries it in `attachments`, as shown in the WhatsApp
example above — Facebook and Instagram included. Attachment types can be `image`, `video`, `audio`,
`document`, or `sticker`. Message webhooks never carry `mediaUrls` — that field belongs to the
post-delivery payloads such as [Scheduled Action](#scheduled-action), where it reports the media you
sent.
#### Unsupported Messages
Facebook and Instagram do not expose every message type through their Messaging API. For a message
of a type they withhold, Meta sends only a flag, with no text and no media. Ayrshare forwards that as
`unsupported: true`, so the event arrives with neither `message` nor `attachments`:
```json theme={"system"}
{
"action": "messages",
"conversationId": "aWdfZAG06MTpJR01lc3NhZA2VUaHJlYWQ6MTc4",
"created": "2026-07-28T14:05:19Z",
"id": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEOjE3ODQxNDUyMjEyNzA",
"platform": "instagram",
"recipientId": "17841401430400299",
"senderId": "7270633706358444",
"subAction": "messageCreated",
"type": "received",
"unsupported": true
}
```
This is not a delivery failure: Meta never made the content available to Ayrshare.
[Get Messages](/docs/apis/messages/get-messages) returns the same message with `unsupported: true` and no
content, so the content cannot be recovered there either. Meta does not
say which message types it withholds. Plain text, links sent as text, and media all arrive normally,
so treat `unsupported: true` as a reason the event is empty, not as a sign that text or media are
being lost. If your integration replies automatically, check for this flag before treating an empty
message as something the sender needs to resend. The flag is absent on every other message; it is
never `false`.
### Message Read Event
Notification when a message is read by the recipient.
```json Facebook Read theme={"system"}
{
"action": "messages",
"conversationId": "t_10161117434308936",
"created": "2024-06-08T23:33:30Z",
"hookId": "CviPBMXEy3cdJnK0EESd",
"platform": "facebook",
"read": 1717889607802, // UNIX timestamp of when the message was read
"readerDetails": {
"name": "John Smith",
"id": "7101149746568444",
"picture": "https://platform-lookaside.fbsbx.com/platform/profilepic"
},
"recipientId": "106638148652329",
"refId": "9abf1426d6ce9122ef11c8932",
"scheduleDate": "2024-06-08T23:33:30Z",
"senderId": "7101149746568444",
"subAction": "messageRead",
"timeStamp": 1717889610, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "read",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
```json Instagram Read theme={"system"}
{
"action": "messages",
"conversationId": "t_10161117434308938",
"created": "2024-06-08T23:33:30Z",
"hookId": "CviPBMXEy3cdJnK0EESd",
"platform": "instagram",
"read": {
"mid": "aWdfZAG1faXRlbToxOkl" // Instagram message ID
},
"readerDetails": {
"name": "John Smith",
"id": "7101149746568444",
"picture": "https://platform-lookaside.fbsbx.com/platform/profilepic",
"username": "johnsmith"
},
"recipientId": "17841401430400299",
"refId": "9abf1426d6ce9122ef11c8932",
"scheduleDate": "2024-06-08T23:33:30Z",
"senderId": "7101149746568444",
"subAction": "messageRead",
"timeStamp": 1717889610, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "read",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
When a message is read on Instagram, the webhook payload includes an `mid` field that uniquely identifies which specific message was read.
For Facebook messages, message reads are tracked at the conversation level using the `conversationId`.
When a read event occurs, all messages in that conversation with timestamps before the `created` (or `read`) timestamp should be considered read by the user.
### Reaction Created and Deleted Events
Notification when a reaction, such as a like, is created or deleted on a message.
```json Facebook Reaction theme={"system"}
{
"action": "messages",
"conversationId": "t_10161117434308936",
"created": "2024-06-06T00:49:18Z",
"hookId": "LcgLuXzZki15lqBNt69h",
"platform": "facebook",
"reaction": "😮",
"recipientId": "106638148652329",
"refId": "9abf1426d6ce9432",
"scheduleDate": "2024-06-06T00:49:18Z",
"senderId": "7101149746568444",
"subAction": "reactionCreated", // reactionDeleted if deleted
"timeStamp": 1717634958, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "reaction",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
```json Instagram Reaction theme={"system"}
{
"action": "messages",
"conversationId": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEO",
"created": "2024-06-06T00:49:18Z",
"hookId": "LcgLuXzZki15lqBNt69h",
"platform": "instagram",
"reaction": "😮",
"recipientId": "17841401430400299",
"refId": "9abf1426d6ce9432",
"scheduleDate": "2024-06-06T00:49:18Z",
"senderId": "7101149746568444",
"subAction": "reactionCreated", // reactionDeleted if deleted
"timeStamp": 1717634958, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "reaction",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
### Message Edit Event
Notification when a user edits a message they previously sent. Available for Facebook and Instagram direct messages.
The `messageEdit.mid` field matches the `id` of the original `messageCreated` event, so consumers can correlate the edit with the original message. The `messageEdit.text` field carries the new, edited message text.
```json Facebook Message Edit theme={"system"}
{
"action": "messages",
"conversationId": "t_10161117434308936",
"created": "2024-06-06T00:49:18Z",
"hookId": "LcgLuXzZki15lqBNt69h",
"id": "m_xyz...", // Message ID — matches the original messageCreated event
"messageEdit": {
"mid": "m_xyz...",
"text": "the edited message text"
},
"platform": "facebook",
"recipientId": "106638148652329",
"refId": "9abf1426d6ce9432",
"senderId": "7101149746568444",
"subAction": "messageEdited",
"timeStamp": 1717634958, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "edit",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
```json Instagram Message Edit theme={"system"}
{
"action": "messages",
"conversationId": "aWdfZAG1faXRlbToxOklHTWVzc2FnZAUlEO",
"created": "2024-06-06T00:49:18Z",
"hookId": "LcgLuXzZki15lqBNt69h",
"id": "aWdfZAG1faXRlbToxOkl",
"messageEdit": {
"mid": "aWdfZAG1faXRlbToxOkl",
"text": "the edited message text"
},
"platform": "instagram",
"recipientId": "17841401430400299",
"refId": "9abf1426d6ce9432",
"senderId": "7101149746568444",
"subAction": "messageEdited",
"timeStamp": 1717634958, // Present if Webhook Security enabled
"title": "Primary Profile",
"type": "edit",
"url": "https://mysite.com/webhook" // Your webhook URL
}
```
### WhatsApp Message Status
WhatsApp tracks per-message delivery state on stored outbound messages using one `status` field:
`sent`, `delivered`, `read`, or `failed`. These updates are **not currently delivered to your
registered webhook URL** as separate events. If you need real-time delivery state, please contact
your Ayrshare account representative.
## Batch Action
Notification when a batch has completed processing and the file is available, such as [get all user profiles](/docs/apis/user/batch-all-users). You may access the file with the pre-signed URL in the `url` field.
### Batch Event
```json theme={"system"}
{
"action": "batch",
"batchType": "users",
"created": "2024-01-11T22:00:30Z",
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"source": "user",
"timeStamp": 1705010424, // Present with Webhook Security
"title": "Primary Profile",
"type": "batch",
"url": "https://storage.googleapis.com/batch.ayrshare.com/users/dfdf92jskd933r/users-batch-2024-01-11-22-00.json",
"urlExpires": "2024-01-18T22:00:04Z",
"userCount": 73
}
```
## Feed Action
Notification when a new RSS feed item is found for registered RSS feeds. Note: if the Webhook is active, new RSS items will not be automatically posted to the social networks.
### Feed Event
```json theme={"system"}
{
"action": "feed",
"created": "2023-01-05T01:18:47Z",
"code": 200, // HTTP response code
"refId": "140b8700bd6ade089b242d845e268fb886130c53", // User Reference ID
"title": "Title of profile if available", // optional, only if available
"data": { ... },
"url": "https://api.myapp.com/Webhook/Ayrshare/Feed" // Your webhook URL
}
```
## Mentions Action
Notification when your connected account is mentioned. Available for Instagram.
> [!NOTE]
>
> For Instagram, registering the `mentions` webhook is all that is required to receive mention events. The Messaging Add-On, messaging enablement, and relinking the social account are **not** needed.
Ayrshare relays Meta's native mention payload unchanged, adding only the standard envelope fields (`action`, `refId`, `hookId`, `url`, and `timeStamp` with Webhook Security), `subAction`, `platform`, and `postId`. The example below is the **Instagram** shape: `media_id`, plus `comment_id` when the mention is in a comment. **Facebook** Page mention events are **not currently delivered** — Ayrshare acknowledges them to Meta and does not forward them to your webhook. Only Instagram mentions are delivered today, so `platform` is always `instagram` on this action.
`postId` follows the same rule as on the [Comments Action](#comments-action), and on mentions it is usually absent: a mention normally happens on someone else's post, which has no Ayrshare id. It appears only when the mention is on a post you published through Ayrshare.
### Mention Event (Instagram)
```json theme={"system"}
{
"action": "mentions",
"subAction": "mention",
"platform": "instagram", // The social network the mention came from
"postId": "TBAAAqAMMpoweA9wKHUp", // Ayrshare post id; absent unless you published the post
"media_id": "17900000000000000", // Meta media the mention occurred on
"comment_id": "17900000000000001", // Present when the mention is in a comment
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1705010424, // Present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Mentions" // Your webhook URL
}
```
## Comments Action
Notification when a comment is created on your connected content. Available for Facebook, Instagram, and Threads.
> [!NOTE]
>
> For Instagram, registering the `comments` webhook is all that is required to receive comment events. The Messaging Add-On, messaging enablement, and relinking the social account are **not** needed. For Facebook, comment events are delivered only while messaging is enabled on the profile (the Messaging Add-On) — registering the `comments` webhook alone is not enough. Relinking the Facebook Page is **not** needed.
Ayrshare relays the social network's native comment payload unchanged, adding only the standard envelope fields (`action`, `refId`, `hookId`, `url`, and `timeStamp` with Webhook Security), `subAction`, `platform`, and `postId`. Threads events also carry a normalized `created` timestamp. The example below is the **Instagram** shape. **Facebook** comments arrive as a Page `feed` change with different field names (for example `comment_id`, `post_id`, `message`, and `from.name`); consult Meta's webhooks reference for the Facebook field set. Because every network delivers to the same `comments` endpoint, branch on `platform` — `instagram`, `facebook`, or `threads` — instead of inferring the network from which field names are present.
### Correlating a comment with the post you created
`postId` is the Ayrshare `id` of the post the comment was left on — the same id [Post](/docs/apis/post/post) returned when you created it, and the one [Get Post](/docs/apis/post/get-post) and [Post Analytics](/docs/apis/analytics/post) accept. It is there so you can match a comment to your own post without keeping your own map of native ids.
It is **best-effort, and omitted when it cannot be determined**. Treat a missing `postId` as "this comment is not on a post I published", never as an error. It is absent when:
* **The post was not published through Ayrshare.** Comment events fire for everything on your connected account, including posts created directly in the Instagram, Facebook, or Threads app. Those have no Ayrshare id at all — nothing can recover one.
* **The post was published before this field shipped.** Correlation data is recorded at publish time and is not backfilled, so comments on your older posts will not carry a `postId`.
* **The post was published by a different [User Profile](/docs/apis/profiles/overview).** Several profiles can be connected to the same Page, Instagram account, or Threads account and all receive the comment event, but only the profile that actually published the post is told its id.
`postId` is the Ayrshare post id, not the network's. The network's own id for the same post is still where it always was — `media.id` on Instagram, `post_id` on Facebook, `root_post.id` on Threads.
### Comment Event (Instagram)
```json theme={"system"}
{
"action": "comments",
"subAction": "comment",
"platform": "instagram", // The social network the comment came from
"postId": "TBAAAqAMMpoweA9wKHUp", // Ayrshare post id; absent unless you published the post
"id": "17900000000000002", // Comment ID
"text": "Great post!",
"from": {
"id": "1234567890",
"username": "alice"
},
"media": {
"id": "17900000000000000", // ID of the commented media
"media_product_type": "FEED" // e.g. FEED, REELS, STORY
},
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1705010424, // Present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Comments" // Your webhook URL
}
```
### Comment Event (Threads)
Threads sends the `comments` action when someone replies to a connected account's post or to another reply in that conversation. Use `id` as the reply ID, `replied_to.id` as its direct parent, and `root_post.id` as the original Threads post. `postId` is the Ayrshare id of that original post when you published it through Ayrshare. `is_verified` and `profile_picture_url` describe the reply's author and are included when Threads provides them. If Threads sends the same reply more than once, Ayrshare delivers it to your webhook once.
```json theme={"system"}
{
"action": "comments",
"created": "2026-10-20T09:45:42.000Z",
"hookId": "58f50bbf382b6d175b1d7733ad18e904cc37019c56e30222e861d8b04a93dbb0",
"id": "18345678901234568",
"is_verified": false,
"media_type": "TEXT_POST",
"permalink": "https://www.threads.com/@alice/post/DEF456",
"platform": "threads",
"postId": "TBAAAqAMMpoweA9wKHUp", // Ayrshare post id; absent unless you published the post
"profile_picture_url": "https://scontent.cdninstagram.com/...",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"replied_to": {
"id": "18345678901234560"
},
"root_post": {
"id": "18345678901234560",
"owner_id": "12345678901234567",
"username": "your_account"
},
"shortcode": "DEF456",
"subAction": "comment",
"text": "Great post!",
"timeStamp": 1705010424, // Present with Webhook Security
"timestamp": "2026-10-20T09:45:42+0000",
"username": "alice",
"url": "https://api.myapp.com/Webhook/Ayrshare/Comments"
}
```
## Automations Action
Notification when an automation fires a webhook action for a user, such as a comment or DM auto-reply trigger.
Two event types are sent on the `automations` action, distinguished by the `type` field. See [webhook events](/docs/apis/automations/overview#webhook-events) for the full field reference.
| `type` | Sent when |
| - | - |
| `automation.triggered` | The automation's `fire_webhook` action runs. Not sent when the dispatch is rate-limited or that action is deduplicated |
| `automation.completed` | The dispatch reached a terminal status. Off by default: only sent when the automation's `fire_webhook` action sets `"sendCompletedEvent": true` |
### Automation Triggered Event
```json theme={"system"}
{
"action": "automations",
"type": "automation.triggered",
"automationId": "a1B2c3D4",
"triggerId": "t9X8y7Z6",
"trigger": "comment_keyword", // The trigger type that fired the automation (varies by automation)
"platform": "instagram",
"recipientId": "17841400000000000",
"recipientUsername": "alice", // null if unavailable
"keyword": "INFO", // null if not keyword-triggered; the emoji for dm_reaction
"activityId": "a1B2c3D4:t9X8y7Z6:comment.received:17912345678901234", // Matches the row in GET /automations/:id/activity
"actionId": "act_wh_1", // The fire_webhook action that sent this; matches actionResults[].actionId
"eventId": "comment.received:17912345678901234",
"commentId": "17912345678901234", // null for DM-driven triggers
"commentText": "INFO please", // DM text for dm_keyword, the emoji for dm_reaction, null when there is none
"postId": "17895695668004550", // null unless the engagement was on a post or reel
"storyId": null, // null unless the engagement was on a story
"timestamp": "2026-05-27T22:00:30Z", // When the automation fired (ISO 8601)
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1705010424, // Unix timestamp, present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Automations" // Your webhook URL
}
```
### Automation Completed Event
```json theme={"system"}
{
"action": "automations",
"type": "automation.completed",
"automationId": "a1B2c3D4",
"triggerId": "t9X8y7Z6",
"trigger": "comment_keyword",
"platform": "instagram",
"recipientId": "17841400000000000",
"activityId": "a1B2c3D4:t9X8y7Z6:comment.received:17912345678901234", // Join key; matches automation.triggered when one was sent
"eventId": "comment.received:17912345678901234",
"status": "sent", // sent, failed, rate_limited, deduplicated, skipped, or auth_error
"actionResults": [ // Empty when rate_limited, deduplicated, or failed after the retry limit
{ "actionId": "act_dm_1", "type": "send_dm", "status": "sent", "errorDetails": null },
{ "actionId": "act_wh_1", "type": "fire_webhook", "status": "sent", "errorDetails": null }
],
"errorDetails": null, // Reason when skipped, rate_limited, or failed after the retry limit
"timestamp": "2026-05-27T22:00:58Z",
"hookId": "Kq2LmN8pR4tV6wXyZ1aB",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"timeStamp": 1705010452,
"url": "https://api.myapp.com/Webhook/Ayrshare/Automations"
}
```
## Demo Action
Notification for [Demo User Profile](/docs/apis/profiles/overview#demo-user-profiles) lifecycle events. Demo User Profiles are available on the Enterprise plan; please [contact us](mailto:support@ayrshare.com) if you'd like to learn more.
Two event types are sent on the `demo` action, distinguished by the `type` field:
* `upgradeWarning` — sent two days before a Demo User Profile automatically converts to a standard User Profile, so you can prompt your user to continue or remove the profile.
* `upgrade` — sent when the conversion happens.
### Demo Upgrade Warning Event
```json theme={"system"}
{
"action": "demo",
"type": "upgradeWarning",
"demoExpires": "2026-08-27T09:57:00.000Z", // When the profile converts (ISO 8601)
"daysUntilUpgrade": 2,
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1787734624, // Unix timestamp (2026-08-26), present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Demo" // Your webhook URL
}
```
### Demo Upgrade Event
```json theme={"system"}
{
"action": "demo",
"type": "upgrade",
"upgradedAt": "2026-08-28T08:57:04.000Z", // When the profile converted (ISO 8601)
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1787907424, // Unix timestamp (2026-08-28), present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Demo" // Your webhook URL
}
```
## Suspended Action
Notification when a User Profile is [suspended or reactivated](/docs/multiple-users/manage-user-profiles#reactivate-a-suspended-user-profile). Suspensions can originate from a social network's own enforcement (for example a Facebook identity verification requirement), from [rate limit protection](/docs/errors/errors-http#rate-limit-protection-and-suspension-policy), or from billing issues. While a profile is suspended, API calls for that profile return HTTP 403.
Two event types are sent on the `suspended` action, distinguished by the `type` field:
* `suspend` — sent when the profile is suspended. Pause posting and other activity for this profile until it is reactivated.
* `unsuspend` — sent when the profile is reactivated.
Events are sent only when the profile's overall suspension state changes. A profile can be suspended for more than one cause at a time: if an additional cause is added while the profile is already suspended, or one of several causes is resolved while another still applies, no event is sent — the profile remains suspended, and the next event is the `unsuspend` when the last cause clears. To inspect the current suspension state at any time, use [Get Profiles](/docs/apis/profiles/get-profiles) with `include=suspension`.
A platform-triggered suspension is usually preceded by a [Social Action](#social-action) unlink event for the affected network. The suspension can only be resolved at the source — for example, a Facebook identity check must be completed by the Page admin in the Facebook app — after which the profile can be reactivated and the network relinked.
### Suspend Event
```json theme={"system"}
{
"action": "suspended",
"type": "suspend",
"reason": "Confirm your identity before you can publish as this Page.", // As reported by the platform or Ayrshare; null if unavailable
"platform": "facebook", // The social network that triggered the suspension; null if not platform-triggered
"origin": "policy", // What suspended the profile: policy, payment, rateLimit, manual, report, or subscription; null if unavailable
"suspendedAt": "2026-08-13T20:31:41.128Z", // When the suspension occurred (ISO 8601); null if unavailable
"unsuspendAt": null, // Scheduled automatic reactivation (ISO 8601), when already known
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1786731101, // Unix timestamp, present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Suspended" // Your webhook URL
}
```
### Unsuspend Event
```json theme={"system"}
{
"action": "suspended",
"type": "unsuspend",
"reason": null,
"platform": null,
"origin": null,
"suspendedAt": null,
"unsuspendAt": null,
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1786988742, // Unix timestamp, present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Suspended" // Your webhook URL
}
```
> [!NOTE]
>
> Suspension events are sent for the profile whose state changed. You can also check suspension state on demand with [Get Profiles](/docs/apis/profiles/get-profiles) using `include=suspension`, which returns `isSuspended`, `reason`, `suspendedAt`, `unsuspendAt`, and `suspensionCount`.
## Ads Action
Notification when one of your [ads](/docs/apis/ads/overview) changes, or when an ad account is connected, disconnected or loses access. You receive events for ads and campaigns created through Ayrshare, on every ads platform.
Every ad event on the `ads` action carries the same core fields (`platform`, `adId`, `postId`, `status`, `review`, `spend`), so you can read them all the same way; a few event types add their own fields, listed below the table. Check `type` to see what changed:
* `created` — an ad was created, for example by [Boost Post](/docs/apis/ads/facebook/boost-post).
* `campaignCreated` — a campaign was created with no post behind it, for example a Google Search campaign. `adId` is `null` and `campaignId` names the campaign.
* `statusChanged` — an ad's status changed: you paused, activated, deleted or archived it through Ayrshare, or, for a Facebook or Instagram ad, Meta reported a new status for it. A change to the status the ad already has sends nothing.
* `reviewChanged` — the platform's review of an ad changed. For a Facebook or Instagram ad, it is sent when Meta reports a policy problem with it (`rejected`, with Meta's explanation in `review.reasons`) or reports it as approved (`approved`). Meta does not report when an ad goes into review, and does not report every approval, so do not wait for this event to learn that an ad was approved. An approved ad that Meta rejects later sends it again. The same review sent again sends nothing. For a Google ad, Ayrshare checks Google's review once a night and sends this event when Google approves the ad (`approved`, also when Google approves it with limits) or disapproves it (`rejected`, with the policies behind it in `review.reasons`), and again if Google's decision changes later. A Google event also carries `campaignId`; for a Performance Max asset, `adId` is `asset:~~` (`asset:~~` for a brand asset). See [Ad Review](/docs/apis/ads/google/get-campaign#ad-review).
* `spendSynced` — the nightly spend sync ran for your ads. Sent once per run for each User Profile whose ads were synced, with yesterday's (UTC) total spend on each ads platform, which can be `0`. Get each ad's spend from [Ad History](/docs/apis/ads/facebook/get-ad-history).
Ad account events (Google Ads and TikTok ads) have their own fields, described in [Ad Account Events](#ad-account-events):
* `accountConnected` — an ad account was connected to the User Profile.
* `accountDisconnected` — an ad account was disconnected from the User Profile. Unlinking TikTok in the Ayrshare dashboard or on the social linking page sends it too. Unlinking TikTok through the API or in Ayrshare Connect, or deleting the User Profile, also removes the ad account but does not send this event.
* `accountRevoked` — Google Ads only. The ad account lost access, for example because its owner removed Ayrshare's access in Google. Ads calls for it fail until it is connected again.
| Field | Description |
| - | - |
| `platform` | The ads platform, for example `facebook`. `null` on `spendSynced`, which lists them in `platforms`. |
| `adId` | The ad's ID on the platform. `null` on `campaignCreated` and `spendSynced`. |
| `postId` | The Ayrshare post ID the ad boosts, when there is one. |
| `status` | The ad's status in lower case, for example `active`, `paused`, `deleted` or `archived`. |
| `review` | The platform's review as Ayrshare last recorded it: `status` and `reasons`, the platform's explanation. `null` when no review is recorded. |
| `spend` | On `spendSynced` only. `date` is the day reported (yesterday, UTC). `byPlatform` holds that day's total spend on each platform in `currency` (always `USD`); a platform's total is `null` when some of its spend could not be read or converted to USD. `ads` is the number of ads synced and `days` the number of daily spend records written. `null` on every other event. |
Fields only some events add:
| Field | Event | Description |
| - | - | - |
| `campaignId` | `campaignCreated`, `reviewChanged` (Google) | The campaign's ID on the platform. |
| `campaignType` | `campaignCreated` | The campaign's type, for example `search`, when the platform has one. |
| `platforms` | `spendSynced` | The ads platforms the sync covered for this User Profile. |
### Ad Created Event
```json theme={"system"}
{
"action": "ads",
"type": "created",
"platform": "facebook",
"adId": "120210000000000001",
"postId": "s7FKDaV0Ux2QYqVsHbE1", // Ayrshare post ID
"status": "active",
"review": null, // Not reviewed yet
"spend": null,
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083", // User Reference ID
"timeStamp": 1791892800, // Unix timestamp, present with Webhook Security
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads" // Your webhook URL
}
```
### Ad Review Changed Event
```json theme={"system"}
{
"action": "ads",
"type": "reviewChanged",
"platform": "facebook",
"adId": "120210000000000001",
"postId": "s7FKDaV0Ux2QYqVsHbE1", // Ayrshare post ID
"status": "active",
"review": {
"status": "rejected",
"reasons": ["Ad rejected", "Your ad doesn't follow our Advertising Policies."] // Meta's explanation
},
"spend": null,
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads"
}
```
### Spend Synced Event
```json theme={"system"}
{
"action": "ads",
"type": "spendSynced",
"platform": null,
"platforms": ["facebook", "linkedin"],
"adId": null,
"postId": null,
"status": null,
"review": null,
"spend": {
"date": "2026-10-12", // The day reported (yesterday, UTC)
"currency": "USD",
"byPlatform": {
"facebook": 12.34, // That day's total spend on the platform
"linkedin": 0
},
"ads": 3, // Ads synced in this run
"days": 12 // Daily spend records written
},
"hookId": "dI3PNhrG83j2FzAFJqkb",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads"
}
```
### Ad Account Events
An ad account event concerns no ad, so it has no `adId`, `postId`, `status`, `review` or `spend`. Each one is sent once: `hookId` is the same if the same event is ever sent again, so you can ignore a repeat.
| Field | Description |
| - | - |
| `platform` | The ads platform: `google` or `tiktok`. |
| `accountId` | The ad account's ID on the platform, when the event concerns exactly one. `null` for Google Ads, whose connection covers every account the Google login can reach, and for a TikTok connection that covers several advertisers. |
| `id` | Identifies this event. The same event always has the same `id`. |
| `created` | When the event happened, as an ISO 8601 date-time in UTC. |
#### Account Connected Event
```json theme={"system"}
{
"action": "ads",
"type": "accountConnected",
"platform": "tiktok",
"accountId": "7012345678901234567", // TikTok advertiser ID
"id": "3f1c9a2b7d4e8f0a6b5c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a",
"created": "2026-10-20T10:00:00.000Z",
"hookId": "3f1c9a2b7d4e8f0a6b5c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2b",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads"
}
```
#### Account Disconnected Event
```json theme={"system"}
{
"action": "ads",
"type": "accountDisconnected",
"platform": "google",
"accountId": null, // A Google Ads connection names no single account
"id": "8b2d4f6a1c3e5a7b9d0f2e4c6a8b0d2f4e6a8c0b2d4f6e8a0c2b4d6f8e0a2c4b",
"created": "2026-10-20T11:30:00.000Z",
"hookId": "8b2d4f6a1c3e5a7b9d0f2e4c6a8b0d2f4e6a8c0b2d4f6e8a0c2b4d6f8e0a2c4c",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads"
}
```
#### Account Revoked Event
Sent when an ads call finds that Google no longer accepts the connection, for example after the account owner removed Ayrshare's access. Connect Google Ads again to resume ads calls.
```json theme={"system"}
{
"action": "ads",
"type": "accountRevoked",
"platform": "google",
"accountId": null,
"id": "c4e6a8b0d2f4e6a8c0b2d4f6e8a0c2b4d6f8e0a2c4b6d8f0a2c4e6b8d0f2a4c6",
"created": "2026-10-21T08:15:00.000Z",
"hookId": "c4e6a8b0d2f4e6a8c0b2d4f6e8a0c2b4d6f8e0a2c4b6d8f0a2c4e6b8d0f2a4c7",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"url": "https://api.myapp.com/Webhook/Ayrshare/Ads"
}
```
# Webhook History
Source: https://www.ayrshare.com/docs/apis/webhooks/history
GET /hook/history
Retrieve recent webhook delivery history grouped by action type, such as social, scheduled, or messages. Filter by action to get the last 6 months.
## Webhook History
Retrieve recent webhook delivery history grouped by action type.
The response object contains keys for each action (e.g., `social`, `scheduled`, `messages`, `feed`, `batch`, `mentions`, `comments`, `automations`, `demo`, `suspended`, `ads`), and each key maps to an array of events.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**action** `string`
The webhook action to return history for the last 6 months. If not provided, all actions will be returned.
Available actions: `social`, `scheduled`, `messages`, `feed`, `batch`, `mentions`, `comments`, `automations`, `demo`, `suspended`, `ads`
```json 200: Response theme={"system"}
{
"social": [
{
"action": "social",
"created": "2025-09-05T23:13:08Z",
"displayName": "ayrshare",
"hookId": "6e4zPT9ozQpxD72WX78r",
"platform": "snapchat",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"source": "user",
"timeStamp": 1757166928,
"title": "Primary Profile",
"type": "link",
"url": "https://us-central1-ayrshare.cloudfunctions.net/testFeed"
},
{
"action": "social",
"created": "2025-09-05T23:13:00Z",
"displayName": "madworlds25",
"hookId": "UWURsgBxTdRAMCZJV4Yp",
"platform": "snapchat",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"source": "user",
"timeStamp": 1757166928,
"title": "Primary Profile",
"type": "unlink",
"url": "https://us-central1-ayrshare.cloudfunctions.net/testFeed"
}
],
"scheduled": [
{
"action": "scheduled",
"created": "2025-09-06T17:02:08Z",
"errors": [],
"hookId": "AxGbVck3Y7hsOdbAqVp7",
"id": "NAOWq7cQf1CsyT7vqTng",
"idShare": "v_pub_url~v2.7547022225119938615",
"isVideo": true,
"mediaUrls": [
"https://img.ayrshare.com/random/portrait8.mp4"
],
"platform": "tiktok",
"platforms": [
"tiktok"
],
"post": "Wise men talk because they have something to say; fools, because they have to say something. - Plato",
"postIds": [
{
"status": "success",
"idShare": "v_pub_url~v2.7547022225119938615",
"id": "7547022158694731022",
"isVideo": true,
"platform": "tiktok",
"postUrl": "https://www.tiktok.com/@helmar1066/video/7547022158694731022",
"mediaUrls": [
"https://img.ayrshare.com/random/portrait8.mp4"
]
}
],
"postUrl": "https://www.tiktok.com/@helmar1066/video/7547022158694731022",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"source": "user",
"status": "success",
"subAction": "tikTokPublished",
"timeStamp": 1757180499,
"title": "Primary Profile",
"url": "https://us-central1-ayrshare.cloudfunctions.net/testFeed"
},
{
"action": "scheduled",
"created": "2025-09-05T11:26:07Z",
"errors": [],
"hookId": "D92f6fMh2FCxNps4S27O",
"id": "2Is9BJkDdoBv01K4nZ2k",
"idShare": "v_pub_url~v2.7546563700132497463",
"isVideo": true,
"mediaUrls": [
"https://img.ayrshare.com/random/portrait6.mp4"
],
"platform": "tiktok",
"platforms": [
"tiktok"
],
"post": "Either write something worth reading or do something worth writing. - Benjamin Franklin",
"postIds": [
{
"status": "success",
"idShare": "v_pub_url~v2.7546563700132497463",
"id": "7546564355161099533",
"isVideo": true,
"platform": "tiktok",
"postUrl": "https://www.tiktok.com/@helmar1066/video/7546564355161099533",
"mediaUrls": [
"https://img.ayrshare.com/random/portrait6.mp4"
]
}
],
"postUrl": "https://www.tiktok.com/@helmar1066/video/7546564355161099533",
"refId": "9abf1426d6ce9122ef11c72bd62e59807c5cc083",
"source": "user",
"status": "success",
"subAction": "tikTokPublished",
"timeStamp": 1757180499,
"title": "Primary Profile",
"url": "https://us-central1-ayrshare.cloudfunctions.net/testFeed"
}
]
}
```
# List Registered Webhooks
Source: https://www.ayrshare.com/docs/apis/webhooks/list
GET /hook/webhook
Return the webhook URLs registered for your account. Set allWebhooks to true to list the webhooks for every User Profile, including the Primary Profile.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Query Parameters
**allWebhooks** `boolean` (default: false)
Return all registered webhooks for every User Profile associated with your account, including the Primary Profile.
You only need to provide the Primary Profile's API Key in the request header.
```json 200: Response theme={"system"}
{
"batch": "https://mywebsite.com/hook",
"batchUpdated": "2024-01-11T20:05:18Z",
"feedUpdated": "2024-01-11T18:20:50Z",
"refId": "1c72bd62e59807fdfdc5cc083",
"scheduled": "https://mywebsite.com/hook",
"scheduledUpdated": "2023-12-06T01:54:08Z",
"social": "https://mywebsite.com/hook",
"socialUpdated": "2023-10-26T03:20:46Z",
"status": "success",
"updated": "2024-01-11T18:20:51Z"
}
```
```json 200: Using allWebhooks theme={"system"}
{
"webhooks": [
{
"messages": "https://mywebsite.com/hook",
"messagesUpdated": "2024-12-11T09:58:22Z",
"refId": "d0b8bf39805ae442094137f09f2417bf2s82",
"scheduled": "https://mywebsite.com/hook",
"scheduledUpdated": "2024-02-19T12:17:12Z",
"status": "success",
"updated": "2023-08-22T11:55:06Z"
}
]
}
```
# Webhooks Overview
Source: https://www.ayrshare.com/docs/apis/webhooks/overview
Webhook API Endpoints to register webhooks to receive updates on events
## What is a Webhook?
A Webhook allows you to be notified when certain system *actions* occur via a call to a URL you provide. Webhooks are also known as "URL Callbacks" or "HTTP push calls". Your URL must use SSL and begin with HTTPS.
> [card] [Webhook Actions](/docs/apis/webhooks/actions)
>
> See the available actions for webhooks.
### Understanding Ayrshare Webhooks
Webhooks are categorized by the specific action and are *registered at the Primary Profile or User Profile level*. Any updates for the Primary or User Profiles are sent first to the registered Webhook for the User Profile. If User Profile does not have a registered Webhook, the update will be sent to the Primary Profile registered Webhook.
For example:
If a User Profile has a registered Social Action Webhook and unlinks TikTok,
the registered Social Action Webhook URL for the User Profile will be
called. The Primary Profile webhook *will not* be called.
If a User Profile unlinks TikTok and *does not* have a registered Social
Action Webhook, but the Primary Profile does have a registered Webhook, the
registered Social Action Webhook URL for the Primary Profile will be called.
### Register a Webhook
Register a Webhook by providing an endpoint URL and the type of action type to the POST [`/hook/webhook`](/docs/apis/webhooks/register) endpoint. When the action occurs an `HTTP POST` message will be sent to the provided URL.
E.g. register a URL to get notified of the status of scheduled post.
The Webhook endpoint URL should not use redirects and must be the final destination URL.
If you only register the Primary Profile webhook, the User Profiles will automatically inherit the Primary Profile webhook.
To have a unique webhook for each User Profile, you must register a webhook for each User Profile.
> [!NOTE]
>
> After your Webhook receives the `HTTP POST`, your server **must** respond with
> an HTTP status of `200` to mark the call as successful. If your server does
> not respond within 15 seconds, the attempt is recorded as failed and
> retried. Respond as soon as you receive the request and do your processing
> asynchronously — a timeout is not a rejection, so if your handler completes
> the work but answers late, the retry will make you process it twice.
You can also register webhooks in the Developer Dashboard.
### Webhook Retries
Whether a failed delivery is retried depends on **how** it failed. Every retry carries the same `hookId`. The payload is rebuilt for each attempt, so `timeStamp` — and the signature over it — can differ.
**Retried — transient failures.** A `429`, `408`, or `425` response, any `5xx`, a timeout, or a dropped connection gets up to 9 dispatch attempts over roughly an hour — the first send plus 8 retries. If it is still failing after that, delivery is attempted again on a decaying schedule — about 5 minutes, 30 minutes, 2 hours, and 12 hours later. **A delivery can therefore arrive up to around 16 hours after the original event.**
**Not retried — rejections.** Any other `4xx` response, such as `400`, `401`, `403`, `404`, or `410`, is treated as final on the first attempt. These say the request itself was rejected, and repeating it cannot change the outcome.
> [!WARNING]
>
> If your endpoint returns a `4xx` only intermittently — a `404` while you deploy, or a
> `401` from a credential that has just expired — that delivery will **not** be retried.
> [`GET /hook/history`](/docs/apis/webhooks/history) (Premium) lists your recent deliveries so you
> can see which ones failed. It is read-only and does not resend them.
### Delivery Semantics and Idempotency
Ayrshare delivers webhooks **at least once**. **Occasional duplicates are normal operation, not a defect — every consumer needs idempotency as a permanent property.**
Duplicates arrive in two different shapes, and each needs a different key:
| Duplicate | Why it happens | What is identical | Key that catches it |
| - | - | - | - |
| **A retry of one delivery** | Your endpoint returned `429`, `408` or `425`, or a `5xx`, timed out, or the connection dropped | `hookId`. The payload is **not** byte-identical: it is rebuilt per attempt, so `timeStamp` — and the signature over it — differ | `hookId` |
| **The same event notified again** | The social network sends us the event a second time, or it is re-observed upstream | The payload's own identifiers, such as `id` on `messages` — but `hookId` is **different** | a key you build from the payload |
`hookId` identifies **one delivery** of an event. It is identical on every retry of that delivery, so claiming on it makes retries safe — but a fresh notification of the same underlying event arrives with a **new** `hookId`, so `hookId` on its own will not recognize that case.
Recommended receiver pattern:
1. **Respond first.** Return `2xx` immediately, then process asynchronously. A timeout is not a rejection — if you finish the work but answer late, the event is sent again.
2. **Claim `hookId` atomically** the moment the request arrives — a unique constraint, an `INSERT ... ON CONFLICT DO NOTHING`, or a `SET NX` — **not** a read-then-write check. Two attempts can arrive concurrently, and a check-then-act guard lets both through.
3. **Claim a key of your own too**, built from the payload, so a second notification carrying a new `hookId` is still recognized. On `messages`, `id` combined with `subAction` works well.
4. **Then** do the work, holding both claims long enough to cover the retry window and any later re-notification. Because a retry can arrive up to around 16 hours later, **hold your claims for at least 24 hours**. A claim that expires sooner will not recognize a late retry, and you will process the same event twice.
> [!NOTE]
>
> The payload's `id` is not unique on its own for every event type — the same
> message id recurs across edits and reactions, and `messageRead` payloads carry
> no `id` — so combine it with `subAction` rather than using it bare.
### Delivery Metadata Headers
Every delivery carries two headers identifying that specific transmission, so you can tell an original from a retry:
```bash theme={"system"}
X-Ayrshare-Delivery-Id :
X-Ayrshare-Delivery-Attempt : <0 on the first send, then 1, 2, ...>
```
`X-Ayrshare-Delivery-Attempt` is `0` on the first send and increments by one on each retry, so any value above `0` means we have already sent this delivery at least once. Treat it as an unbounded counter rather than a fixed set of values — the number of retries is an operational detail that can change. `X-Ayrshare-Delivery-Id` is unique to each attempt — quote it to support and it identifies the exact delivery record.
These identify the **transmission**; `hookId` identifies the **event**. Deduplicate on `hookId`, not on the delivery id — the delivery id is different on every attempt by design, so nothing would ever be recognized as a duplicate.
## Webhook Security
You may choose to add additional security by setting HMAC authentication as an HTTP request. This is often done to prevent replay attacks. Ayrshare uses [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) to hash the body of the message and includes it and the UNIX timestamp in the header of the POST.
```bash theme={"system"}
X-Authorization-Timestamp :
X-Authorization-Content-SHA256 :
```
Based on a secret key set when [registering your webhook](/docs/apis/webhooks/register), you may validate the post by comparing the header `X-Authorization-Content-SHA256` with the HMAC-SHA256 of the POST body. The signing secret is profile-wide — one secret per User Profile, used across all of that profile's webhook actions, so setting it for one action changes it for all actions on that profile. Multi-profile accounts manage a separate secret per profile (target a profile with the `Profile-Key` header).
## Webhook Logs
In the Ayrshare Dashboard, you may view the [active webhooks](https://app.ayrshare.com/webhooks), see the details of the Webhook sent, your server response status, and resend the Webhook to the registered URL.
Switch to a particular User Profile to view that profile's Webhook logs.
### HTTP Response Codes
The first column indicates a successful HTTP response ✔️ (200, 300) from the Webhook or a failed response ✖️ (400, 500).
Switch to a particular user profile to view that profile's Webhook logs.
### Error Rate
The "Error Rate" of the most recent 1,000 posts can be viewed on both the Actions and Webhook Logs pages within the dashboard. Any webhook response from your server of 400-500 is considered an error.
# Register Webhook
Source: https://www.ayrshare.com/docs/apis/webhooks/register
POST /hook/webhook
Register a webhook URL for an action such as scheduled, messages, or comments on the Primary Profile or a User Profile, with an optional HMAC secret.
## Register Webhook
Register a new Webhook for one of the [available actions](/docs/apis/webhooks/actions).
A Webhook may be registered with the Primary Profile or a User Profile.
You may also register a webhook in the Ayrshare dashbboard on the Webhooks page.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**action** `string` *required*
Available actions: `feed`, `social`, `scheduled`, `batch`, `messages`, `mentions`, `comments`, `automations`, `demo`, `suspended`, `ads`
**url** `string` *required*
The URL to be called on action. URL must be in a valid format and begin with *https\://*
The URL must be reachable on the public internet. A URL that points to `localhost`, a private network address such as `192.168.x.x` or `10.x.x.x`, or another internal address returns error `101`.
**secret** `string`
Secret text used for HMAC. Please see [overview](/docs/apis/webhooks/overview#webhook-security) for more details.
```json 200: Response theme={"system"}
{
"status": "success",
"action": "scheduled",
"url": "https://mysite.com/hook",
"refId": "3dc079614bdc3f281d9" // User Profile Ref Id
}
```
# Rotate Signing Secret
Source: https://www.ayrshare.com/docs/apis/webhooks/rotate-signing-secret
POST /hook/webhook/secret
Safely rotate your webhook signing secret with a 24-hour dual-signing grace window
## Overview
Ayrshare signs every webhook delivery with an [HMAC-SHA256](https://en.wikipedia.org/wiki/HMAC) of the payload, keyed by your **signing secret**, so your receiver can confirm a delivery genuinely came from Ayrshare. See [Webhook Security](/docs/apis/webhooks/overview#webhook-security) for how verification works.
Rotating your signing secret lets you replace it on a regular schedule, or immediately if you suspect it has been exposed. To make rotation safe, Ayrshare opens a **24-hour grace window** after every rotation during which deliveries are signed with **both** your previous and your new secret. This lets you update your receiver on your own schedule without dropping or rejecting a single delivery — the same pattern used by Stripe and GitHub.
> [!NOTE]
>
> The signing secret is **profile-wide**: there is one secret per User Profile (UID),
> and it signs **every** webhook action that profile has registered. There is no
> per-action signing secret — setting or rotating the secret changes it for all
> actions on that profile at once.
## Rotate from the Dashboard
You can set or rotate your signing secret from the [Webhooks page](https://app.ayrshare.com/webhooks) in the Developer Dashboard. The **Signing Secret** panel appears above your webhook list once the profile has at least one registered webhook.
**1. Open the Signing Secret panel**
Go to the [Webhooks page](https://app.ayrshare.com/webhooks). If no secret is configured yet, the panel shows **No signing secret configured** with a **Set Signing Secret** button. If one is already configured, it shows **Signing secret configured** with a **Rotate** button.
**2. Set or Rotate**
Click **Set Signing Secret** (first-time) or **Rotate** (existing secret). A modal opens with a strong, randomly generated secret pre-filled and revealed. You can **Copy** it, **Regenerate** a new one, or toggle **paste my own** to supply your own value.
**3. Confirm**
Copy the secret somewhere safe — it is shown only once and can never be retrieved from the UI again — then confirm to submit. A success toast appears and the panel updates.
**4. Update your receiver**
On a rotation (not a first-time set), the panel shows an active grace-window indicator and the **Rotate** button is disabled until the window closes. You have 24 hours to deploy the new secret to your receiver.
## Rotate via the API
Rotate (or set) the signing secret with a single call. This creates a new secret, repoints the profile's secret reference to it, and — when an existing secret was in place — records the superseded secret as the previous secret with an expiry 24 hours out.
### Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
### Body Parameters
**secret** `string` *required*
The new signing secret value. Any non-empty string is accepted. We recommend a long, high-entropy random value (for example, 32 random bytes encoded as base64url).
```bash cURL theme={"system"}
curl --request POST \
--url https://api.ayrshare.com/api/hook/webhook/secret \
--header 'Authorization: Bearer YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--header 'Profile-Key: YOUR_PROFILE_KEY' \
--data '{
"secret": "your-new-signing-secret"
}'
```
```json 200: Response theme={"system"}
{
"status": "success",
"action": "webhook",
"refId": "3dc079614bdc3f281d9" // User Profile Ref Id
}
```
The plaintext `secret` is **never** returned in the response and is never logged. The response carries the client-facing `refId` (a hash of the UID), never the UID itself. The `Profile-Key` header is optional and scopes the rotation to a single User Profile for multi-profile accounts.
A missing or empty `secret` returns a mapped error (`code: 101`, "Missing/incorrect parameter") with an HTTP `400` status, and no change is made to your current secret. First-time set via the API (no existing secret) creates the secret with no previous secret recorded and no grace window.
## Safe Rotation Procedure
Because of the 24-hour grace window, there is no required order of operations — your receiver keeps working throughout. The recommended sequence is:
**1. Rotate the secret**
Rotate from the dashboard or via the API. Ayrshare immediately begins signing deliveries with both your previous and your new secret.
**2. Update your receiver**
Within 24 hours, deploy the new secret to your webhook receiver so it verifies against the new value.
**3. Let the window close**
After 24 hours, Ayrshare automatically clears the previous secret and signs only with the new secret. No further action is needed on your side.
> [!TIP]
>
> If you rotate again while a grace window is still open, the just-superseded
> secret becomes the new previous secret and a fresh 24-hour window starts. Only
> one previous secret is kept at a time.
## Verifying Signatures During the Grace Window
Outside of a grace window, signed deliveries carry the standard headers (see [Webhook Security](/docs/apis/webhooks/overview#webhook-security)):
```bash theme={"system"}
X-Authorization-Timestamp :
X-Authorization-Content-SHA256 :
X-Authorization-Content-SHA256-V2 : v1=
```
During the 24-hour window after a rotation, the new `X-Authorization-Content-SHA256-V2` header lists **both** signatures, current first, comma-separated:
```bash theme={"system"}
X-Authorization-Timestamp :
X-Authorization-Content-SHA256 :
X-Authorization-Content-SHA256-V2 : v1=,v1=
```
> [!NOTE]
>
> `X-Authorization-Content-SHA256` is unchanged: it always carries the single
> current-secret HMAC, for backward compatibility. Dual signatures appear only in
> the new `X-Authorization-Content-SHA256-V2` header.
Each value in `X-Authorization-Content-SHA256-V2` is prefixed with a scheme tag. `v1=` denotes an HMAC-SHA256 signature, computed exactly like `X-Authorization-Content-SHA256`. The `-V2` header is always present whenever a delivery is signed — it carries at least `v1=` — so you can rely on it as a stable receiver contract.
To verify a delivery during (or outside) a rotation:
**1. Compute the HMAC**
Compute the HMAC-SHA256 of the **raw request body** using your locally configured signing secret.
**2. Compare against every listed signature**
Read `X-Authorization-Content-SHA256-V2`, split it on commas, strip the `v1=` prefix from each value, and accept the delivery as authentic if your computed HMAC matches **any** listed `v1=` signature.
Accepting if **any** listed signature matches is what makes rotation zero-downtime: a receiver still configured with the old secret matches `v1=`, while a receiver updated to the new secret matches `v1=` — both succeed throughout the window.
### Receiver Verification Example
```javascript Node.js theme={"system"}
import crypto from "crypto";
// secret is the signing secret currently configured on your receiver.
function isAuthenticWebhook(rawBody, headers, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody) // the raw, unparsed request body
.digest("hex");
const headerValue = headers["x-authorization-content-sha256-v2"] || "";
// Accept if ANY v1= signature in the header matches our computed HMAC.
return headerValue
.split(",")
.map((part) => part.trim())
.filter((part) => part.startsWith("v1="))
.map((part) => part.slice("v1=".length))
.some((sig) => {
const sigBuf = Buffer.from(sig);
const expectedBuf = Buffer.from(expected);
// timingSafeEqual throws on length mismatch — treat as not authentic.
return (
sigBuf.length === expectedBuf.length &&
crypto.timingSafeEqual(sigBuf, expectedBuf)
);
});
}
```
> [!WARNING]
>
> Always compute the HMAC over the **raw** request body bytes, exactly as
> received — not over a re-serialized JSON object. Re-serialization can change
> whitespace or key order and break verification. Use a constant-time comparison
> (such as `crypto.timingSafeEqual`) to avoid timing attacks.
If a delivery's current secret record is missing, the delivery proceeds **unsigned** (no signature headers) rather than failing. If only the previous secret record is gone, the previous signature is skipped and the current signature is still emitted in both headers.
# Unregister Webhook
Source: https://www.ayrshare.com/docs/apis/webhooks/unregister
DELETE /hook/webhook
Unregister the webhook associated with an action, such as feed, social, scheduled, messages, or comments, by sending the action in the request body.
Unregister the webhook associated with the action.
## Header Parameters
**Authorization** `string` *required*
[API Key](/docs/apis/overview#authorization) of the Primary Profile.
Format: `Authorization: Bearer API_KEY`
**Profile-Key** `string`
[Profile Key](/docs/apis/overview#profile-key-format) of a User Profile.
Format: `Profile-Key: PROFILE_KEY`
## Body Parameters
**action** `string` *required*
Available actions: `feed`, `social`, `scheduled`, `batch`, `messages`, `mentions`, `comments`, `automations`, `accountActivity`, `demo`, `suspended`, `ads`
```json 200: Response theme={"system"}
{
"status": "success",
"action": "scheduled",
"refId": "3dc079614bdc3f281d9" // User Profile Ref Id
}
```
# Bluesky Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/bluesky
Link Bluesky to Ayrshare from the Social Accounts page by entering your Bluesky handle and a one-time app password, which is not your Bluesky password.
## How to Link Bluesky with Ayrshare
Authorizing Bluesky with Ayrshare is simple. You will need to enter your Bluesky handle and a one-time app password.
> [!WARNING]
>
> The Bluesky app password is used to sign in to other Bluesky clients without giving full access to your account or password.
> You will create this one-time app password in settings section of Bluesky and in the format of XXXX-XXXX-XXXX-XXXX.
>
> **The app password is not your Bluesky password you use to sign in to Bluesky.**
>
> Please see below for more information.
## Authenticating Bluesky
**1. Click to Link Bluesky**
On the Social Accounts page, click the Bluesky icon.

**2. Bluesky Authentication**
The Bluesky authentication page will open in a modal.
1. Enter your Bluesky handle, which can be found in your [Bluesky profile](https://bsky.app/). For example, `@ayrshare` or `ayrshare.bsky.social`.
2. Create a one-time [Bluesky app password](https://bsky.app/settings/app-passwords). Copy the password to your clipboard. The password should be in the format of XXXX-XXXX-XXXX-XXXX.
3. Enter the app password in the Ayrshare Bluesky App Password field and click **Submit**.

**3. Confirmation**
Your Social Accounts page will now be updated with your Bluesky account.

You can publish Bluesky posts, get Bluesky analytics, and manage Bluesky
comments.
## Additional Bluesky Information
* Please see here for details on
[posting to Bluesky](/docs/apis/post/social-networks/bluesky),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/bluesky)
# Facebook Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/facebook
Link a Facebook Page to Ayrshare step by step. Only Facebook Pages are supported, and you must be a Page admin and grant all permissions during linking.
## How to Link Facebook with Ayrshare
Only **Facebook Pages** are supported. Facebook does not support Personal Facebook accounts.
> [!WARNING]
>
> Please be sure you are the admin of Facebook Page and **grant all permissions** to Pages and
> selections. Failure to do so might prevent successful posting.
### Linking Facebook
**1. Click to Link Facebook**
Click the Facebook icon on the Social Accounts page.

**2. First Time Facebook Linking**
A Facebook sign-in will pop-up - *please be sure you allow pop-ups*.
If you have already linked Facebook with Ayrshare, skip ahead to step 4.

**3. Choose a Facebook Page**
Follow the instructions and select a Facebook Page to link.
**4. Continue**
Click **Continue as NAME** on the login page.

**5. Choose a Facebook Page**
Once back at the Ayrshare dashboard, choose a Facebook page to link and click **Submit**.

**6. Confirmation**
Your Social Accounts page will now be updated with your Facebook account.

## Troubleshooting Facebook
If you're having issues with Facebook please see the [troubleshooting guide](/docs/help-center/technical-support/facebook-or-instagram-linking-issues).
## Additional Facebook Information
* Please see here for details on
[posting to Facebook](/docs/apis/post/social-networks/facebook),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/facebook-pages)
# Google Business Profile Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/google-business
Claim and verify your Google Business Profile listing, then link it to Ayrshare by choosing the Google account that is an admin of your GBP page.
## Claim Your Google Business Profile Page
Google Business Profile (GBP) requires you to [claim](https://support.google.com/business/answer/2911778) your business listing page.
After claiming your listing, you can then link it with Ayrshare, which includes verifying your business with Google.
Check if your business [has been verified](https://business.google.com/locations).
Be sure to choose the Google account that is an admin of your GBP page during link authorization process.
See our [Google Business Profile article](https://www.ayrshare.com/blog/google-my-business-what-is-gmb-why-you-need-it-and-how-to-use-it/) to learn more.
## Additional Google Business Profile Information
Please see here for details on [posting to Google Business
Profile](/docs/apis/post/social-networks/google), [getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or [retrieving history.](/docs/apis/history/overview)
For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/google-business-profile)
# Instagram Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/instagram
Link Instagram to Ayrshare directly or through a Facebook Page, switch to a Business or Creator account, and set up music attach eligibility for Reels.
## How to Link Instagram
> [!TIP]
>
> The following is a guide for linking Instagram. There are two ways to link Instagram:
>
> 1. [Directly link Instagram account to Ayrshare.](#linking-instagram-directly)
> 2. [Link Instagram account to Ayrshare through a Facebook page.](#linking-instagram-through-facebook-facebook-page-required)
>
> Option 1 is the default option. If option 2 is preferred, in the dashboard settings, disable [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) to use the authentication flow that requires a Facebook Page to be associated with the Instagram account.
>
> **Attaching music to a Reel requires option 2.** Meta's Audio API is not available to accounts linked with Instagram Login, so if you plan to use [`audioConfiguration`](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel), choose option 2 now rather than relinking later. See [Music Attach Eligibility](/docs/dashboard/connect-social-accounts/instagram#music-attach-eligibility-facebook-login-required).
Instagram does not support Personal Facebook accounts, Creator Studio accounts, and publishing to IGTV.
[See below](/docs/dashboard/connect-social-accounts/instagram#enabling-an-instagram-business-or-creator-account) for details.
### Linking Instagram Directly
**1. Click To Link Instagram**
Click the Instagram icon on the Social Accounts page.

**2. Instagram Login**
Input your credentials and click **Log in**.

**3. Instagram Permissions**
Toggle on all permissions for Instagram and click **Allow**.

**4. Confirmation**
Your Social Accounts page will now be updated with your Instagram account.

### Linking Instagram through Facebook (Facebook Page required)
> [!WARNING]
>
> Please be sure you are the admin of the Facebook Page and **grant all
> permissions** to Pages, Instagram accounts, and selections. Failure to do so
> might prevent successful posting.
This Instagram linking process authenticates through Facebook, so when you
click the Instagram icon on the Social Accounts page, a Facebook sign-in will
pop-up - *please be sure you allow pop-ups*.
**1. Click To Link Instagram**
Click the Instagram icon on the Social Accounts page.

**2. First Time Instagram Linking**
The first time you link Instagram, you will be asked to authenticate through Facebook.
If you have previously connected Ayrshare with Facebook and don't see this image, skip to step 3.

**3. Edit Instagram Settings**
Click **Edit Settings** during the initial set up.

**4. Authorize Instagram Business or Creator Accounts**
Check all the **Instagram Business Accounts** or **Instagram Creator Accounts**. Click **Next**.

**5. Select Facebook Pages**
Check all **Facebook Pages**. Click **Next**.

**6. Enable Permissions**
Turn all permissions to **Yes**. Click **Done**. You will then be asked to
select a specific Instagram account to link with Ayrshare. Once selected your
link will be complete and you can start posting to Instagram.

**7. Confirmation**
Your Social Accounts page will now be updated with your Instagram account.

## Enabling an Instagram Business or Creator Account
Instagram requires that your account be an **Instagram Business** or **Instagram Creator** account.
Learn more about [Instagram Business and Creator accounts](https://www.ayrshare.com/blog/understanding-the-difference-between-the-instagram-business-creator-and-personal-profiles/).
### Switching from a Personal to Business or Creator Account
If you have a personal Instagram account, open up the Instagram mobile app:
Go to your profile and tap the menu icon ≡ in the upper right corner to go
to the **Settings and activity** page.
Scroll down to the **For professionals** section.
Click **Account type and tools**.
Tap **Switch to professional account**.
Go through the steps to set up your account and select **Creator** or
**Business**.
### Switching from a Business to Creator or Vice Versa
If you want to switch from a Business account to a Creator account or vice versa:
Go to your profile and tap the menu icon ≡ in the upper right corner to go
to the **Settings and activity** page.
Scroll down to the **For professionals** section.
Click **Creator (or Business) tools and controls**.
Scroll down and tap **Switch account type**.
Choose to switch to **Creator** or **Business**.
> [!NOTE]
>
> If you do have a Business or Creator account, but when linking Ayrshare you
> receive an error saying your account isn't a Creator or Business, try
> switching your Instagram back to **Personal** and then back to **Creator** or
> **Business**. This often resets the account type and allows you to link
> Ayrshare.
## Music Attach Eligibility (Facebook Login required)
> [!WARNING]
>
> **Attaching music to a Reel only works when Instagram is linked through Facebook Login.** If your
> Instagram is linked with **Instagram Login** — the Ayrshare default — every audio request fails
> with `code: 514`, including [audio search](/docs/apis/media/search-instagram-audio), [audio
> lookup](/docs/apis/media/get-instagram-audio), and publishing a Reel with
> [`audioConfiguration`](/docs/apis/post/social-networks/instagram#adding-music-to-a-reel).
This is a restriction Meta places on its Audio API, not an Ayrshare limitation. Meta's documentation states the API "is only available on the Instagram API with Facebook Login. It is not supported on the Instagram API with Instagram Login." There is no permission, plan, or setting on the Ayrshare side that changes it.
### What you need
An Instagram **Business** or **Creator** account. Personal accounts cannot publish through the API at all.
A **Facebook Page connected to that Instagram account**. This is what the Facebook Login flow authenticates against.
Instagram linked to Ayrshare with **Instagram Login disabled**, so the Facebook Login flow is used.
### Setting it up
**1. Confirm the account is Business or Creator**
See [Enabling an Instagram Business or Creator
Account](/docs/dashboard/connect-social-accounts/instagram#enabling-an-instagram-business-or-creator-account)
below. A Personal account cannot be used.
**2. Connect a Facebook Page to the Instagram account**
In Facebook, switch to the Page, then go to **Settings & privacy → Settings → Permissions →
Linked accounts** and connect Instagram. The direct link is
[facebook.com/settings/?tab=linked\_profiles](https://www.facebook.com/settings/?tab=linked_profiles).
Full detail in [Connect a Facebook Page to
Instagram](/docs/dashboard/connect-social-accounts/instagram#connect-a-facebook-page-to-instagram)
below.
**3. Disable Instagram Login in Ayrshare**
In the Ayrshare dashboard, turn off [Instagram
Login](/docs/multiple-users/manage-user-profiles#instagram-login). This switches linking to the
Facebook Login flow, which requires the connected Page.
**4. Relink Instagram**
Unlink the Instagram account in Ayrshare and link it again, following [Linking Instagram through
Facebook](/docs/dashboard/connect-social-accounts/instagram#linking-instagram-through-facebook-facebook-page-required).
Grant **all** permissions, including `instagram_content_publish`.
An account that was already linked with Instagram Login **must be relinked**. Changing the
dashboard setting alone does not convert an existing connection.
**5. Verify**
Call [audio search](/docs/apis/media/search-instagram-audio) with any keyword. A `200` with a
`status` of `success` confirms eligibility. A `514` means the account is still on Instagram
Login.
> [!NOTE]
>
> **Instagram Login is the default and is the right choice for most accounts.** It is simpler and
> needs no Facebook Page. Only switch to Facebook Login if you need the Audio API or another
> Facebook-Login-only feature, since relinking interrupts posting until it completes.
## Connect a Facebook Page to Instagram
If you already have an Instagram Business or Creator Account and want to connect a Facebook Page, see [detailed instructions](https://www.facebook.com/business/help/898752960195806).
Or if you want to [create a new Facebook Page](https://www.facebook.com/pages/create/?ref_type=site_footer).
**1. Open Linked accounts**
Log into Facebook on desktop and switch to the **Facebook Page** you want to connect (not your
personal profile). Go to **Settings & privacy → Settings**, then in the left-hand panel find the
**Permissions** section and click **Linked accounts**.
The direct link is
[facebook.com/settings/?tab=linked\_profiles](https://www.facebook.com/settings/?tab=linked_profiles)
while you have the Page selected.
The panel lists **Instagram**. If a connection already exists it reads
`Connected to @yourhandle`. If it reads `Not connected`, click the row.

**2. Connect the account**
Click **Connect account** and complete the Instagram login prompt.
Note Facebook's own wording here: it connects your Facebook Page to a **professional** Instagram
profile. A personal Instagram profile is switched to professional as part of connecting, which
is the Business or Creator account type Ayrshare requires.

> [!WARNING]
>
> Facebook has moved this setting. Older guides (including earlier versions of this page) tell you
> to open **Settings → Instagram** from a "Manage Page" menu. That path no longer exists. The
> setting now lives under **Permissions → Linked accounts**, as described above.
## Troubleshooting Instagram
If you're having issues with Instagram please see the [troubleshooting guide](/docs/help-center/technical-support/facebook-or-instagram-linking-issues).
## Additional Instagram Information
> [!NOTE]
>
> Instagram has very strict requirements on image sizes. Images must have a width of at least 1080 pixels with an aspect ratio between 1.91:1 and 4:5.
>
> Ayrshare will detect if your image size is not correct and respond with an error.
* Please see here for details on [posting to Instagram](/docs/apis/post/social-networks/instagram), [getting analytics](/docs/apis/analytics/overview), [managing comments](/docs/apis/comments/overview), or [retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/instagram)
# LinkedIn Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/linkedin
Link a LinkedIn company page or personal account. Company pages need a Super Admin or Content Admin, and LinkedIn must be re-linked every 365 days.
## How to Link LinkedIn with Ayrshare
You can link either your LinkedIn company page or personal LinkedIn account.
When authorizing LinkedIn with Ayrshare, you will be redirected to LinkedIn to login and authorize Ayrshare to access your LinkedIn account.
**1. Click to Link LinkedIn**
Click the LinkedIn icon on the Social Accounts page.

**2. Login to LinkedIn**
The page will be redirected to LinkedIn and request you to login to your
LinkedIn account. If you have previously authorized Ayrshare with LinkedIn,
this step may be skipped.

**3. Accept Authorization Permissions**
Accept the authorization permissions Ayrshare is requesting.

**4. Select Company or Personal LinkedIn Account**
You will be redirected back to Ayrshare where you can select the LinkedIn
account type you wish to link with Ayrshare. You may either choose a LinkedIn company page or your personal LinkedIn account.
> [!NOTE]
>
> If selecting to link a company page, the LinkedIn user must be a Super Admin
> or Content Admin of the company page to link it with Ayrshare.

**5. Select a Company Page**
If you chose "Company Page", you will see a list of company pages you can select from.
Remember, the LinkedIn user must be a Super Admin or Content Admin of the company page to link it with Ayrshare.

**6. Confirmation**
Your Social Accounts page will now be updated with your LinkedIn account.

## LinkedIn Troubleshooting
The most common error you may encounter is a company page not showing in the list of available companies.
This can be caused by the LinkedIn user not being a Super Admin or Content Admin of the company page and can be fixed by a Super Admin of the company page [updating the user's page role](https://www.linkedin.com/help/linkedin/answer/a1481267).
## LinkedIn Link Expiration
LinkedIn must be re-linked every 365 days.
Please see the [user endpoint](/docs/apis/user/profile-details) for getting the refresh required date and days remaining.
## Additional LinkedIn Information
* Please see here for details on
[posting to LinkedIn](/docs/apis/post/social-networks/linkedin),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/linkedin)
# Connect Your Social Media Accounts
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/overview
Learn how to link Facebook, Instagram, X, LinkedIn, TikTok, and more to the Ayrshare Dashboard so you can start scheduling and publishing posts in minutes.
## Linking and Posting via the Web Dashboard
How to link and post to your social media accounts via the Ayrshare Dashboard.
[Linking and Posting via the Ayrshare Dashboard](https://www.youtube.com/embed/Aav2Njv-AkY)
### How to Link a Social Media Network
Connecting your social media accounts is easy with a few clicks.
On the Social Account linkage page, or the user profile's [social account linking page](/docs/multiple-users/user-integration#user-experience) used for managing multiple users, just click or tap the social network you want to connect. You will then be prompted to authorize access to Ayrshare via a pop-up or page redirect.

> [!NOTE]
>
> Please grant all permissions Ayrshare requests during authorization. Removing permissions may
> cause unintended issues at the social networks or Ayrshare's APIs. Also allow pop-ups in your
> browser.
Once the social network has been authorized, you'll see your account logo and the green "Linked" pill will appear, indicating your link is active.
### How to Un-Link a Social Network
Just tap on the green "Linked" pill and you will be prompted to unlink the account.
Posts that have already been sent to the network will be unaffected. However, future scheduled posts and new posts will not be sent until the link to the network has been re-established.
### Additional Help
#### Choose a Page or Brand
A few of the social networks will ask you to choose an account or brand. For example, if you
have multiple Facebook Pages, you will be given a choice of which Page to link.
### Additional Linking Guides
> [card] [Bluesky Linking](/docs/dashboard/connect-social-accounts/bluesky)
> [card] [Facebook Linking](/docs/dashboard/connect-social-accounts/facebook)
> [card] [Instagram Linking](/docs/dashboard/connect-social-accounts/instagram)
> [card] [Google Business Linking](/docs/dashboard/connect-social-accounts/google-business)
> [card] [Linkedin Linking](/docs/dashboard/connect-social-accounts/linkedin)
> [card] [Pinterest Linking](/docs/dashboard/connect-social-accounts/pinterest)
> [card] [Reddit Linking](/docs/dashboard/connect-social-accounts/reddit)
> [card] [Telegram Linking](/docs/dashboard/connect-social-accounts/telegram)
> [card] [Threads Linking](/docs/dashboard/connect-social-accounts/threads)
> [card] [Snapchat Linking](/docs/dashboard/connect-social-accounts/snapchat)
> [card] [TikTok Linking](/docs/dashboard/connect-social-accounts/tiktok)
> [card] [YouTube Linking](/docs/dashboard/connect-social-accounts/youtube)
> [card] [X/Twitter Linking (retiring March 31, 2026)](/docs/dashboard/connect-social-accounts/x-twitter)
> [card] [Set Up Your Own X API Key](/docs/dashboard/connect-social-accounts/x-twitter-byo-keys)
# Pinterest Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/pinterest
Link Pinterest with Ayrshare from the Social Accounts page. Set up at least one Pinterest board first, then authorize the app and choose a board to link.
## How to Link Pinterest with Ayrshare
### Pinterest Board Setup
Before linking your Pinterest account, you must have at least one Pinterest
board set up. To do so, navigate to your Pinterest profile page and click on the
**+**(plus) symbol. Follow the instructions to create a new board.

### Linking Pinterest
**1. Click to Link Pinterest**
Click the Pinterest icon on the Social Accounts page.

**2. First Time Pinterest Login**
If you are not logged in yet, you'll navigate to the Pinterest login page.
Enter your credentials accordingly.
If you are already logged in, please proceed to the next step.

**3. Authorize App**
If already logged in, click **Give access** on the Authorize app page.

**4. Choose a Pinterest Board**
Once back at the Ayrshare dashboard, choose the Pinterest board to link.

**5. Confirmation**
Your Social Accounts page will now be updated with your Pinterest account.

## Additional Pinterest Information
* Please see here for details on
[posting to Pinterest](/docs/apis/post/social-networks/pinterest),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/pinterest)
# Reddit Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/reddit
Link your Reddit account with Ayrshare by clicking the Reddit icon on the Social Accounts page, logging in to Reddit, and allowing the permissions.
## How to Link Reddit with Ayrshare
You can link Reddit account with Ayrshare.
When authorizing Reddit with Ayrshare, you will be redirected to Reddit to login and authorize Ayrshare to access your Redditaccount.
**1. Click to Link Reddit**
Click the Reddit icon on the Social Accounts page.

**2. FIrst Time Reddit Login**
If you are not logged in yet, enter your credentials to login to Reddit and
click **Log In**.
If you are already logged in, please proceed to the next step.

**3. Authorize Access**
Allow permissions for Ayrshare to connect to your Reddit account by clicking **Allow**.

**4. Confirmation**
Your Social Accounts page will now be updated with your Reddit account.

## Additional Reddit Information
* Please see here for details on
[posting to Reddit](/docs/apis/post/social-networks/reddit),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/reddit)
# Snapchat Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/snapchat
Link Snapchat with Ayrshare by setting up a Public Profile, switching it to a Professional Profile, and authorizing the app from the Social Accounts page.
## How to Link Snapchat with Ayrshare
### Snapchat Public Profile Setup
Before linking your Snapchat account, you must have a Snapchat Public Profile set up.
Non-public profiles can not be linked to Ayrshare.
Additionally, the public profile must be switched to a Professional Profile.
Without a professional profile, certain features such as posting stories is not
permitted.
If you don't have public profile yet, please follow the steps below.
If you have a public profile already, please proceed to step 3.
If your public profile is already a professional profile, please proceed to [Linking Snapchat](/docs/dashboard/connect-social-accounts/snapchat#linking-snapchat).
**1. Open Snapchat and click on top left corner**
Open Snapchat and tap on your icon in the top left corner.

**2. Public Profile**
Click on the **Public Profile** button and follow the instructions to set up your public profile.

**3. Professional Profile Settings**
Navigate to **Switch to a Professional Profile** option in the Profile Settings.
Follow the instructions, select a category, and click **Confirm** to switch
to a professional profile.

### Linking Snapchat
**1. Click to Link Snapchat**
Click the Snapchat icon on the Social Accounts page.

**2. First Time Snapchat Login**
If you are not logged in yet, you'll navigate to the Snapchat login page. Enter your credentials
accordingly. If you are already logged in, please proceed to the next step.

**3. Authorize App**
Review the permissions and click **Continue** on the authorization page.

**4. Confirmation**
Your Social Accounts page will now be updated with your Snapchat account.

## Additional Snapchat Information
* Please see here for details on
[posting to Snapchat](/docs/apis/post/social-networks/snapchat),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history](/docs/apis/history/overview).
* For more information on recommended image and video sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/snapchat)
# Link a Telegram group or channel to Ayrshare
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/telegram
Connect Telegram to Ayrshare by adding @AyrshareBot as an administrator so you can publish and delete posts in your group or channel.
Telegram uses bots to allow posting to your group or channel.
You will need to give admin access to the Ayrshare Telegram Bot via the Telegram mobile app to enable publishing and deleting.
> [!WARNING]
>
> **Add the correct bot: `@AyrshareBot`** (its display name shows as "Ayrshare", with a red logo like the one below).
>
> 
>
> Searching Telegram for "Ayrshare" returns several bots with similar names or logos that are
> **not** ours — for example the usernames `@AyrShareSocialMediaBot`, `@ayrshare_bot`,
> `@kylo_post_bot`, and `@AyrshareTestbot`. Only **`@AyrshareBot`** works with Ayrshare. If you
> add any other bot, your linking code will never be received and nothing will happen. Before
> adding it, open the bot and confirm its username is exactly **`@AyrshareBot`**.
> [!NOTE]
>
> You must have administrative access to the channel or group to add a bot. Telegram channel posts
> show the channel name and logo as the author. Telegram group posts show the Ayrshare name and logo
> as the author.
> [!NOTE]
>
> **The bot must be an Administrator — not just a member.** `@AyrshareBot` has Telegram Privacy
> Mode enabled, so if it is added only as a member it never receives your `Code:` message and
> linking silently fails with no error. Promote it to Administrator in the same channel or group
> where you paste the code.
## How to Link Telegram with Ayrshare
1. In the Telegram mobile app, go to the channel or group where you want to publish. Tap the three horizontal dots in the upper right corner and go to **Info**.

2. Add Ayrshare as an Admin:
If a *Channel*, select **Administrators** and **Add Admin**.
If a *Group*, add Ayrshare as a member by clicking "+ Add", and search for **Ayrshare** to
add as a member. Next, select **Edit** in the upper right corner, click **Administrators,**
and **Add Admin**.
3. Search for **Ayrshare** and select the official bot **`@AyrshareBot`** (display name "Ayrshare"). Do not select any of the similarly named look-alike bots — see the warning above.

4. You will be asked to select permissions for the bot. Select all the permissions except the last two so the bot can post and delete on your behalf.

5. Tap **Done** when complete.
6. In your Ayrshare Dashboard under Social Accounts you will be given a Telegram access code when you click on the Telegram icon.
7. Copy the code including the "Code:" part.

8. In your Telegram group or channel at the chat prompt, paste this code and press enter/tap the send button. If successful, you will get a success message back in the channel or group.
> [!NOTE]
>
> It could take up to **5 minutes** for Telegram to send you the success message after you enter the
> code message, so please be patient.

The Telegram integration is complete. You can now publish via the API or Web App.
If you need help or have questions, please [chat with us](javascript:void\(0\)) or [contact us](mailto:support@ayrshare.com).
## Additional Telegram Information
Please see here for details on [posting to Telegram](/docs/apis/post/social-networks/telegram), [getting analytics](/docs/apis/analytics/overview), [managing comments](/docs/apis/comments/overview), or [retrieving history.](/docs/apis/history/overview)
# Threads Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/threads
Link Threads with Ayrshare from the Social Accounts page. Sign in with an Instagram account, grant the requested permissions, or relink your account.
## How to Link Threads with Ayrshare
Authorizing Threads with Ayrshare is simple. During the authentication process,
you will be required to sign in with an Instagram account in order to leverage
Threads's full capabilities. Please see the below steps for more information.
## Authenticating Threads
**1. Click to Link Threads**
On the Social Accounts page, click the Threads icon.

**2. First Time Threads Linking**
The first time you link Threads, you will be asked to authenticate with your
Instagram account. An Instagram account is required in order to post to
Threads.
If you have previously connected Ayrshare with Threads and don't see this
image, skip to step 4.

**3. Threads Permissions**
After login, you'll be required to grant Ayrshare access to your Threads
account. Please provide all requested permissions in order to continue. (If
required permissions are not granted, you can re-enable them on the
[Website Permissions Page](https://www.threads.com/settings/website_permissions)
in your Threads settings)

**4. Relink Threads**
If you have previously connected Ayrshare with Threads, please click the
Continue button to relink your Threads account.

**5. Confirmation**
Your Social Accounts page will now be updated with your Threads account.

You can publish Threads posts, get Threads analytics, and manage Threads
comments.
## Additional Threads Information
* Please see here for details on
[posting to Threads](/docs/apis/post/social-networks/threads),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/threads)
# TikTok Linking
Source: https://www.ayrshare.com/docs/dashboard/connect-social-accounts/tiktok
Link a TikTok personal or business account to Ayrshare by authorizing access on TikTok, with notes on the 365 day link expiration and troubleshooting.
## How to Link TikTok with Ayrshare
You can link either your TikTok personal or business account with Ayrshare.
When authorizing TikTok with Ayrshare, you will be redirected to TikTok to login and authorize Ayrshare to access your TikTok account.
**1. Click to Link TikTok**
Click the TikTok icon on the Social Accounts page.

**2. First Time TikTok Login**
If you're not already logged into TikTok, you'll see the TikTok login page.
Enter your credentials to log in to your TikTok account.
If you are already logged in, please proceed to the next step.

**3. Authorize Access**
Click **Continue** to allow Ayrshare to connect to your TikTok account.

**4. Confirmation**
Your Social Accounts page will now be updated with your TikTok account.

## TikTok Link Expiration
TikTok must be re-linked every 365 days.
Please see the [user endpoint](/docs/apis/user/profile-details) for getting the refresh required date and days remaining.
## Troubleshooting TikTok
If you're having issues with Tiktok, please see the [troubleshooting guide](/docs/help-center/technical-support/tiktok-account-restricted).
## Additional TikTok Information
* Please see here for details on
[posting to TikTok](/docs/apis/post/social-networks/tiktok),
[getting analytics](/docs/apis/analytics/overview),
[managing comments](/docs/apis/comments/overview), or
[retrieving history.](/docs/apis/history/overview)
* For more information on recommended image sizes, please see:
> [card] [Image and Video Guidelines](/docs/media-guidelines/tiktok)
# Ayrshare Dashboard Overview: Manage Social Media Posting
Source: https://www.ayrshare.com/docs/dashboard/overview
Explore the Ayrshare Dashboard to schedule posts, manage connected social accounts, track analytics, and control your social media API from a single place.
The Ayrshare API dashboard provides a straightforward interface for managing your social media integrations, user profiles, and API access.

The dashboard supports connection to major social platforms including:
Bluesky
Facebook
Google Business Profile
Instagram
LinkedIn
Pinterest
Reddit
Snapchat
Telegram
Threads
TikTok
WhatsApp
X/Twitter
YouTube
As a management hub, the dashboard enables essential administrative functions:
User profile management, including adding and removing users, switching between profiles, and
viewing user details
Business account settings configuration, such as setting your own logo, customizing text, and
admin settings
Connect your social media accounts
Account data access (billing and add-ons)
API and Profile key management
Webhook set up and log monitoring
RSS feed configuration
For direct social media management, users can manually publish posts to any connected platform except WhatsApp, which is messaging-only, and track their social media activities.
The dashboard maintains a comprehensive history of all posts, including the JSON request and response for each interaction, making it invaluable to developers for testing and troubleshooting purposes.
Additional features include:
Post analytics data
Comment viewing and creation
Direct message composition and management
It's important to note that the dashboard represents only a subset of Ayrshare's full [API capabilities](/docs/apis/overview).
While it provides essential management and monitoring tools, the real power of Ayrshare lies in its comprehensive API.
> [!NOTE]
>
> The Ayrshare Dashboard is for your internal use only. You should [never give your users access to the dashboard](/docs/help-center/product/can-i-give-my-users-the-dashboard) as it will give them the ability to make changes to your account.
# API Integration for Business
Source: https://www.ayrshare.com/docs/multiple-users/api-integration-business
Allow Your Users to Connect Their Social Media Accounts
An important part of the Business Plan or [Launch Plan](/docs/multiple-users/business-launch-overview) integration consists of creating new profile accounts in Ayrshare for your users or clients and allowing your users to link their social networks with the [social linking page](/docs/multiple-users/user-integration).
Each of your users is set up as an Ayrshare user profile. A user profile gets one connection to each social network.
> [!NOTE]
>
> **This is the standard way to onboard customers.** Your users connect their own
> social accounts through the white-labeled connect page described below —
> they never log in to Ayrshare and never touch your dashboard. The
> [dashboard](/docs/multiple-users/manage-user-profiles#using-the-dashboard) is reserved for your
> internal team (creating and managing profiles, testing, and posting on a
> client's behalf).
## Integration Workflow
To integrate Ayrshare into your application, you'll need to make two API calls:
1. Call the [Create Profiles endpoint](/docs/apis/profiles/create-profile) to create a new Ayrshare profile for your user.
2. Call [Create a Link Session](/docs/apis/profiles/create-link-session) to get a linking URL.
The linking URL signs your user in to the social linking page, so they reach it without an Ayrshare account of their own.
You'll display this page in a new tab, window, or view controller to let users connect their social media accounts.

After your user links their social media accounts, you will be able to publish posts, get analytics, and manage their accounts via the API.
> [!NOTE]
>
> For security reasons, social media platforms require users to authenticate through their browser,
> which cannot be done through backend server calls. This ensures your users can safely connect
> their social media accounts by logging in directly with each platform's official login page.
> Social networks restrict authentication to happen only through their authorized partner domains,
> such as Ayrshare. They also block iFrames, which means you cannot embed or display the Ayrshare
> linking page within an iFrame on your site.
## Create an Ayrshare User Profile
### Create a Profile with the API
When a new user registers with your system or when your client clicks the social network link in your app, create a new Ayrshare profile account by calling the [/profiles/create-profile](/docs/apis/profiles/create-profile) RESTful endpoint, or using the [NPM](/docs/packages-guides/nodejs) or [PyPi](/docs/packages-guides/python) packages.
> [card] [Profiles](/docs/apis/profiles/overview)
Returned from this call will be your user's *PROFILE KEY*. This key will be used to post on your user's behalf and to manage their account. You should store it in a secure location.
### Create a Profile with the Dashboard (Internal Team)
Your internal team can also create User Profiles directly from the **Developer Dashboard** — useful for testing, onboarding a profile manually, or one-off administration. This is a team tool, not a customer-facing flow: your customers are never given dashboard access and are always onboarded through the [connect page](#single-sign-on-with-jwt-authentication). For production onboarding, create profiles programmatically with the [API](/docs/apis/profiles/create-profile) so each new user is provisioned automatically.
> [card] [Manage User Profiles in Dashboard](/docs/multiple-users/manage-user-profiles#using-the-dashboard)
### Integration Package
During your onboarding as a Business Plan or Launch Plan client, you received a personalized integration package with important set up and domain information, example endpoint calls, and more. Please check with your primary account holder for this information.
You may also retrieve or reset the integration package in the dashboard in the API page. Be sure to first switch to your Primary Profile.
If your integration signs its own tokens, don't reset the package. A reset retires your current domain and its keys, so links you've already handed out stop working and you can't sign new ones. See [Self-Signed Tokens (Legacy)](/docs/apis/profiles/social-linking-overview#self-signed-tokens).

## Single Sign-On to the Social Linking Page
Ayrshare signs your user in to the social linking page for you. You create a **link session** and open the URL it returns; there is nothing for your app to construct and nothing to sign.
### Create a Linking URL
Call [Create a Link Session](/docs/apis/profiles/create-link-session) with the user's `Profile-Key` in the header. It returns a `url` carrying a short-lived, opaque session token:
```javascript Linking URL theme={"system"}
https://profile.ayrshare.com?session=ayr_ls_kJ8mQ2vX9pLnR4tYwZ6aBcD1eFgH3iJkLmN0oPqRsTu&domain=YOUR_DOMAIN
```
Send it to your user exactly as returned and open it in a new tab or window, so they can [link their social networks](/docs/multiple-users/user-integration). The token exists only inside the URL — never in a body field you have to store — and the URL signs your user into their profile, so treat it like a password and send each one to a single user.
> [!NOTE]
>
> The URL is valid for **5 minutes** by default. Use `expiresIn` to set a different window, up to
> 2880 minutes (48 hours), which requires the [Max Pack](/docs/additional/maxpack).
You can also [check whether a link has been opened](/docs/apis/profiles/get-link-session) and [revoke it](/docs/apis/profiles/revoke-link-session) before it expires.
> [!NOTE]
>
> [Generate a Linking URL](/docs/apis/profiles/generate-jwt) (`generateJWT`) does the same job and keeps
> working unchanged, so links you have already handed out are unaffected. It is deprecated for new
> integrations and has no removal date. Its `privateKey`, `base64` and `verify` parameters are still
> accepted and are ignored — nothing is signed on your side, so you don't need the private key we
> issued you to create links. If your integration signs its own tokens, keep it. See
> [Self-Signed Tokens (Legacy)](/docs/apis/profiles/social-linking-overview#self-signed-tokens).
> [!NOTE]
>
> The linking URL is the recommended onboarding path for every customer. Your users authenticate
> directly with each social network on their own — you never collect or store their social
> credentials. If your team needs to link an account manually during testing, you can do so from
> the dashboard, but this is an internal convenience and not how customers should be onboarded.
> [!NOTE]
>
> **Three ways to connect an account, and this page describes one of them.** The hosted linking page
> above shows your user every network you permit, on a page we host and you brand. If you would
> rather the connecting happen inside your own dashboard, the
> [Ayrshare Connect widget](/docs/multiple-users/connect-widget) embeds our buttons directly in your
> layout, and [Direct Mode](/docs/multiple-users/connect-direct-mode) opens one network at a time from a
> popup you manage yourself. Both pages open with the same comparison.
### White-Label the Connect Accounts Page
Because the connect page opened by the linking URL is the only Ayrshare interface your users ever see, you should brand it as your own. Add your logo and custom headers, choose which social networks appear, set the page language, and use your own help links — and with the [Max Pack](/docs/additional/maxpack), apply a full custom CSS file, favicon, page title, and footer. These options are configured once by your team from the Primary Profile and apply to every linking URL you create.
> [card] [White-Label the Connect Accounts Page](/docs/multiple-users/manage-user-profiles#white-label-the-connect-accounts-page)
>
> See every branding and customization option for the connect page.
### Automatic Logout of a Profile Session
> [!WARNING]
>
> **How to Automatically Logout of the Active Profile Session**
>
> If a profile is already logged in, sending a different profile's linking URL will not switch profiles. This is done to make the experience faster for already logged-in users. When testing, log out of the current profile before making an SSO call with a different profile. Signed-in profiles remain signed in until explicitly logged out.
>
> However, if you have a business need, such as your users having multiple profiles they often access, or you want to test, include the URL parameter in the SSO URL:
>
> `logout=true`
>
> For example:
>
> `https://profile.ayrshare.com?session=[session token]&domain=[domain id]&logout=true`
>
> This forces a logout and then logs in the new profile.
>
> You can also set the `logout` body parameter on [Create a Link Session](/docs/apis/profiles/create-link-session) to have it added for you.
>
> **Warning**: Using automatic logout causes a performance delay for your user, so unless you need it, we suggest not forcing a logout and not using it in production.
## Opening & Closing the Social Linking URL
The Social Accounts page has a "Close" button that closes the window and returns your user to your site.
### Opening the Social Linking URL
When you open the social linking URL, we recommend using JavaScript's `window.open()` function instead of an anchor href.
The "Close" button uses JavaScript to close the window, and most browsers only allow windows opened via a script to be closed.
You can use the following sample code on your website to launch the social linkage window.
```html Window Open theme={"system"}
Link Social Networks
```
or
```html Window Open Function theme={"system"}
```
You may control the "Close" button to either redirect the social linking tab to a page of your choosing or redirect the origin opener tab/window and close the social linking tab.
This can be done using the redirect parameter and adding `origin=true` to the query parameters of the redirect URL.
See [Create a Link Session](/docs/apis/profiles/create-link-session) for more details.
### Redirecting When Closing
Additionally, if you want to have the `Close` button redirect back to your page (often useful on mobile), pass in the `redirect` GET parameter as a *URL encoded* string starting with https.
See the [Create a Link Session body parameters](/docs/apis/profiles/create-link-session#body-parameters) for more details.
```javascript Linking URL theme={"system"}
https://profile.ayrshare.com?session=[session token]&domain=[domain id]&redirect=https%3A%2F%2Fmywebsite.com
```
The redirect will be saved for that profile and used in subsequent SSO calls even if the `redirect` parameter is not passed.
To reset the `Close` button back to close, pass the parameter `redirect=null`.
## More Information
### User Linking or Unlinking Notifications
When a user links or unlinks a social network, you can receive a notification via webhook. Please see the [Social Action Webhooks](/docs/apis/webhooks/actions#social-action) page for more information.
You may also use the [user endpoint](/docs/apis/user/profile-details) to get the connected social networks for a user profile.
### Multiple Sets of Social Accounts
#### Overview
Each User Profile has a set of social media accounts and can make one connection to each social network.
If your users or clients have multiple sets of social media accounts, such as two Facebook Pages, two Instagram accounts, and two YouTube channels, you can manage these multiple sets of social media accounts by following these steps:
1. **Create multiple User Profiles** - [Create separate User Profiles](/docs/apis/profiles/create-profile) within the Ayrshare system for each set of social accounts your user or client needs to manage.
2. **Store unique Profile Keys** - Each User Profile will have its own unique Profile Key, returned from the create User Profile endpoint.
3. **Label your profiles** - [Title](/docs/apis/profiles/create-profile#param-title) or [tag](/docs/apis/profiles/create-profile#param-tags) each User Profile to clearly identify which social media accounts they're associated with (e.g., "Facebook Page A" and "Facebook Page B").
4. **Map in your database** - In your own system, associate all relevant Profile Keys with the respective user or client.
5. **Use appropriate Profile Key** - When posting content or retrieving analytics for a specific social account, use the Profile Key associated with that account. For example, to post to Facebook Page A, use the Profile Key of the User Profile connected to Facebook Page A.
#### Example
Let's consider a practical scenario for multiple client accounts:
**Client:** Joe's Restaurant Chain with two locations
Joe needs separate social media accounts for each restaurant location:
**Restaurant Location 1:**
Facebook Page: "Joe's Downtown Bistro"
Instagram: @joesdowntownbistro
LinkedIn: Joe's Downtown Bistro business page
**Restaurant Location 2:**
Facebook Page: "Joe's Seaside Grill"
Instagram: @joesseasidegrill
LinkedIn: Joe's Seaside Grill business page
**Implementation solution:**
1. Create two separate User Profiles in Ayrshare:
User Profile 1: Titled "Joe's Downtown Bistro Accounts"
User Profile 2: Titled "Joe's Seaside Grill Accounts"
2. Connect the appropriate social accounts to each profile:
Profile 1: Links the Downtown Bistro Facebook, Instagram, and LinkedIn
Profile 2: Links the Seaside Grill Facebook, Instagram, and LinkedIn
3. Store both Profile Keys in your database, associating them with Joe's account:
```
Client: Joe's Restaurant Chain
- Profile Key 1: abc123... (Downtown location)
- Profile Key 2: xyz789... (Seaside location)
```
4. When posting a special menu update for the Downtown location, use Profile Key 1 in the API calls. When posting about a seafood promotion at the Seaside location, use Profile Key 2.
By creating multiple User Profiles, [titling](/docs/apis/profiles/create-profile#param-title) or [tagging](/docs/apis/profiles/create-profile#param-tags) them appropriately, and associating them to a single user or client in your system, you can manage their multiple social media accounts across the same platforms using Ayrshare.
This approach allows for targeted posting and analytics retrieval for each specific account.
### Next Steps
Please see here for more details of the user experience:
> [card] [User Integration](/docs/multiple-users/user-integration)
Once your user sets up their social media links, you will be able to begin posting on their behalf using
the /post and /profile endpoints.
> [card] [Post API](/docs/apis/post/overview)
> [card] [Profiles API](/docs/apis/profiles/overview)
# Launch Plan: entry-level multi-user Business plan
Source: https://www.ayrshare.com/docs/multiple-users/business-launch-overview
The Ayrshare Launch Plan is the entry-level Business tier for agencies and platforms managing multi-user social media posting, analytics, and DMs.
The **Launch Plan** is the entry-level tier of the [Ayrshare Business Plan](/docs/multiple-users/business-plan-overview). It's designed for companies, agencies, and platforms that are just starting to manage social media on behalf of their users, clients, or brands and don't yet need the scale of the full Business plan.
The Launch Plan includes the same multi-user APIs as the full Business plan (posting, analytics, comments, DMs, and ads on behalf of your users) at a lower starting price, with a smaller profile cap to match the needs of early-stage teams. Post quotas, API rate limits, and support match the full Business plan.
To get started, see current pricing and start your subscription on the [Ayrshare pricing page](https://www.ayrshare.com/pricing/).
## Who Launch Is For
Early-stage SaaS platforms adding social posting for their first users.
Agencies onboarding a small set of clients before scaling further.
Teams running a pilot or proof-of-concept that needs production-grade APIs without committing to the full Business plan.
Anyone who needs Business-tier features (multi-user JWT linking, per-user analytics, comments, DMs, ads) for **up to 10 user profiles**.
> [!NOTE]
>
> The Launch Plan is capped at 10 [User Profiles](/docs/multiple-users/manage-user-profiles)
> (sub-profiles, separate from your primary account). Once you reach the cap,
> the API returns an error with upgrade guidance, and the dashboard shows the
> same message as a toast. To add more, upgrade to the full
> [Business Plan](/docs/multiple-users/business-plan-overview).
## What's Included
The Launch Plan gives you the full Business API surface for managing multiple users, capped at 10 [User Profiles](/docs/multiple-users/manage-user-profiles):
A single API call to post to your users' [social media networks](/docs/apis/post/overview): Bluesky,
Facebook, Google Business Profile, Instagram, LinkedIn, Pinterest, Reddit, Snapchat,
Telegram, Threads, TikTok, X, and YouTube.
[Schedule posts](/docs/apis/post/overview#schedule-posts) and [auto-schedule](/docs/apis/auto-schedule/overview) at pre-determined times.
[Per-user analytics](/docs/apis/analytics/overview) on posts, social profiles, and links.
[Comment management](/docs/apis/comments/overview) and [direct messages](/docs/apis/messages/overview) on supported platforms.
[Facebook Ads](/docs/apis/ads/overview) created from existing posts.
[Hashtag automation](/docs/apis/hashtags/overview), [RSS feeds](/docs/apis/feeds/overview), [post validation](/docs/apis/validate/overview), and [post history](/docs/apis/history/overview).
Secure social authentication via [JWT](/docs/multiple-users/api-integration-business): your users connect their own accounts and you never handle their credentials.
## How the Launch Plan Compares to the Full Business Plan
The Launch Plan and full Business Plan share the same API tier and feature set for posting, analytics, comments, DMs, and ads. The differences are about scale and add-ons:
| Capability | Launch | Business |
| - | - | - |
| User Profile cap | Up to 10 | 30+ (scales as needed) |
| Core posting, analytics, comments, DMs APIs | Included | Included |
| Facebook Ads add-on | Available | Available |
| [Max Pack](/docs/additional/maxpack) add-on | Available at a reduced Launch price | Available |
| Free trial | 28 days | — |
Post quotas, API rate limits, and support are the same on the Launch Plan and the full Business Plan.
For exact pricing and the most up-to-date details, see the [Business Plan page](https://www.ayrshare.com/business-plan-for-multiple-users/).
## 28-Day Free Trial
The Launch Plan includes a **28-day free trial** so you can integrate the multi-user APIs and validate the flow with real users before committing. The trial gives you the same APIs and profile capacity as a paid Launch Plan subscription.
## Upgrading to the Full Business Plan
Because the Launch Plan is capped at 10 User Profiles, you'll need to upgrade to the full [Business Plan](/docs/multiple-users/business-plan-overview) once you're ready to onboard your 11th User Profile. You can upgrade at any time from your [account dashboard](https://app.ayrshare.com). Your existing User Profiles, JWT integration, and API keys carry over. No re-integration required.
## Moving to Launch from the Premium Plan
If you're on the Premium Plan and want to move to the Launch Plan, this is a change between plan families (single-user to multi-user). A self-serve option for this isn't available in the dashboard yet — for now, email [support@ayrshare.com](mailto:support@ayrshare.com) and we'll migrate your account to the Launch Plan for you — your connected social accounts, API keys, and settings carry over.
> [!NOTE]
>
> Self-serve dashboard upgrades currently apply only to the monthly Launch Plan
> moving to the full Business Plan. Moving from the Premium Plan to the Launch
> Plan or Business Plan is handled by support for now, and moving from an annual
> Launch Plan to the Business Plan is also support-only. (New customers on the
> free Basic Plan can self-serve any plan from the [pricing page](https://www.ayrshare.com/pricing/).)
## Get Started
> [card] [Set Up Multi-User Integration](/docs/multiple-users/api-integration-business)
>
> Generate JWTs and connect your users' social accounts via the API.
> [card] [Manage User Profiles](/docs/multiple-users/manage-user-profiles)
>
> Create, update, and manage the User Profiles included in your plan.
> [card] [Full Business Plan Overview](/docs/multiple-users/business-plan-overview)
>
> Compare the Launch Plan against the full Business Plan and Max Pack add-ons.
# Business Plan Overview
Source: https://www.ayrshare.com/docs/multiple-users/business-plan-overview
Ayrshare Business Plan for managing multiple users, clients, or brands
The [Business Plan](https://www.ayrshare.com/business-plan-for-multiple-users/) enables organizations such as companies, agencies, and platforms to manage social media activities including posting, scheduling, analytics, and comment management across multiple users, clients, or brands from a single API integration.
Ayrshare is API-first. You build your own front end and call the API to post, fetch analytics, and manage comments on behalf of your users. Your customers connect their own social accounts through a **white-labeled connect page** that you open with a [JWT URL](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication) — they never log in to Ayrshare and never see your dashboard.
> [!WARNING]
>
> **The dashboard is for your internal team only.** On the Launch, Business, and
> Enterprise plans, the [Ayrshare dashboard](/docs/multiple-users/manage-user-profiles#using-the-dashboard)
> is where *your team* creates and manages [User Profiles](/docs/multiple-users/manage-user-profiles),
> tests integrations, and posts on a client's behalf. Your customers should
> **always** be onboarded through the [JWT URL connect page](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication) —
> never give clients dashboard access, as it exposes your account, billing, and
> every other profile. (On the single-user Premium plan, you use the dashboard
> directly yourself.)
> [!NOTE]
>
> Just getting started with multi-user social? The
> [Launch Plan](/docs/multiple-users/business-launch-overview) is the
> entry-level Business tier with the same API surface, capped at 10 User Profiles,
> and a 28-day free trial.
Get more details about the Business Plan by [contacting us](https://www.ayrshare.com/business-plan-for-multiple-users/#contactbusiness).

## Post, Get Analytics, Manage Comments and DMs, and Create Facebook Ads on Behalf of Your Users
A single API call to post to your users' [social media networks](/docs/apis/post/overview): Bluesky,
Facebook, Google Business Profile (Google My Business), Instagram, LinkedIn, Pinterest,
Reddit, Snapchat, Telegram, Threads, TikTok, X, and YouTube.
[Send scheduled posts](/docs/apis/post/overview#schedule-posts) of text, images, and videos or
[create an auto schedule](/docs/apis/auto-schedule/overview) to post at pre-determined times.
Get [detailed analytics](/docs/apis/analytics/overview) on your user's posts, social profile, and
links. E.g. number of impressions, follower demographics, average view time of videos, and more.
[Add comments](/docs/apis/comments/overview) to a user's post or get all comments made by other
users.
[Manage direct messages](/docs/apis/messages/overview) across supported platforms.
[Create Facebook ads](/docs/apis/ads/overview) from existing posts.
Automatically [add hashtags](/docs/apis/hashtags/overview) to posts based on the content, and
automatically post your users' [RSS feeds](/docs/apis/feeds/overview).
[Validate posts](/docs/apis/validate/overview) before sending to the social networks so your users'
social accounts are always protected and don't get in trouble with the social networks.
Retrieve a user's [post history](/docs/apis/history/overview), [analytics](/docs/apis/analytics/overview),
and [comments](/docs/apis/comments/overview), even on posts that originated outside of Ayrshare.
## Seamlessly Integrate with Your Product or Platform
Simple and transparent integration for your users, clients, and brand.
Make RESTful API calls, or use the Node.js, Python, Bubble.io, or Airtable [packages and
integrations](/docs/packages-guides/overview).
Enterprise grade security and secure social authentication where you never ask for your users'
credentials.
Staging environment add-on available for your build workflow.
## White-Label the Connect Accounts Page
The connect accounts page is the only Ayrshare interface your customers ever see, so make it your own. Add your logo, set custom headers, choose which social networks appear, localize the language, and swap in your own help links — and with the [Max Pack](/docs/additional/maxpack), apply a full custom CSS file, favicon, page title, and footer. Done well, the connect page feels like a native part of your product, not a third-party tool.
> [card] [White-Label the Connect Accounts Page](/docs/multiple-users/manage-user-profiles#white-label-the-connect-accounts-page)
>
> See every branding and customization option available for the connect page.
## Additional Capabilities Available with Max Pack
The [Max Pack](/docs/additional/maxpack) provides additional endpoints and other useful utilities for building your app or platform.
# Build Your Own Linking Popup
Source: https://www.ayrshare.com/docs/multiple-users/connect-direct-mode
Direct mode — connect one network at a time from your own button, with a popup you open and watch yourself.
Direct mode connects **one social network at a time**, from a button in your own dashboard. You
create a link session for that network, open the URL it returns in a popup, and your page hears
what happened. Your user never sees a page listing every network, and never leaves your app for
longer than the network's own login takes.
## Which Surface You Want
Three shapes, and the first two are the same integration. This page is the one you build
yourself.
| Surface | Your user sees | White-labelling | Choose it when |
| - | - | - | - |
| **Widget — embedded frames** ([mount](/docs/multiple-users/connect-widget#mount-a-slot)) | our buttons inline in your own layout; no popup until the network's own | **Strongest.** Your page, your fonts and colours, and your user never leaves it | you have a dashboard with a row per network and want linking to happen in place. Max Pack. |
| **Widget — your own button** ([popup](/docs/multiple-users/connect-widget#your-own-button)) | your button, then one popup for the network | **Strong.** The popup is ours, but it is brief and inherits your appearance | you want your own button and styling, and no frames in your layout. Max Pack. |
| **Hosted linking page** ([how to](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | a page we host, carrying your logo, colours and custom CSS | **Weakest.** It is our page, and your user leaves yours to use it | you want one link to hand out, or you are onboarding by email. Nothing to build, no Max Pack. |
> [!NOTE]
>
> **Direct mode is the third row's popup without our script.** If your page can load the script, the
> [widget](/docs/multiple-users/connect-widget) does everything on this page for you — it opens and
> watches the popup itself, and it can embed frames as well. Direct mode is what you want when your
> page cannot load a third-party script, or the surface is a native app rather than a browser.

Your button, your page. The popup is the only thing of ours your user sees, and only for as long as the network needs.
## What You Build
Four steps. The first is on your server, the rest are in your page.
**1. Create a session for one network**
From your backend, call
[Create a Link Session](/docs/apis/profiles/create-link-session) with `mode: "connect"`, the
`network`, and the `origin` your page runs on.
```javascript Your backend theme={"system"}
const response = await fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.AYRSHARE_API_KEY}`,
"Profile-Key": profileKey,
},
body: JSON.stringify({
mode: "connect",
network: "reddit",
origin: "https://app.example.com",
}),
});
const { url } = await response.json();
```
You get back a `url` pointing at a single-network connect page, and no `token` — the token is
inside the URL. Treat the whole URL like a password: it signs your user into their User
Profile.
> [!WARNING]
>
> Create the session on your server, never in the browser. The call needs your API key.
**2. Open it in the click handler, synchronously**
The popup has to be opened by `window.open` **in the click handler itself**. A browser only
permits a popup while it is still processing your user's click, and that permission does not
survive an `await` — so fetching the URL first and opening it in the callback is reliably
popup-blocked.
Fetch the URL when you render the button, or when your user hovers it. By click time you
should already have it.
```javascript Your page theme={"system"}
// `url` was fetched earlier. Nothing async between the click and window.open.
button.addEventListener("click", () => {
const popup = window.open(url, "ayrshare-connect", "width=600,height=800");
if (!popup) {
showError("Allow popups for this site to connect an account.");
return;
}
listenForOutcome(popup, url);
});
```
**3. Listen for the outcome**
Because you passed `origin`, the popup posts an event to your page for each thing that
happens in it: `connect:success`, `connect:error`, `connect:cancelled`, and progress events
in between. Exactly one of those three arrives per connection.
[Link Completion Events](/docs/multiple-users/link-completion-events) has the full event table and a
copy-paste listener — `listenForOutcome` above is that snippet. Two parts of it are easy to
leave out and both cause real bugs:
* **Check `event.origin`** against the origin of the URL you opened. Any page can post a
message to your window, and the origin is the only part of a message that cannot be faked.
* **Poll `popup.closed`**, with a short grace window before you conclude anything. A popup
your user closed by hand sends nothing at all, and without the grace window a successful
connection can be reported as cancelled.
**4. Handle each ending**
| Ending | What it means | What to do |
| - | - | - |
| `connect:success` | the account is connected **and saved** | refresh that row. A [`GET /user`](/docs/apis/user/profile-details) straight after already shows it. |
| `connect:error` | the connection failed | show `message`. `code` is present when the failure has a catalogued code and absent when it does not. |
| `connect:cancelled` | your user backed out, or closed the window | leave the row as it was. `reason` is `popupClosed`, `scopesDenied` or `userCancelled`. |
| nothing arrives | the link was expired, revoked or unknown, so the page never learned where to send events | poll [Get a Link Session](/docs/apis/profiles/get-link-session), which reports those states authoritatively. |
> [!TIP]
>
> Add `&autoClose=false` to the URL while you are building. The popup then stays open after every
> outcome instead of closing itself, so you can read what it says.
## Per-Network Notes
Most networks are one popup and nothing else: your user clicks, authorizes at the network, and
the popup closes. These are the exceptions worth knowing before you build.
> [accordion] X requires your own API keys
>
> X in direct mode uses **your** X Developer App credentials, supplied when you create the
> session as the `X-Twitter-OAuth1-Api-Key` and `X-Twitter-OAuth1-Api-Secret` headers on
> [Create a Link Session](/docs/apis/profiles/create-link-session).
>
> A session created for `twitter` or `x` **without** those headers is refused when the popup
> opens: your user is told the connection is not available and is shown no form, and your page
> receives `connect:error` with a `message` and **no `code`**. That is deliberate. The missing
> credential is yours, not your user's, and end users must never be asked to type your API keys.
>
> Contrast Bluesky, where the app password is the end user's own credential — that one the
> connect page does collect, in a form inside the popup.
> [accordion] Facebook shows one button before Meta's login
>
> `network: "facebook"` shows a single button in the popup, and Meta's own login opens from
> that click — Meta requires its login to be started by a click inside the page that hosts its
> SDK. Your user clicks twice rather than once; nothing else differs.
>
> Instagram behaves the same way when it is linked **via a Facebook Page** — that is, when the
> session carries `instagramLinkMethod: "facebook"`, or when your account's
> [Instagram Login](/docs/multiple-users/manage-user-profiles#instagram-login) setting selects that
> flow. With direct Instagram Login there is no extra button.
> [accordion] Bluesky and Telegram show page content, not a redirect
>
> Neither sends your user to a network login. The popup renders content instead: a handle and
> app-password form for Bluesky, and a code to use for Telegram. The outcome events are the
> same either way.
>
> X does not belong in this group. With your keys on the session it completes without prompting
> your user for anything, and without them it is refused — see above.
> [accordion] Telegram finishes out of band
>
> Telegram shows a code rather than redirecting anywhere, and the connection completes when your
> user uses that code — after the popup is gone. There is no browser event to wait for, so poll
> [Get a Link Session](/docs/apis/profiles/get-link-session) and watch `completedNetworks`.
> [accordion] Facebook Groups cannot be connected this way
>
> Facebook Groups is not a link target, so `network: "fbg"` returns `code: 508` when you create
> the session.
>
> WhatsApp **is** available in direct mode. It opens Meta's Embedded Signup in the popup, and the
> outcome events are the same as any other network.
## Native Apps
A native app opens the same `url`, in the **system browser**, and finds out the result by polling
[Get a Link Session](/docs/apis/profiles/get-link-session). Set `origin` to your custom scheme
(`myapp://connected`) so the page has a way back to your app; a custom scheme cannot receive
events, because there is no browser window to post them to.
* **iOS** — `ASWebAuthenticationSession`, or `SFSafariViewController`.
* **Android** — Chrome Custom Tabs.
> [!WARNING]
>
> **Never open a linking URL in an embedded webview** (`WKWebView`, `UIWebView`, Android
> `WebView`). The social networks refuse to authenticate in one: Google rejects the sign-in with
> `disallowed_useragent`, and Meta blocks it outright. Your user sees the network's own error page,
> not ours, and nothing you can change on your side fixes it. The system browser components above
> exist for exactly this reason and keep the user inside your app.
## What Direct Mode Requires
The **[Max Pack](/docs/additional/maxpack)**. Creating a connect-mode session without it returns
`code: 504`, whatever else the request says.
An **`origin`** on every session. There is no allowlist and no registration step — you send it
per call. Omitting it returns `code: 505`; a value that is not an `https` origin, a custom
scheme, or `http://localhost` returns `code: 506`.
A **`network`** that your account has enabled. An unrecognized name returns `code: 508`; a
recognized one your account has not enabled returns `code: 509`, which you can fix on your
[Social Networks](/docs/multiple-users/manage-user-profiles#set-social-networks-access) page.
**Not** `allowedSocial`. It cannot be combined with `network` (`code: 507`) — a single-network
session is already its own allowlist.
Every one of these is in the
[Link Session Errors](/docs/errors/errors-ayrshare#link-session-errors) reference, with the message the
API returns.
## Next Steps
> [card] [Link Completion Events](/docs/multiple-users/link-completion-events)
>
> Every event the popup sends, and the listener to receive them.
## Related
> [card] [Create a Link Session](/docs/apis/profiles/create-link-session#connect-mode)
>
> The `mode`, `origin` and `network` parameters, and the response shapes.
> [card] [Get a Link Session](/docs/apis/profiles/get-link-session)
>
> Poll for completion when you cannot use a popup.
# Ayrshare Connect Widget
Source: https://www.ayrshare.com/docs/multiple-users/connect-widget
Let your users link social accounts from inside your own dashboard, with one script tag and our buttons embedded in your page.
The Ayrshare Connect widget puts our linking buttons **inside your own dashboard**. You load one
script, drop a slot wherever a network belongs in your layout, and we render a button there that
already shows whether the account is connected. Your user clicks it and links the account without
leaving your page — at most one popup, the network's own.
You write no popup handling, no OAuth callbacks, no session refresh, and no per-network logic. When
a network changes something on their end, the fix ships inside our frames the moment we deploy it;
you redeploy nothing.
## Which Surface You Want
| Surface | Your user sees | White-labelling | Choose it when |
| - | - | - | - |
| **Widget — embedded frames** ([`mount()`](#mount-a-slot)) | our buttons inline in your own layout; no popup until the network's own | **Strongest.** Your page, your fonts and colours, and your user never leaves it | you have a dashboard with a row per network and want linking to happen in place. Max Pack. |
| **Widget — your own button** ([`popup()`](#your-own-button)) | your button, then one popup for the network | **Strong.** The popup is ours, but it is brief and inherits your appearance | you want your own button and styling, and no frames in your layout. Max Pack. |
| **Hosted linking page** ([how to](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication)) | a page we host, carrying your logo, colours and custom CSS | **Weakest.** It is our page, and your user leaves yours to use it | you want one link to hand out, or you are onboarding by email. Nothing to build, no Max Pack. |

Embedded frames: our tiles rendered inside your own layout, one slot per network or one slot for several.

Your own button: you render the button, and one brief popup of ours handles the network.

Hosted linking page: a page we host, carrying your logo and colours, that your user leaves your app to use.
> [!NOTE]
>
> The two widget rows are **one integration**, not two. A single `init` gives you both: mount frames
> where you want our buttons, and call `popup()` from your own button everywhere else. They share
> one session and report on the same handlers.
>
> [Direct mode](/docs/multiple-users/connect-direct-mode) is the same popup **without** our script — for
> a page with a strict Content-Security-Policy, a server-rendered page, or a native app. There you
> open and watch the popup yourself.
## Add the Script
Pin a version and its hash, or track a channel without one. Never both — an `integrity` attribute
on a moving URL stops working at our next release, because the file it names has legitimately
changed.
```html Pinned version theme={"system"}
```
```html Tracking v1 theme={"system"}
```
| Path | Caching | `integrity` |
| - | - | - |
| `/ayrshare-connect//widget.js` | immutable, one year | **yes** — pin it |
| `/ayrshare-connect/v1/widget.js` | five minutes | no |
| `/ayrshare-connect/latest/widget.js` | five minutes | no |
**`v1` is the channel to recommend.** It picks up fixes but never crosses a breaking change.
`latest` crosses major versions by definition, so it will eventually hand your page a version whose
behavior you have not reviewed.
Every version's hash is published in
[`manifest.json`](https://app.ayrshare.com/ayrshare-connect/manifest.json), which also names what
each channel currently serves:
```json manifest.json theme={"system"}
{
"versions": {
"1.2.0": { "integrity": "sha384-qQ/of8CnYj26nF9RkQ6qk3D1vvzWesu7mpBK6MFK2D0EmmgRJHfbDmRowFBfltmz" }
},
"latest": "1.2.0",
"channels": { "v1": "1.2.0", "latest": "1.2.0" }
}
```
Each bundle also opens with a comment naming its own version, which is the fastest way to tell us
what a page is actually running:
```js theme={"system"}
/*! ayrshare-connect 1.2.0 */
```
### Content-Security-Policy
If your page sends a Content-Security-Policy, it needs **two** entries, both naming the host you
load the script from:
```
script-src https://app.ayrshare.com;
frame-src https://app.ayrshare.com;
```
That is the whole list. You need **no `connect-src` entry** for us — our frames reach our API from
inside themselves, not from your page — and **no entry for popups**, which are top-level windows
your policy does not govern. Both entries were measured: removing `script-src` blocks the script,
and removing `frame-src` blocks the frame.
## Start an Instance
`session` is the only required option. It is called **once per instance**, not once per mount, and
must return your backend's response to
[Create a Link Session](/docs/apis/profiles/create-link-session) with `mode: "connect"` —
`{ sessionId, token, expiresAt }` — exactly as it came back.
```javascript Your page theme={"system"}
const connect = AyrshareConnect.init({
session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
appearance: { "--ayr-connect-accent": "#0B7A6C" },
maxHeight: 800,
});
```
```javascript Your backend theme={"system"}
app.get("/my-api/ayrshare-session", async (req, res) => {
const response = await fetch("https://api.ayrshare.com/api/profiles/link-sessions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.AYRSHARE_API_KEY}`,
"Profile-Key": profileKeyFor(req.user),
},
body: JSON.stringify({ mode: "connect", origin: "https://app.example.com" }),
});
res.json(await response.json());
});
```
A widget session names **no** `network` — it authorizes every network your account permits, and
which ones appear is decided per mount, in your page. It does carry an `origin`, which is the one
thing that makes the frames render at all: a frame checks the page embedding it against that value
and refuses to render anywhere else.
> [!WARNING]
>
> Create the session on your server. The call needs your API key, and the token it returns signs
> your user into their User Profile — treat it like a password.
We call `session` again before the token expires, so the widget keeps working on a page left open
all day. A call that rejects, or returns no token, is retried twice more — after 0.5s, then 1s —
before we give up and emit `error`. So one refresh costs at most three calls to your endpoint.
| Option | Default | Does |
| - | - | - |
| `session` | — | **Required.** Returns a connect-mode link session. |
| `appearance` | our defaults | Design tokens, applied inside every frame. See [Appearance](#appearance). |
| `css` | none | A CSS string applied inside every frame. See [Custom CSS](#custom-css). |
| `maxHeight` | `600` | How tall a frame may grow before it scrolls internally instead. |
## Mount a Slot
One `mount` per slot. Ask for one network, several, or everything the session permits — the
granularity is yours, so a slot can be a single row in an existing table or one panel holding
everything.
```javascript theme={"system"}
connect.mount("#instagram-slot", { network: "instagram" });
connect.mount("#some-slot", { networks: ["facebook", "tiktok", "x"] });
connect.mount("#everything");
const row = connect.mount(document.querySelector("#tall"), { maxHeight: 1200 });
row.unmount();
```
`mount` takes a CSS selector or an element, and returns `{ unmount, element }`. It throws if the
target matches nothing — which is almost always a slot that does not exist yet, so mount after your
markup is in the document.
Each mount is one iframe. It reports its own height to us and we resize it to match, so your layout
reflows as our content changes; past `maxHeight` the frame scrolls internally instead of running off
your page. **The narrowest slot we support is 300px.**
Network keys are Ayrshare's own, and the alias spellings work too: `instagram` and `instagramapi`
both mean `instagramApi`, and `x` means `twitter`. A key that is not a network renders no tile.
## Your Own Button
A customer who would rather use their own button than one of our frames calls `popup` instead. It
runs the same flow, on the same session, and reports on the same handlers.
> [!WARNING]
>
> **Call it directly inside the click handler, with nothing awaited before it.** A browser only
> permits a popup while it is still processing your user's click, and that permission does not
> survive an `await`. Nothing needs awaiting anyway — the session was created at `init`.
```javascript theme={"system"}
linkedInButton.addEventListener("click", () => {
connect.popup({ network: "linkedin" });
});
```
`popup` returns `{ close(), network }`, and **always** returns a handle — including after a blocked
popup, where `close()` does nothing — so your code never has to null-check it before calling
`close()`.
Every network works here, including the ones a frame finishes inside its own panel: Facebook shows
its hand-off explainer in the popup, Bluesky and X show their credential form, and LinkedIn,
Pinterest, YouTube and Google Business go out to the network and come back.
It throws synchronously for the three things that are programming errors — no `network`, a
destroyed instance, or a session that has not resolved yet. A blocked popup is **not** one of them:
that emits `error` with `reason: "popupBlocked"`, because your user did nothing wrong.
At most one popup is open at a time. A second call closes the first and reports `cancelled` with
`reason: "superseded"` on it. A popup one of our **frames** opened is a different thing and is never
touched, so your button cannot cancel a flow running inside a mounted slot.
> [!NOTE]
>
> Whether the session may link a network is the **server's** answer, not the script's. A session
> scoped to Bluesky that is asked for LinkedIn gets a refusal rendered in the popup and reported as
> `error`. The script only checks that a network was named at all.
## React
The script is framework-free, so React needs nothing special from us — but four things about its
lifecycle are worth getting right the first time.
Load the script **once**, outside your component tree. In Next.js that is `next/script` in your
root layout; in Vite or Create React App it is a tag in `index.html`. Loading it per component
re-runs it on every mount.
```jsx ConnectAccounts.jsx theme={"system"}
import { useEffect, useRef, useState } from "react";
export function ConnectAccounts() {
const slot = useRef(null);
const [linked, setLinked] = useState([]);
useEffect(() => {
// Inside the effect, so the ref is attached: mount() throws if its target
// does not exist yet, which is exactly what happens if you call it during render.
const connect = window.AyrshareConnect.init({
session: () => fetch("/my-api/ayrshare-session").then(r => r.json()),
appearance: { "--ayr-connect-accent": "#0B7A6C" },
});
connect.mount(slot.current, { networks: ["instagram", "tiktok", "x"] });
const stop = connect.on("success", ({ network }) => {
setLinked(current => [...current, network]);
});
// `success` is one of ten events the widget reports. See Events below for
// the full list, including `state`, which replaces polling.
// destroy() takes the frames, the message listener and the timers with it.
// Without this, a route change leaves all three behind.
return () => {
stop();
connect.destroy();
};
}, []);
return ;
}
```
> [!WARNING]
>
> **Never reuse an instance after `destroy()`.** A destroyed instance stays destroyed — `popup()`
> throws on one, and `mount()` will not bring it back. Create a new instance in the next effect run,
> which is what the code above does.
Two consequences of that pattern, neither of them a bug:
* **In development you will see the session callback fire twice.** React's Strict Mode runs effects
mount → unmount → mount, so the instance is created, destroyed and created again. The cleanup
above makes that safe; it costs one extra call to your backend in dev and none in production.
* **Keep the effect's dependencies stable.** An array literal passed straight into `mount` from a
parent render is a new value every time, so an effect that depends on it tears the widget down and
rebuilds it on every render. Memoize it, or keep it constant as above.
## Events
Subscribe with `on`, which returns an unsubscribe function. Handlers receive the event payload and
the mount it came from; `off(name, handler)` does the same job when you would rather name the
handler.
```javascript theme={"system"}
const stop = connect.on("success", ({ network, displayName }, mount) => {
refreshRow(network, displayName, mount.element);
});
stop();
connect.on("error", ({ network, code, message }) => report(code, message));
connect.destroy(); // every frame, listener and timer
```
Ten events. Every one carries a `network` except `ready`, and except the one kind of `error` that
is not about a network at all — see [`error` has two sources](#error-has-two-sources) below.
| Event | Fires when | Also carries |
| - | - | - |
| `ready` | a frame is mounted and ready | — |
| `click` | your user clicked a network, **in either direction** | `action`: `"connect"` or `"unlink"` |
| `started` | a link attempt is under way | — |
| `selection` | your user reached a picker or a form | `step` |
| `success` | the account is connected **and saved** | `displayName` (omitted when unknown), `refId` |
| `unlinked` | a connected account was removed, and the removal is saved | — |
| `error` | the attempt failed | `code`, `message`, `reason` |
| `cancelled` | your user backed out | `reason` |
| `closed` | the popup this attempt used has closed | — |
| `state` | a network's account state, at mount and on every change | `state`, `since` |
**Four of them are endings** — `success`, `unlinked`, `error` and `cancelled` — and exactly one
arrives per attempt. `closed` is a lifecycle notice that follows an ending rather than being one.
`click` fires **before** any linking work starts, so it reports a click that a blocked popup or a
dead session goes on to refuse. It is the event to use for your own analytics; `started` is the one
that means an attempt is really running.
### Reasons
| Event | `reason` | Means |
| - | - | - |
| `error` | `popupBlocked` | the browser refused to open the popup |
| `cancelled` | `popupClosed` | your user closed the window by hand |
| `cancelled` | `scopesDenied` | your user declined a permission the network asked for |
| `cancelled` | `userCancelled` | your user backed out, or your code called `handle.close()` |
| `cancelled` | `superseded` | a second `popup()` call replaced this attempt |
### `error` Has Two Sources
Only one of them is a link failure, and they carry different fields.
* A **link error** carries `network`, `code` and the mount it came from.
* A **session error** — we could not create or refresh your session — carries only `message`,
because nothing was being linked at the time.
Destructure defensively: `code` and `network` are `undefined` on the second kind.
### `state` Saves You Polling
`state` is the data channel rather than a report on an attempt. Every frame emits one per network as
it mounts, carrying that network's current state and the `since` timestamp it has held it from, and
another whenever a state changes — **including changes that originate on our side**, such as a token
dying into relink-required. So you can drive your whole UI from the widget without polling anything.
The values are the same enum
[`GET /profiles` with `include=state`](/docs/apis/profiles/get-profiles) returns: `linked`, `unlinked`,
`identityVerificationRequired`, `restricted`, `rateLimited`, `suspended`.
## Unlinking
Our tiles unlink as well as link. Your user clicks a connected network, confirms, and the account is
removed:
1. `click` fires with `action: "unlink"`.
2. `unlinked` fires once the removal is saved.
An unlink that **fails** reports `error`, and one your user backs out of at the confirm step reports
`cancelled`. There is no separate unlink-failed event.
## Appearance
A customer's stylesheet cannot reach inside a cross-origin frame, so styling travels as data we
apply within it. Pass `appearance` to `init` as CSS custom properties; each one we do not receive
keeps our default.
```javascript theme={"system"}
const connect = AyrshareConnect.init({
session: getSession,
appearance: {
"--ayr-connect-font-family": "Inter, system-ui, sans-serif",
"--ayr-connect-surface-bg": "#111318",
"--ayr-connect-surface-fg": "#F2F3F7",
"--ayr-connect-accent": "#0B7A6C",
"--ayr-connect-accent-fg": "#FFFFFF",
"--ayr-connect-radius": "12px",
},
});
```
> [!WARNING]
>
> **Set colors in pairs.** A background token with no foreground token beside it is the one way to
> make this produce something unreadable: set `--ayr-connect-surface-bg` to a dark value on its own
> and our default `--ayr-connect-surface-fg` is still dark navy. Nothing can infer the other half
> for you.
With no `appearance` at all, every token holds its default and a frame looks like this:

Default tokens: white surfaces, dark navy text, indigo accent, 8px radius.
Pass a handful of tokens and the same frame takes on your palette. This example turns the
surfaces pale blue, deepens the text and accent to match, and rounds the corners a little more:
```javascript theme={"system"}
const connect = AyrshareConnect.init({
session: getSession,
appearance: {
"--ayr-connect-surface-bg": "#EFF6FF",
"--ayr-connect-border-color": "#BFDBFE",
"--ayr-connect-surface-fg": "#0F2A5F",
"--ayr-connect-surface-fg-muted": "#4A6A9A",
"--ayr-connect-accent": "#1D4ED8",
"--ayr-connect-radius": "14px",
"--ayr-connect-spacing": "10px",
},
});
```

The same three tiles after the tokens above are applied.
**There is one palette and no light/dark preset.** Nothing keys on `prefers-color-scheme`, on
purpose — your page's theme may not match your user's operating system, and a media query would
silently outvote the colors you chose. A dark dashboard is themed by supplying dark values.
This token list is a supported contract we keep across versions.
### The Tokens
| Token | Default |
| - | - |
| `--ayr-connect-font-family` | `Inter, "Segoe UI", system-ui, -apple-system, sans-serif` |
| `--ayr-connect-font-size` | `16px` |
| `--ayr-connect-font-size-sm` | `12px` |
| `--ayr-connect-label-font-weight` | `600` |
| `--ayr-connect-status-font-size` | `12px` |
| `--ayr-connect-status-font-weight` | `600` |
| `--ayr-connect-surface-bg` | `#FFFFFF` |
| `--ayr-connect-surface-bg-hover` | `#F7F7F7` |
| `--ayr-connect-surface-fg` | `#010629` |
| `--ayr-connect-surface-fg-muted` | `#56596F` |
| `--ayr-connect-border-color` | `#DDDEE2` |
| `--ayr-connect-border-width` | `1px` |
| `--ayr-connect-radius` | `8px` |
| `--ayr-connect-spacing` | `8px` |
| `--ayr-connect-accent` | `#4553EE` |
| `--ayr-connect-accent-fg` | `#FFFFFF` |
| `--ayr-connect-focus-ring-color` | `#4553EE` |
| `--ayr-connect-focus-ring-width` | `2px` |
| `--ayr-connect-disabled-fg` | `#6E7185` |
| `--ayr-connect-status-radius` | `4px` |
| `--ayr-connect-status-success-bg` | `#EBFFF8` |
| `--ayr-connect-status-success-fg` | `#237C5C` |
| `--ayr-connect-status-warning-bg` | `#FFF7EF` |
| `--ayr-connect-status-warning-fg` | `#702E00` |
| `--ayr-connect-status-critical-bg` | `#FFE5E1` |
| `--ayr-connect-status-critical-fg` | `#AD1902` |
| `--ayr-connect-status-info-bg` | `#ECF9FF` |
| `--ayr-connect-status-info-fg` | `#1F6686` |
| `--ayr-connect-callout-bg` | `#FFF7EF` |
| `--ayr-connect-callout-fg` | `#702E00` |
| `--ayr-connect-partner-name` | `""` |
| `--ayr-connect-icon-size` | `32px` |
| `--ayr-connect-avatar-size` | `40px` |
A value that is not valid CSS for its token is **ignored, with a warning in your console**, rather
than applied. That matters more than it sounds: a unitless `8` for `--ayr-connect-spacing` is a
perfectly innocent string that would invalidate every calculation reading it and collapse the
layout, with no error anywhere. Give lengths a unit.
### Your Own Name on the Hand-Off Screen
Before we hand your user to a network, we show a short screen naming who they are connecting
through. Two hooks let you make that your own, and both are version-stable.
`--ayr-connect-partner-name` sets the label. It is the one token whose value is **text**, so it has
to be quoted as a CSS string — an unquoted value is invalid and renders nothing at all:
```javascript theme={"system"}
AyrshareConnect.init({
session: getSession,
appearance: { "--ayr-connect-partner-name": "'Acme Social'" },
css: "[data-ayr-connect-partner-mark]::after { background-image: url('https://cdn.example.com/mark.svg') }",
});
```
The logo is not a token. We ship the mark as a positioned, sized element and you fill it with a
`background-image` through `css`, as above — a token that could fetch an image from inside our
document is not something we accept, so the request comes from a rule you wrote rather than from a
value you passed us.
> [!NOTE]
>
> `[data-ayr-connect-partner-mark]` and `[data-ayr-connect-partner-name]` are the **exception** to
> the custom-CSS caveat below: these two selectors are part of the contract and we keep them across
> versions.
### Custom CSS
`css` takes a string applied inside every frame, for the cases tokens do not cover.
```javascript theme={"system"}
AyrshareConnect.init({ session: getSession, css: "button { letter-spacing: 0.01em }" });
```
> [!WARNING]
>
> **Custom CSS is not supported across versions.** Its selectors target our internal markup, which
> changes between releases — a rule that works today can silently stop matching after any update.
> The token contract above is the part we keep. Pin a version if you depend on custom CSS.
Popups inherit the instance's `appearance` and `css` exactly as frames do, so your user does not
watch our neutral defaults appear halfway through a flow.
## Worth Knowing Before You Ship
> [accordion] A popup whose session has expired reports cancelled, not error
>
> A popup you opened with `popup()` cannot be told why a token was refused — to say so it would
> have to trust an origin it has not yet validated, which our security model does not allow. So a
> popup carrying a dead token closes and surfaces as `cancelled` with `reason: "popupClosed"`
> rather than `error`.
>
> In practice this is rare: a popup opened before a silent refresh keeps working, because it
> validated its token when it opened. If you see unexplained `popupClosed` results, check that
> your `session` endpoint is returning a fresh session.
> [accordion] One session per instance, not one per mount
>
> Fourteen mounts share one token and cost one call to your backend, not fourteen. If you want
> differently scoped slots — a different `allowedSocial` on some of them — run a second `init`
> with its own session rather than expecting a mount to narrow it.
> [accordion] The frames only render on the origin you declared
>
> Every session carries the `origin` your page runs on, and a frame compares the page embedding it
> against that value before rendering anything. A frame embedded somewhere else stays blank and
> sends no events. There is no allowlist to register and nothing to configure — send the right
> `origin` when you create the session.
> [accordion] Height is handled for you, and is not an event
>
> Frames report their height to the script, and the script resizes them. Your layout just reflows.
> There is no resize event to subscribe to, and nothing to measure on your side.
## Requirements
The **[Max Pack](/docs/additional/maxpack)**. A widget session is a connect-mode session, and
creating one without the Max Pack returns `code: 504`. Contact support if you need connect mode
enabled on an account without it.
An **`origin`** on every session — the exact origin your page runs on. Omitting it returns
`code: 505`; a value that is not an `https` origin, a custom scheme, or `http://localhost`
returns `code: 506`.
**No `network`** on the session. That parameter is what makes a session
[direct mode](/docs/multiple-users/connect-direct-mode) instead, and a direct-mode session's URL is
not what the script expects.
Every code above is in the
[Link Session Errors](/docs/errors/errors-ayrshare#link-session-errors) reference, with the exact message
the API returns and what to do about it.
# Link Completion Events
Source: https://www.ayrshare.com/docs/multiple-users/link-completion-events
Get told when your user connects an account, instead of polling for it.
When you open a linking page in a popup, your own page can listen for what happens in it:
your user connected Reddit, they backed out, the connection failed. You get an event per
outcome, so your UI updates the moment it happens rather than on a timer.
Set [`origin`](/docs/apis/profiles/create-link-session) when you create the link, open the
returned `url` in a popup, and listen. Nothing else is required, and nothing changes for
links created without `origin` — they behave exactly as they always have.
> [!NOTE]
>
> **Using the [embedded widget](/docs/multiple-users/connect-widget)? This is already wired up for you.**
> The script receives these events and hands them to `connect.on("success", …)` with the
> `connect:` prefix stripped — no `message` listener, no origin check, no `popup.closed` poll to
> write. This page is the wire protocol underneath, and what you build against when **you** own the
> window: direct mode, or a popup you open yourself.
> [!NOTE]
>
> Events are sent only to the exact `origin` you set on the link, and only to the window
> that opened the popup. Always check `event.origin` in your listener anyway: any page can
> post a message to your window, and the origin is the only part of a message that cannot
> be faked.
## The Events
Every message posted to your page is an object shaped
`{ source: "ayrshare", version: 1, event, ... }`, and carries `network` whenever the event is
about one. Two events are not: a `connect:closed` from the hosted linking page, where it means your
user pressed Done rather than that one connection ended, and the widget's `connect:ready` (see
below). The outcomes your own code synthesizes — a blocked popup, or a window your user closed — do
not come from us and carry only what you give them.
> [!NOTE]
>
> The [embedded widget](/docs/multiple-users/connect-widget) differs on one event. Its `ready` announces
> that a slot is up rather than anything about a network, so it carries no `network` — whereas the
> popup's `ready` names the network it was opened for. If you handle both surfaces from one
> listener, read `network` defensively on `ready`.
>
> The widget also adds one `cancelled` reason this surface never sends: `superseded`, when a second
> `popup()` call replaces an attempt still in flight. Here there is one window and one outcome, so
> there is nothing to supersede.
| Event | Extra fields | Sent when |
| - | - | - |
| `connect:ready` | | The page has loaded and the link has been checked. |
| `connect:started` | | Your user has been sent to the social network. |
| `connect:selection` | `step` | A picker or a form is on screen — your user has something to do. |
| `connect:success` | `refId`, `displayName` | The account is connected **and saved**. `refId` is the User Profile it was connected to. `displayName` is the account name, and is omitted when we do not have one yet. |
| `connect:error` | `message`, and `code` when there is one | The connection failed. `code` matches the [error reference](/docs/errors/overview); it is absent when the failure has no catalogued code, and on any outcome your own snippet synthesizes, such as a blocked popup. |
| `connect:cancelled` | `reason` | Your user backed out. `reason` is `popupClosed`, `scopesDenied`, or `userCancelled`. |
| `connect:closed` | | The popup is about to close itself. Carries no `network` when it comes from the hosted linking page, where it means your user pressed Done rather than that one connection ended. |
Exactly one of `connect:success`, `connect:error` or `connect:cancelled` arrives per
connection. `connect:closed` is not one of them — it follows, to tell you the popup closed
on purpose.
`connect:success` is sent only after the account has been saved, so a
[`GET /user`](/docs/apis/user/profile-details) immediately after it already shows the connected account.
> [!NOTE]
>
> The popup stays open after `connect:error` so your user can read what went wrong. It
> closes itself after `connect:success` and `connect:cancelled`. Add `&autoClose=false` to
> the URL to keep it open in every case while you are debugging.
## Listening
Two things this snippet does that are easy to leave out. It checks `event.origin`, and it
watches for a popup your user closed by hand — a closed window cannot send anything, so
polling is the only way to notice.
```javascript theme={"system"}
function connectAccount(url) {
// Derive the origin from the URL you were given rather than hard-coding one:
// if your account uses its own linking domain, the popup runs on that.
const popupOrigin = new URL(url).origin;
const popup = window.open(url, "ayrshare-connect", "width=600,height=800");
if (!popup) {
handleOutcome({ event: "connect:error", message: "The popup was blocked." });
return;
}
// Declared before anything can call `cleanup`: a message arriving early would
// otherwise hit `poll` in its temporal dead zone and throw.
let poll;
const cleanup = () => {
clearInterval(poll);
window.removeEventListener("message", onMessage);
};
const onMessage = event => {
// Both checks, not just the origin: another tab or frame on the same origin
// could otherwise post a message your handler would believe.
if (event.origin !== popupOrigin || event.source !== popup) return;
const message = event.data;
if (!message || message.source !== "ayrshare") return;
if (["connect:success", "connect:error", "connect:cancelled"].includes(message.event)) {
// `finally`, so your own handler throwing cannot leave the poll running —
// it would later see the closed popup and report `cancelled` on top of the
// outcome you already had. Cleanup also matters after `connect:error`,
// where the popup stays open so your user can read it.
try {
handleOutcome(message);
} finally {
cleanup();
}
}
};
window.addEventListener("message", onMessage);
// A hand-closed popup sends nothing, so watch for it. Wait a moment before
// deciding: the popup closes itself right after sending, and the message can
// still be in flight when you first see the window go.
let closedAt = null;
poll = setInterval(() => {
if (!popup.closed) return;
if (closedAt === null) {
closedAt = Date.now();
return;
}
if (Date.now() - closedAt < 750) return;
cleanup();
handleOutcome({ event: "connect:cancelled", reason: "popupClosed" });
}, 500);
}
```
## Errors
`connect:error` carries the same codes as the rest of the API, so a code you see here means
what it means everywhere else. The one specific to this surface:
| Code | Meaning |
| - | - |
| `513` | The link was opened as a single-network connect window, but was not created for one. Only reachable if you build that URL yourself — the `url` this endpoint returns always matches the link it created. |
Anything else is the social network's own failure, reported with whatever code that
failure already has — for example `322` for an Instagram authorization problem, which
includes an account that is still Personal rather than Professional.
### A dead link arrives differently
A link that is **expired**, **revoked** or **unknown** is refused before the page can learn
where to send events, so it cannot send one. Your user sees the reason and its code on
screen, and your page hears `connect:cancelled` when they close the window.
To tell those apart, poll [Get a Link Session](/docs/apis/profiles/get-link-session): it reports
`expired` and `revoked` authoritatively, and returns `502` for a link that does not exist.
## If You Would Rather Not Manage a Popup
Two options, and only the second gives up events.
**Let the widget own the window.** The [embedded widget](/docs/multiple-users/connect-widget) opens and
watches it for you and delivers every event above to your handlers. You still get `success` the
moment an account is saved; you just do not write the plumbing. Its frames also mean most networks
never open a popup at all until the network's own login.
**Poll instead.** If a popup is genuinely not available — a server-rendered app, or a mobile app
opening the link in the system browser — poll
[Get a Link Session](/docs/apis/profiles/get-link-session). It reports `completedAt` and
`completedNetworks` as soon as an account is saved, which is the same moment `connect:success`
would have been sent. Telegram always finishes this way, since it completes out of band with no
browser callback at all.
# Manage Multiple User Profiles
Source: https://www.ayrshare.com/docs/multiple-users/manage-user-profiles
There are two ways to create and manage multiple account profiles for your organization, users, clients, or brands - via the Dashboard or API.
> [!WARNING]
>
> **The dashboard is for your internal team only.** On the Launch, Business, and
> Enterprise plans, the dashboard is where *your team* creates and manages User
> Profiles, tests, and posts on a client's behalf. Your customers are **never**
> given dashboard access — they connect their social accounts through the
> white-labeled [JWT URL connect page](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication).
> In production, create and manage profiles programmatically with the
> [API](#using-the-api). (The single-user Premium plan is the exception: there is
> no multi-user layer, so you use the dashboard directly yourself.)
## Using the Dashboard
If your Ayrshare account is entitled to manage multiple profiles, then you will see the option for **User Profiles** in the left-hand navigation.
> [!NOTE]
>
> If your account does not have the option to manage profiles, then contact your Ayrshare
> representative or [contact us](mailto:contact@ayrshare.com) to upgrade.
### Create a New Profile
Click the **Add Profile** button and enter your User Profile Title.

### Switch to the New Profile
You will see the new Profile show up in the Active Profiles section.
You can now click on the **switch to this profile** icon to switch to that User Profile.

When you switch to a different User Profile, you will see that profile name shown in the top left of the screen under the profile icon.
You can always switch back to your primary account profile by clicking the **switch profile** link next to the Primary Profile or by clicking the **Switch to Primary** button at the top of the User Profiles page.
From here you are acting as your client/user and can post on their behalf by going to the *Post* page.
### Get the Profile Key
The Profile Key is used to post on behalf of the profile with the API. Switch to a [User Profile](/docs/multiple-users/manage-user-profiles) and go to the *Profile Key page* in the left-hand navigation of the Dashboard to get that profile's Key.
When making an API call, be sure to include your [API\_KEY](/docs/apis/overview#authorization) and [PROFILE\_KEY](/docs/apis/overview#profile-key-format) in the header.
You also receive the Profile Key when you create a new User Profile using the [/profiles/profile endpoint](/docs/apis/profiles/create-profile). This is the preferred method to get the Profile Key.
### Invite a Team Member
You can create User Profiles for members of your team. By default, Team Members have an Ayrshare account with full access/admin rights to the dashboard and count as a new User Profile. Please note, you may restrict access by [locking the Primary Profile](/docs/multiple-users/manage-user-profiles#restrict-primary-profile-access).
> [!TIP]
>
> You can only invite team members who have not yet been registered with Ayrshare. If you want to
> use an already registered email, please have the user login to the dashboard, go to the Account
> page, and click the **Delete** button.
In the User Profiles page, switch to your Primary Profile and click the *+ Add Profile* button at the top of the page.
In the pop-up modal, click the checkbox "Create a team member profiles with admin rights" box and enter your team member's email address.

Click *Add Team Profile* and an email is sent to the team member's email address with a link to activate their account.
#### Team Member Status
Once the team member has accepted the invite you will see a green check mark next to the Team Member tag.

If the team member has not yet accepted the invite you may resend it by clicking the mail icon.

#### Team Members Contacting Support
> [!WARNING]
>
> For security, only registered team members may contact support on behalf of a business. We will
> not be able to assist if they are not registered and verified as a team member of your business.
### Reactivate a Suspended User Profile
#### Suspension Reason
When a User Profile gets suspended due to a social network violation (such as a [Facebook community standards violation](/docs/help-center/technical-support/facebook-or-instagram-account-restricted)), you will receive a detailed notification email.
This email contains important information about the suspension, including the specific reason and the affected User Profile.
To locate this notification email:
1. Search your inbox using the RefId of the suspended User Profile
2. The email subject will clearly indicate a suspension has occurred
Alternatively, you can view suspension details directly in the dashboard by clicking on the "Suspended" tag that appears next to the affected User Profile.
All API calls for that User Profile will return a [403 HTTP status code](/docs/errors/errors-http#403-access-denied).
#### Reactivate a Suspended User Profile
You may reactivate, i.e. unsuspend, the User Profile by:
1. Logging into the Dashboard and going to the User Profiles page.
2. Find the suspended User Profiles by clicking the "Suspended" tab or searching by the RefId of the User Profile, which is included in the email.
3. Unsuspend the User Profile by clicking the "Unsuspended" button.
4. Review and accept the compliance certification.

For a User Profile's first suspension, the User Profile can be immediately unsuspended once you accept the compliance certification.
However, for subsequent suspensions the User Profile will be reinstated *48 hours* after accepting the compliance certification.
> [!WARNING]
>
> We recommend you *do not* use your Primary Profile as a regular User Profile. If the Primary
> Profile is suspended, all user profiles will also be suspended.
### Managing Active User Profiles
Effective management of User Profiles is essential for maintaining an efficient system in Ayrshare.
We recommend implementing a policy to remove inactive User Profiles based on your business requirements.
For example, if a user hasn't engaged with your platform by posting content, requesting analytics for some time period, or has not connected any social networks, consider deleting their Ayrshare User Profile, thus keeping your user base current and reducing future costs.
You can check if a User Profile has been active in the current month by reviewing their [posting history](/docs/apis/history/overview) and [API usage and connections](/docs/apis/user/overview).
If you determine it to be inactive, [delete the User Profile](/docs/apis/profiles/delete-profile).
Each monthly billing period is charged on the highest number of User Profiles reached on any single day of that period, and never on fewer than the minimum included in your plan — deleting User Profiles while you are at that minimum does not lower the charge. That high-water mark only ever rises: deleting a User Profile stops later days from pushing it higher, but it cannot bring down a figure the period has already reached, so once your peak is behind you the saving lands in your next billing cycle. This does not apply to the Launch plan, which has a flat price, or to custom and Enterprise agreements, whose own terms apply. Remove inactive User Profiles as soon as you identify them rather than waiting for the period to end. You can see your daily User Profile counts with [Get User Profiles](/docs/apis/profiles/get-profiles) and `actionLog` set to `true`.
## White-Label the Connect Accounts Page
The connect accounts (social linking) page is the **only** Ayrshare interface your customers ever see, so you can white-label it to match your brand. Settings below are configured once by your team from the Primary Profile and apply to the page your users open via the [JWT URL](/docs/multiple-users/api-integration-business#single-sign-on-with-jwt-authentication).
**Available on the Launch, Business, and Enterprise plans:**
[Add or remove your company logo](#update-logo-on-social-linking-page) and set its height.
[Choose which social networks](#set-social-networks-access) your users can connect.
[Set the page language](#set-language-for-social-linking).
[Show your own help links or hide Ayrshare's](#help-links-visible).
[Set the top and sub headers](/docs/apis/profiles/create-profile) per User Profile (e.g. the client's name or business).
[Control the Close button redirect](/docs/multiple-users/api-integration-business#opening-&-closing-the-social-linking-url) back to your app.
[Customize the Instagram and Telegram modals](#custom-modals) with your own HTML.
**Available with the [Max Pack](/docs/additional/maxpack) (and included with Enterprise):**
[Apply a custom CSS file](#customize-css) to control colors, fonts, and which features show, using [documented class hooks](#css-class-hooks) to restyle the connect buttons themselves.
[Color the linking dialogs](#linking-dialog-colors) — page pickers, credential forms, and confirmations — to match your page.
[Set your own page title, Close button text, and favicon](#change-page-title-close-button-and-favicon).
[Add custom footer text or HTML](#footer-text).
See the [User Integration](/docs/multiple-users/user-integration#social-linking-page-customization) page for how this looks from your user's perspective.
## Settings
In the *User Profiles* page select "⚙️ Settings" button to access global settings for your profiles.
Be sure to first switch to your *Primary Profile* to access the settings.

### Video Demo of Linking Page Customization Options
[Social Account Linking Page Customization Demo](https://www.youtube.com/embed/3RatEh09Qyg)
### Restrict User Profile Access
#### Restrict Primary Profile Access
Restrict access to the Primary Profile from user and team member profiles by locking the Primary Profile.
Activate the Primary Profile lock if you want to prevent team members or user profiles from accessing Profile Settings or the Account/Billing page.

When activated team member and user profiles will not be able to switch to the Primary Profile, but may
switch to other team member or user profiles.
#### Restrict Team Member Access
Restrict access to team member profiles by locking their account.
This prevents the team member from switching to any other User Profile.
Enable the lock by clicking the or icon in the top right of a team member profile.

You may also lock the team member when you [create the profile](/docs/multiple-users/manage-user-profiles#create-a-new-profile).
### Update Logo on Social Linking Page
Add your own logo and set the logo height for the [social linking page](/docs/multiple-users/user-integration).

Individual User Profiles can suppress this logo on their own linking page via the `hideLogo` parameter on [Create Profile](/docs/apis/profiles/create-profile) and [Update Profile](/docs/apis/profiles/update-profile). The account-wide logo on every other profile is unaffected. This is useful when an agency white-labels one partner while keeping branding on the rest of their profiles.
### Set Social Networks Access
Remove or grant your users/profiles access to the available social networks.
By default all social networks are active and available.
> [!NOTE]
>
> Permissions are global across all your user profiles. Disabling only removes the network from your
> user's Social Accounts view and does not remove established links.

See the [create a profile](/docs/apis/profiles/overview#enable-or-disable-social-networks) endpoint for more
information on disabling social networks at the User Profile level.
### Set Language for Social Linking
Set the language for the social linking page. English is the default.

Available languages are:
English
Arabic
Chinese (Simplified)
French
German
Italian
Japanese
Portuguese
Spanish
If you require a language that is not listed, please contact us.
See here for additional translation options:
[Translate API Error Message Responses](/docs/errors/errors-ayrshare#error-message-translation)
[Translate a Post](/docs/apis/generate/translate-post)
### Display Page Location
On the social linking page, if a Meta (Facebook or Instagram) or Google Business Profile Page has a location associated with it, display that location information along with the Page name. This additional location context is helpful in scenarios where there are multiple Pages with identical names, as it allows users to differentiate between them more easily.

The Page location, if available, will be displayed below the Page name in the social linking page:

### Help Links Visible
Enable or disable visibility of the links to the Ayrshare help docs on your users' social linking page. You can also use your own by entering a URL to your publicly available docs.

### Alternative Emails for Alerts
Choose different email addresses to deliver alerts, such as unlinked accounts.

Further customization, such as the title can be done via the [/profiles endpoint](/docs/apis/profiles/create-profile).
### Instagram Login
Enable or disable Instagram login on the social linking page.

Choose whether to enable direct Instagram Login linking that does not require a connected Facebook Page for authentication. If disabled, Instagram linking will require a [connected Facebook Page](/docs/dashboard/connect-social-accounts/facebook) for authentication.
> [!WARNING]
>
> Instagram Login is **enabled by default**. If you require the advanced features listed below
> (hashtag search, collaborations, location tagging, or brand data), you must manually disable this
> option to use Facebook Page authentication instead.
#### Direct Instagram Login vs Facebook Page Authentication
**Direct Instagram Login** Instagram API with Instagram Login allows Instagram professionals (businesses and creators) to link their accounts directly without needing an associated Facebook Page. This provides a simplified authentication flow.
**Facebook Page Authentication** (traditional method) requires users to first connect a Facebook Page that is linked to their Instagram professional account.
#### Feature Limitations with Direct Instagram Login
While direct Instagram Login offers convenience, certain features are **not available** when using this method:
* **[Brand/User Data](/docs/apis/listen/brand-user#param-instagram-user)** - Cannot retrieve Instagram account information through the brand endpoints.
* **[Hashtag Search](/docs/apis/hashtags/search-hashtags)** - Cannot search for hashtags on Instagram.
* **[Collaborations](/docs/apis/post/social-networks/instagram#collaboration)** - Cannot invite collaborators to Instagram posts.
* **[Location Tagging](/docs/apis/post/social-networks/instagram#location)** - Cannot add location tags to Instagram posts.
* **[`idId`- Legacy Instagram User ID](/docs/apis/analytics/social)** - field not returned for Instagram social analytics.
#### Which Method Should You Choose?
* **Enable Direct Instagram Login** if you only need basic posting functionality and don't require the advanced features listed above.
* **Disable Direct Instagram Login** (use Facebook Page authentication) if you need access to all Instagram features, including hashtag search, collaborations, location tagging, or brand data retrieval.
> [!NOTE]
>
> For most clients, the preferred method is to use the direct Instagram Login authentication. If
> you're unsure which method to use, we recommend disabling Direct Instagram Login to ensure access
> to all features. You can always enable it later if you determine the advanced features are not
> needed.
### Custom Modals
You can customize the content shown in the Instagram and Telegram modals on the social linking page with your own custom HTML content, such as displaying the content in different languages.
For Telegram, your content replaces the modal's default instructions. For Instagram, the modal only appears when you provide custom content — otherwise, clicking Instagram goes straight to the configured login flow: Instagram's own login or, if you have disabled [Instagram Login](#instagram-login), a Facebook login.
Your custom content can include formatted text, images, or a combination of both to better suit your needs.

For example, add your custom text with an anchor link:
```html Custom HTML Modal theme={"system"}
```
```css Your Custom CSS file theme={"system"}
span.underline {
color: blue;
text-decoration: underline;
}
span.underline a {
color: blue;
text-decoration: none;
}
```
> [!NOTE]
>
> The HTML must be enclosed in `
` tags.
>
> Style it with class names and put the rules in your [custom CSS file](#customize-css). `