# Antigravity
Source: https://docs.trypost.it/ai/antigravity
Connect TryPost to Google Antigravity for AI-powered social media management
[Antigravity](https://antigravity.google) is Google's AI coding agent. You can connect it to TryPost's MCP server when the client can complete OAuth.
## Prerequisites
* Antigravity installed
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
From Antigravity, open **MCP Servers → Manage → View raw config**, then add the TryPost server **without** an `Authorization` header so OAuth can run:
```json theme={null}
{
"mcpServers": {
"trypost": {
"serverUrl": "https://app.trypost.it/mcp/trypost"
}
}
}
```
Restart Antigravity if needed, then trigger a TryPost tool call. Complete the browser OAuth flow (**Allow**). Personal Access Tokens are not accepted on the MCP endpoint.
Ask Antigravity:
```
List all my TryPost social accounts
```
Antigravity uses `"serverUrl"` for remote MCP connections (not `"url"`). This is the same format used by Windsurf.
If Antigravity cannot finish OAuth (no browser callback), use the [REST API](/api-reference/introduction) with an API key from **Settings → API Keys** instead of MCP.
## Example prompts
```
Create a new post scheduled for Friday at 2pm
```
```
Show me all labels in my workspace
```
```
Create a signature called "Social Media" with #socialmedia #content #marketing
```
# ChatGPT
Source: https://docs.trypost.it/ai/chatgpt
Connect TryPost to ChatGPT for AI-powered social media management
[ChatGPT](https://chatgpt.com) can connect to TryPost's MCP server as a developer-mode app, so you can manage social media scheduling with natural language in chat.
## Prerequisites
* A ChatGPT **Pro, Plus, Business, Enterprise, or Education** account on the **web** ([Developer mode](https://developers.openai.com/api/docs/guides/developer-mode) — not available on Free; not guaranteed on mobile)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
In ChatGPT on the web, open **Settings → Security and login** and turn on **Developer mode**.
If that toggle is missing, try **Settings → Apps → Advanced settings**. On Business / Enterprise / Education workspaces, an admin may need to allow Developer mode first.
With Developer mode on, open the connectors flow (or go directly to [chatgpt.com/plugins#settings/Connectors?create-connector=true\&redirectAfter=%2Fplugins](https://chatgpt.com/plugins#settings/Connectors?create-connector=true\&redirectAfter=%2Fplugins)). You can also use **Settings → Plugins** / **Settings → Apps** and click **+**.
Fill in:
| Field | Value |
| ------------------ | ------------------------------------------------ |
| **Name** | `TryPost` |
| **Description** | `Schedule and publish social posts with TryPost` |
| **MCP server URL** | `https://app.trypost.it/mcp/trypost` |
For authentication, use **OAuth**. Leave Client ID / Secret blank if shown — TryPost supports Dynamic Client Registration and discovers OAuth from the URL.
Create the connection. Complete the TryPost OAuth sign-in if prompted (**Allow**). Open the app details page to review discovered tools; you can toggle tools on or off and **Refresh** later when the MCP server changes.
Start a new chat. From the composer **+** / Plus menu, choose **Developer mode**, enable the **TryPost** app for that conversation, and ask:
```
List all my scheduled posts
```
ChatGPT will call the TryPost tools to fetch your posts.
ChatGPT only connects to remote HTTPS MCP servers (from OpenAI's infrastructure). The hosted endpoint `https://app.trypost.it/mcp/trypost` works out of the box. Self-hosted instances must be publicly reachable over HTTPS — local `stdio` MCP servers are not supported.
Write actions (create, update, publish, delete) require confirmation by default in Developer mode. Review each tool payload before approving. Tools annotated as read-only skip that confirmation.
After MCP server changes, open the app under **Settings → Plugins** (or **Apps**), click **Refresh**, and start a new conversation so ChatGPT picks up updated tools and descriptions.
## Example prompts
```
Show me all my connected social media accounts
```
```
Create a draft LinkedIn post for tomorrow at 9am
```
```
What posts do I have scheduled for this week?
```
```
Create a label called "Campaign" with color #f59e0b
```
# Claude
Source: https://docs.trypost.it/ai/claude
Connect TryPost to Claude on the web for AI-powered social media management
[Claude](https://claude.ai) is Anthropic's web app for chatting with Claude. You can connect it to TryPost's MCP server to manage your social media scheduling through conversation — no desktop install required. The same custom connector also works in **Claude Cowork** and on **Claude Desktop** when you use remote connectors (they connect from Anthropic's cloud via your Claude account).
## Prerequisites
* A [Claude](https://claude.ai) account on Free, Pro, Max, Team, or Enterprise (custom connectors are available on all of these; Free is limited to one custom connector)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Go to **[Customize → Connectors](https://claude.ai/customize/connectors)** (some Claude UIs still say **Settings → Connectors**) and click **+** → **Add custom connector**.
Free, Pro, and Max use this path. Team and Enterprise Owners add connectors at the org level first — see the accordion below.
Enter a **name** (for example `TryPost`) and the MCP server URL:
```
https://app.trypost.it/mcp/trypost
```
Leave **Advanced settings** (OAuth Client ID / Client Secret) empty — TryPost supports Dynamic Client Registration, so Claude registers itself automatically. Click **Add**.
Complete the OAuth sign-in in the browser. Sign in to TryPost, review the permissions, and click **Allow**. Return to Claude — the connector should show as connected.
Start a new conversation, click **+** (lower left) → **Connectors**, and toggle **TryPost** on for that chat. Then ask:
```
List all my scheduled posts
```
Claude will use the TryPost tools to fetch your posts.
Claude reaches remote MCP servers from Anthropic's cloud, not from your browser or laptop. The hosted URL `https://app.trypost.it/mcp/trypost` is publicly reachable. Self-hosted instances must be exposed on the public internet (or tunneled) for Claude to connect.
Only an Owner (or Primary Owner) can add a custom connector to the organization:
1. Open **[Organization settings → Connectors](https://claude.ai/admin-settings/connectors)**
2. Click **Add** → **Custom** → **Web**
3. Paste `https://app.trypost.it/mcp/trypost` (Client ID / Secret optional — leave empty for DCR)
4. Click **Add**
Each member then opens **[Customize → Connectors](https://claude.ai/customize/connectors)**, finds the connector labeled **Custom**, and clicks **Connect** to authorize their own TryPost account.
## Example prompts
```
Show me all my connected social media accounts
```
```
Create a new signature called "Content Marketing"
```
```
What posts do I have scheduled for this week?
```
```
Create a label called "Urgent" with a red color
```
## Related guides
Prefer the desktop app or the CLI? See [Claude Desktop](/ai/claude-desktop) and [Claude Code](/ai/claude-code).
# Claude Code
Source: https://docs.trypost.it/ai/claude-code
Connect TryPost to Claude Code for AI-powered social media management from the terminal
[Claude Code](https://docs.anthropic.com/en/docs/claude-code) is Anthropic's CLI tool for working with Claude directly in your terminal. You can connect it to TryPost's MCP server to manage your social media scheduling with natural language.
## Prerequisites
* Claude Code installed (`npm install -g @anthropic-ai/claude-code`)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Run the following command in your terminal:
```bash theme={null}
claude mcp add --transport http trypost https://app.trypost.it/mcp/trypost
```
This registers the server in the project's `.mcp.json`. Append `--scope user` to make it available across all projects.
Start Claude Code and trigger the first TryPost call:
```bash theme={null}
claude
```
```
List all my scheduled posts
```
Claude Code opens a browser window asking you to sign in to TryPost and authorize the connection. Click **Allow** — Claude Code stores the OAuth token and refreshes it automatically from then on.
## Manual configuration
If you prefer to edit the config file directly, this is what `claude mcp add` writes (no `Authorization` header — OAuth is discovered from the URL):
```json theme={null}
{
"mcpServers": {
"trypost": {
"type": "http",
"url": "https://app.trypost.it/mcp/trypost"
}
}
}
```
Drop it in `.mcp.json` for project scope or `~/.claude.json` for user scope.
## Example prompts
```
Create a new post scheduled for tomorrow at 10am
```
```
Show me all my connected social accounts
```
```
Create a signature called "SaaS Marketing" with #saas #marketing #growth #startup
```
```
List all my labels
```
# Claude Desktop
Source: https://docs.trypost.it/ai/claude-desktop
Connect TryPost to the Claude Desktop app for AI-powered social media management
[Claude Desktop](https://claude.ai/download) is Anthropic's desktop application for chatting with Claude. You can connect it to TryPost's MCP server to manage your social media scheduling through conversation.
## Prerequisites
* Claude Desktop installed
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
In Claude Desktop, go to **Settings** → **Connectors** and click **Add custom connector**.
Paste the server URL:
```
https://app.trypost.it/mcp/trypost
```
Leave the **Advanced settings** (Client ID / Client Secret) empty — TryPost supports Dynamic Client Registration, so Claude Desktop registers itself automatically.
Claude Desktop opens a browser window to TryPost asking you to sign in and authorize. Click **Allow** and return to Claude Desktop — the connector flips to **Connected**.
Start a new conversation and ask Claude to interact with your TryPost account:
```
List all my scheduled posts
```
Claude will use the TryPost tools to fetch your posts.
Claude Desktop's connector runs from Anthropic's cloud, not from your machine, so the MCP server has to be reachable over the public internet. The hosted endpoint `https://app.trypost.it/mcp/trypost` is. Self-hosted instances must be exposed publicly (or use a tunnel) for Claude Desktop to reach them.
## Example prompts
```
Show me all my connected social media accounts
```
```
Create a new signature called "Content Marketing"
```
```
What posts do I have scheduled for this week?
```
```
Create a label called "Urgent" with a red color
```
# Codex
Source: https://docs.trypost.it/ai/codex
Connect TryPost to OpenAI Codex CLI for AI-powered social media management
[OpenAI Codex](https://github.com/openai/codex) is OpenAI's CLI agent for software development. You can connect it to TryPost's MCP server to manage your social media scheduling with natural language.
## Prerequisites
* Codex CLI installed (`npm install -g @openai/codex`)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Edit `~/.codex/config.toml` and add the TryPost server:
```toml theme={null}
[mcp_servers.trypost]
type = "http"
url = "https://app.trypost.it/mcp/trypost"
```
No `Authorization` header — Codex discovers OAuth from the URL.
Run the OAuth flow so Codex caches the token:
```bash theme={null}
codex mcp login trypost
```
Codex opens your browser to TryPost; approve the request and return to the terminal.
Start Codex and ask:
```
List all my TryPost posts
```
Codex uses TOML for configuration (not JSON). For sandboxed environments where the default callback port isn't reachable, set `mcp_oauth_callback_port` or `mcp_oauth_callback_url` in `config.toml`.
## Example prompts
```
Create a new draft post for all my social accounts
```
```
Show me all my signatures
```
```
Create a label called "Campaign" with color #f59e0b
```
# Cursor
Source: https://docs.trypost.it/ai/cursor
Connect TryPost to Cursor for AI-powered social media management in your editor
[Cursor](https://cursor.com) is an AI-powered code editor. You can connect it to TryPost's MCP server to manage your social media scheduling directly from the editor.
## Prerequisites
* Cursor 1.0 or later (earlier versions don't support OAuth for remote MCP servers)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
In your project root, create or edit `.cursor/mcp.json`:
```json theme={null}
{
"mcpServers": {
"trypost": {
"url": "https://app.trypost.it/mcp/trypost"
}
}
}
```
No `Authorization` header is needed — Cursor detects OAuth from the URL and registers itself dynamically.
Open Cursor's MCP settings (or the AI chat) and click **Connect** next to **trypost**. Cursor opens a browser window for the TryPost sign-in. Approve the request and return to Cursor.
Open the AI chat panel and ask:
```
List all my TryPost social accounts
```
## Example prompts
Use Cursor's AI chat or inline editing to interact with TryPost:
```
Create a new post for all my connected accounts
```
```
Show me all my signatures
```
```
What workspace am I connected to?
```
# Gemini CLI
Source: https://docs.trypost.it/ai/gemini-cli
Connect TryPost to Gemini CLI for AI-powered social media management from the terminal
[Gemini CLI](https://github.com/google-gemini/gemini-cli) is Google's command-line AI assistant. You can connect it to TryPost's MCP server to manage your social media scheduling with natural language.
## Prerequisites
* Gemini CLI installed
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Edit your Gemini CLI settings at `~/.gemini/settings.json`:
```json theme={null}
{
"mcpServers": {
"trypost": {
"httpUrl": "https://app.trypost.it/mcp/trypost",
"oauth": {
"enabled": true
}
}
}
}
```
With `oauth.enabled`, Gemini CLI discovers TryPost's OAuth metadata from the URL and registers dynamically. For project-level configuration, create `.gemini/settings.json` in your project root instead.
Close and reopen Gemini CLI. The first TryPost call prompts you to sign in — Gemini CLI prints the authorize URL or opens your browser. Approve and return to the terminal.
Ask Gemini to interact with your TryPost account:
```
List all my TryPost posts
```
Gemini CLI uses `"httpUrl"` for HTTP-streaming servers (not `"url"`). The `"url"` field is used for SSE transport only.
## Example prompts
```
Create a new signature called "Growth Hacking"
```
```
What workspace am I connected to?
```
```
Show me all my labels
```
# GitHub Copilot
Source: https://docs.trypost.it/ai/github-copilot
Connect TryPost to GitHub Copilot in VS Code for AI-powered social media management
[GitHub Copilot](https://github.com/features/copilot) supports MCP servers in agent mode, allowing you to connect TryPost directly to your VS Code workflow.
## Prerequisites
* VS Code with the GitHub Copilot extension installed
* Copilot agent mode enabled
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup (IDE agent mode)
In your project root, create or edit `.vscode/mcp.json`:
```json theme={null}
{
"servers": {
"trypost": {
"type": "http",
"url": "https://app.trypost.it/mcp/trypost"
}
}
}
```
No `Authorization` header — Copilot uses VS Code's MCP layer, which discovers OAuth from the URL.
Reload the window to load the new MCP configuration.
Open Copilot Chat in agent mode. VS Code prompts you to sign in to TryPost. Approve the request in the browser and the server flips to **Connected**.
Ask Copilot:
```
List all my TryPost posts
```
VS Code uses `"servers"` as the top-level key (not `"mcpServers"`). This is different from most other MCP clients.
## Copilot coding agent (cloud)
GitHub's **coding agent** (the one that runs on GitHub's infrastructure to handle issues and PRs) does not yet support OAuth for remote MCP servers. TryPost's MCP endpoint requires OAuth (`mcp:use`) and **rejects** Personal Access Tokens, so cloud coding agent MCP is not supported today.
Use Copilot's **IDE agent mode** (above) for MCP, or call the [REST API](/api-reference/introduction) from Actions / scripts with an API key from **Settings → API Keys**.
## Global IDE configuration
To make TryPost available across all projects in your IDE, open the command palette and run **MCP: Open User Configuration** to add the server to your user-level settings.
## Example prompts
```
Create a new post scheduled for tomorrow
```
```
Show me all my signatures
```
```
What social accounts are connected to my workspace?
```
# Introduction
Source: https://docs.trypost.it/ai/introduction
Connect AI coding assistants to your TryPost account using the Model Context Protocol
TryPost provides a built-in [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that lets AI coding assistants manage your social media scheduling directly. Your AI assistant can create and publish posts, manage signatures and labels, fetch metrics, and more — all through natural language.
## What is MCP?
MCP is an open protocol that connects AI assistants to external tools and data sources. Instead of copying and pasting API calls, your AI assistant can interact with TryPost directly.
## Available tools
TryPost exposes a full set of MCP tools for end-to-end content workflows. See the [Tools reference](/ai/tools-reference) for parameters and examples.
| Category | Tools | Description |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| **Posts** | List, Get, Create, Update, Publish, Delete, Attach Media from URL, Request Media Upload, Attach Media from Upload, Preview, Get Metrics | Create, schedule, publish, and analyze posts across all connected platforms — including local-file uploads via signed URLs |
| **Platforms** | List Content Types | Discover valid `content_type` values and per-platform limits |
| **Signatures** | List, Create, Update, Delete | Manage reusable text blocks (hashtag sets, CTAs, link callouts) |
| **Labels** | List, Create, Update, Delete | Manage color-coded labels for organizing posts |
| **Social Accounts** | List, List Pinterest Boards, List Discord Channels, Toggle | View connected accounts, resolve Pinterest `board_id` / Discord `channel_id`, toggle active/inactive |
| **Workspace** | Get | Get current workspace details |
| **API Keys** | List, Create, Delete | Manage API keys |
## How it works
MCP access on **TryPost Cloud** requires an active trial or subscription. New accounts get a 7-day trial automatically. See [Plans & billing](/knowledge-base/plans-and-billing) for details. Self-hosted instances skip this check.
Use your assistant's MCP configuration to add a remote HTTP server pointing to `https://app.trypost.it/mcp/trypost`. Each [client guide](#next-steps) below has the exact command or settings file.
The first time the assistant calls a tool, TryPost opens a browser window asking you to sign in and authorize the connection. Click **Allow** and you're done — the assistant holds the OAuth token from then on, with automatic refresh.
Ask your assistant to manage your TryPost account. For example: "Create a new post for LinkedIn next Tuesday at 9am" or "List all my scheduled posts."
## Server URL
```
https://app.trypost.it/mcp/trypost
```
## Authentication
TryPost's MCP server uses **OAuth 2.1** with Dynamic Client Registration (DCR), PKCE, and the `mcp:use` scope. Modern MCP clients (Claude, Claude Code, Claude Desktop, ChatGPT, Perplexity, Cursor, Windsurf, VS Code, GitHub Copilot in IDE, Gemini CLI, OpenCode, OpenClaw, Codex) detect this automatically from the `.well-known` discovery endpoints — you only point them at the URL and the browser sign-in handles the rest.
Personal Access Tokens from **Settings → API Keys** authenticate the [REST API](/api-reference/introduction) only. They are **not** accepted on `/mcp/trypost`. MCP requires an OAuth grant with the `mcp:use` scope (browser sign-in from a supported client). Manage connected assistants under workspace **Settings → MCP**.
## Troubleshooting
Your OAuth grant is valid and the config is correct — the account behind it just doesn't have an active trial or subscription. Open [app.trypost.it](https://app.trypost.it), start a trial or pick a plan (7-day free trial), then reconnect the MCP server. Self-hosted instances skip this check.
The OAuth session expired and refresh failed, or the access token is missing/malformed/revoked. Re-run the assistant's connect flow to sign in again.
The request used a Personal Access Token (API key) or an OAuth token without the `mcp:use` scope. Remove any `Authorization: Bearer …` API key from the MCP config and connect with OAuth instead. For scripts and CI, call the [REST API](/api-reference/introduction) with an API key — not the MCP endpoint.
Run the connection probe your assistant exposes (for example `claude mcp list`). The HTTP status from the probe tells you which case you're in: `402` → see above; `401` → re-authenticate; `403` → use OAuth (not an API key); anything else → open an issue on [GitHub](https://github.com/trypostit/trypost/issues).
Some clients print the authorize URL to the terminal instead of launching the browser when running over SSH or inside a container. Copy the URL into a local browser, complete the flow, and the assistant will pick up the callback automatically. If the callback port is blocked, use a client that can finish OAuth locally, or drive TryPost through the [REST API](/api-reference/introduction) with an API key.
## Next steps
Claude on the web (claude.ai).
CLI assistant by Anthropic.
Desktop app by Anthropic.
OpenAI ChatGPT with MCP apps.
Perplexity chat and Computer.
AI-powered code editor.
Editor with Cascade AI.
Native MCP support in Copilot Chat.
Copilot agent mode.
CLI assistant by Google.
Open-source CLI assistant.
Personal AI assistant gateway.
CLI agent by OpenAI.
AI agent by Google.
# OpenClaw
Source: https://docs.trypost.it/ai/openclaw
Connect TryPost to OpenClaw for AI-powered social media management
[OpenClaw](https://openclaw.ai) is an open-source personal AI assistant that runs on your machine and talks across chat channels. You can connect it to TryPost's MCP server so the agent can manage your social media scheduling with natural language.
## Prerequisites
* OpenClaw installed ([getting started](https://docs.openclaw.ai/start/getting-started))
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Register the remote server with OAuth (Streamable HTTP):
```bash theme={null}
openclaw mcp add trypost \
--url https://app.trypost.it/mcp/trypost \
--transport streamable-http \
--auth oauth \
--oauth-scope mcp:use
```
This saves the definition under `mcp.servers` in `~/.openclaw/openclaw.json`. It does not connect or log in yet.
Start the login flow:
```bash theme={null}
openclaw mcp login trypost
```
OpenClaw prints an authorization URL and stores a temporary PKCE verifier. Open the URL in a browser, sign in to TryPost, and click **Allow**. After approval, pass the returned code back:
```bash theme={null}
openclaw mcp login trypost --code YOUR_CODE
```
Active agent or Gateway processes may still hold a stale tool list. Reload MCP clients in this CLI process (or restart the Gateway / **Save & Publish** from the Control UI), then probe:
```bash theme={null}
openclaw mcp reload
openclaw mcp doctor trypost --probe
```
Or:
```bash theme={null}
openclaw mcp probe trypost
```
Then ask your OpenClaw agent (Control UI, Telegram, Discord, etc.):
```
List all my scheduled posts
```
Manage servers in the Control UI at **Settings → MCP** (`openclaw dashboard`, path `/settings/mcp`). Use `openclaw mcp logout trypost` to clear stored OAuth credentials without removing the server definition. If `openclaw mcp status --verbose` reports `authorization-required`, run `openclaw mcp login trypost` again.
## Manual configuration
Equivalent config written by `openclaw mcp add`:
```json5 theme={null}
{
mcp: {
servers: {
trypost: {
url: "https://app.trypost.it/mcp/trypost",
transport: "streamable-http",
auth: "oauth",
oauth: {
scope: "mcp:use",
},
},
},
},
}
```
Save in `~/.openclaw/openclaw.json`, then run `openclaw mcp login trypost` and complete the `--code` step.
## Example prompts
```
Show me all my connected social media accounts
```
```
Create a draft LinkedIn post for tomorrow at 9am
```
```
What posts do I have scheduled for this week?
```
```
Create a label called "Campaign" with color #f59e0b
```
# OpenCode
Source: https://docs.trypost.it/ai/opencode
Connect TryPost to OpenCode for AI-powered social media management from the terminal
[OpenCode](https://opencode.ai) is an open-source AI coding assistant that runs in your terminal. You can connect it to TryPost's MCP server to manage your social media scheduling with natural language.
## Prerequisites
* OpenCode installed
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Edit `opencode.json` in your project root (or `~/.config/opencode/opencode.json` for global config):
```json theme={null}
{
"mcp": {
"trypost": {
"type": "remote",
"url": "https://app.trypost.it/mcp/trypost",
"enabled": true
}
}
}
```
No `Authorization` header — OpenCode discovers OAuth from the URL.
Run the OAuth flow explicitly so OpenCode caches the token:
```bash theme={null}
opencode mcp login trypost
```
OpenCode opens your browser to TryPost; approve the request and return to the terminal.
Inside OpenCode, ask:
```
List all my TryPost social accounts
```
If anything looks off, `opencode mcp debug trypost` prints the current token state.
OpenCode also supports `opencode mcp logout trypost` to revoke the cached token if you need to switch accounts.
## Example prompts
```
Create a post for tomorrow at 9am
```
```
Show me all my signatures
```
```
Create a label called "Newsletter" with color #8b5cf6
```
# Perplexity
Source: https://docs.trypost.it/ai/perplexity
Connect TryPost to Perplexity for AI-powered social media management
[Perplexity](https://www.perplexity.ai) supports custom remote MCP connectors on paid plans, so you can connect TryPost and manage social media scheduling from Perplexity chat and [Computer](https://www.perplexity.ai/changelog/what-we-shipped---march-13-2026) workflows.
This guide connects **TryPost into Perplexity** as a custom remote connector. Perplexity also ships its own search MCP for use *inside* other clients (Claude, Cursor, etc.) — that is a different product: [Perplexity MCP Server](https://docs.perplexity.ai/docs/getting-started/integrations/mcp-server).
## Prerequisites
* A Perplexity **Pro, Max, or Enterprise** account ([custom remote connectors](https://www.perplexity.ai/help-center/en/articles/13915507-adding-custom-remote-connectors) are not available on Free)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
In Perplexity, open **Account Settings → Connectors**. From **Computer**, you can also open **Connectors** in the left sidebar. Click **+ Custom connector**.
Choose **Remote** and fill in:
| Field | Value |
| ------------------ | ----------------------------------------------------------------------------------------- |
| **Name** | `TryPost` |
| **MCP Server URL** | `https://app.trypost.it/mcp/trypost` |
| **Authentication** | **OAuth 2.0** (leave Client ID / Secret empty — TryPost uses Dynamic Client Registration) |
| **Transport** | Streamable HTTP if offered (prefer over SSE; often auto-detected) |
Acknowledge the risk notice for custom connectors, then click **Add**.
Open the new connector card and complete OAuth. Sign in to TryPost and click **Allow**. The connector should show as connected with TryPost tools available.
Start a new conversation or Computer task, enable the **TryPost** connector for that thread if it is not already on, and ask:
```
List all my scheduled posts
```
Perplexity will call the TryPost tools to fetch your posts.
Perplexity reaches the MCP server from its cloud over HTTPS. The hosted endpoint `https://app.trypost.it/mcp/trypost` works out of the box. Self-hosted instances must be publicly reachable over HTTPS (or use a tunnel).
Choose **OAuth 2.0** for the connector. TryPost rejects Personal Access Tokens on `/mcp/trypost` — an “API Key” auth mode in Perplexity will not work against TryPost MCP.
On Enterprise, admins can share connectors organization-wide and control whether members may add their own. Members still authenticate individually for OAuth connectors unless your admin configures organization-wide auth.
## Example prompts
```
Show me all my connected social media accounts
```
```
Create a draft LinkedIn post for tomorrow at 9am
```
```
What posts do I have scheduled for this week?
```
```
Create a label called "Campaign" with color #f59e0b
```
# Tools reference
Source: https://docs.trypost.it/ai/tools-reference
Complete reference of all TryPost MCP tools available to your AI assistant
This page lists every tool available through the TryPost MCP server. Your AI assistant can call any of these tools to manage your TryPost account.
## Recommended workflow
To create and publish a post end-to-end:
1. **`list-social-accounts-tool`** — discover connected accounts
2. **`list-content-types-tool`** — discover valid `content_type` per platform with limits
3. **`list-pinterest-boards-tool`** *(if targeting Pinterest)* — resolve `platforms[].meta.board_id`
4. **`list-discord-channels-tool`** *(if targeting Discord)* — resolve `platforms[].meta.channel_id`
5. **`create-post-tool`** — create the draft with content + chosen platforms + `meta`
6. **`attach-media-from-url-tool`** *(optional)* — attach images or videos from public URLs
7. **`request-media-upload-tool`** + **`attach-media-from-upload-tool`** *(optional)* — upload a **local** file when no public URL exists
8. **`preview-post-tool`** *(optional)* — see how each platform will render before publishing
9. **`publish-post-tool`** — publish immediately or schedule for a future time
10. **`get-post-metrics-tool`** *(after published)* — fetch engagement metrics
## Posts
| Tool | Description |
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **list-posts-tool** | List posts for the current workspace, ordered by scheduled date (newest first). Supports filters: `status` (draft \| scheduled \| published \| failed), `search` (case-insensitive substring on content), `limit` (1–100, default 50). |
| **get-post-tool** | Get a specific post by ID with all its platform entries (including `meta`), media, and labels. |
| **create-post-tool** | Create a draft post. Accepts `content` (max 10 000), `scheduled_at` (ISO 8601; stored on the draft but **does not** schedule by itself — still `status=draft`), `label_ids[]`, and `platforms[]` — each entry has `social_account_id` + `content_type` and optional `meta`. **No `media[]` on this tool** — attach media with `attach-media-from-url-tool` or `request-media-upload-tool` + `attach-media-from-upload-tool`. Same `meta` contract as the REST API ([full reference](/api-reference/endpoint/update-post#per-platform-meta)). Cross-validates that the content\_type matches the account's platform. |
| **update-post-tool** | Update a draft: `post_id`, `content`, `scheduled_at`, `status` (`draft` \| `scheduled` only — immediate publish is `publish-post-tool`), `label_ids[]`, and `platforms[]`. Each platform entry uses **`id`** (the `post_platform` UUID from `get-post-tool` / `list-posts-tool`), optional `content_type`, optional `meta` (merged; send `null` to clear a key). Platforms **not listed are disabled**. **No `media[]`** — use the attach tools. Cannot edit a finalized post. |
| **publish-post-tool** | Publish a draft. Input: `post_id` (required) + optional `scheduled_at` (ISO 8601 future). **Omit `scheduled_at` → publish immediately**; provide it → schedule. Requires at least one enabled platform; validates **stored** required meta (Pinterest `board_id`, TikTok `privacy_level`, Discord `channel_id`) and that media matches each platform's content\_type. Marked `IsDestructive` so the AI confirms with you first. |
| **preview-post-tool** | Preview how the post will render on each enabled platform — applies platform-specific sanitization (length truncation, etc.) without publishing. |
| **attach-media-from-url-tool** | Download images, videos, or **PDF** documents from public URLs and attach them to a post. Input: `post_id` + `urls` (array of `{ "url": string, "alt"?: string }`, max **10**). Per-type size caps match config (defaults: image **10 MB**, video **1 GB**, document **100 MB**). Allowed MIME types: jpeg/png/gif/webp, mp4/MOV, `application/pdf`. Types are intersected with the enabled platforms on the post. Returns `post`, `attached_count`, `failed_urls`. Requires update permission on the post. |
| **request-media-upload-tool** | Issue a one-shot signed POST URL so the user can upload a **local** file (image, video, or PDF) to their workspace. No input — workspace is resolved from the MCP session. Requires create-post permission. Returns `upload_token`, `upload_url`, `expires_at` (ISO 8601; default TTL **15 minutes**, via `MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES`), `max_bytes` (overall ceiling = video cap, default **1 GB**), `max_bytes_by_type` (`image` / `video` / `document`, defaults 10 MB / 1 GB / 100 MB), and `field_name` (`media`). Hand the URL to the user (e.g. `curl -F media=@path/to/file '{upload_url}'`) or to the MCP client. Then call `attach-media-from-upload-tool`. Prefer `attach-media-from-url-tool` when a public URL already exists. |
| **attach-media-from-upload-tool** | Attach a Media uploaded via `request-media-upload-tool` to a post. Takes `post_id` + `upload_token`, optional `alt` (image alt text). The Media is resolved by the token within the current workspace, then appended to the post. Rejects if the media type isn't accepted by any enabled platform. |
| **delete-post-tool** | Delete a post permanently. Input: `post_id`. Marked `IsDestructive`. |
| **get-post-metrics-tool** | Fetch engagement metrics (likes, comments, shares, etc.) for a published post across every platform. Returns `unsupported` entries for platforms that don't expose post-level metrics. |
## Platforms (read-only)
| Tool | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **list-content-types-tool** | Returns `{ platforms: [...] }` — same catalog as [`GET /content-types`](/api-reference/endpoint/list-content-types). Each platform has `max_content_length`, `recommended_content_length`, `allowed_media_types`, `default_content_type`, and `content_types[]` rows with `max_media_count`, `min_media_count`, `requires_media`, `accept_images` / `accept_videos` / `accept_documents` / `accepts_gif`, `forbids_mixed_media`, and per-type byte caps. **20** content types total — there is no `linkedin_carousel` / `instagram_carousel`. Use before `create-post-tool` / `update-post-tool`. |
## Signatures
| Tool | Description |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **list-signatures-tool** | List all signatures for the current workspace. Signatures are reusable text blocks (hashtags, links, custom text) that can be appended to posts. |
| **create-signature-tool** | Create a signature. Input: `name` (required, max 255), `content` (required). Requires create-post permission. |
| **update-signature-tool** | Update a signature. Input: `signature_id`, `name`, `content` (all required). |
| **delete-signature-tool** | Delete a signature by `signature_id`. Marked `IsDestructive`. |
## Labels
| Tool | Description |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| **list-labels-tool** | List all labels for the current workspace. Each label has a name and hex color. |
| **create-label-tool** | Create a label. Input: `name` (required, max 255), `color` (required hex `#RRGGBB`, e.g. `#FF5733`). Requires create-post permission. |
| **update-label-tool** | Update a label. Input: `label_id`, `name`, `color` (all required). |
| **delete-label-tool** | Delete a label by `label_id`. Detaches it from all posts that referenced it. Marked `IsDestructive`. |
## Social accounts
| Tool | Description |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **list-social-accounts-tool** | List all connected social accounts for the current workspace (LinkedIn, X, Instagram, etc.). Returns id, platform, display\_name, username, is\_active, status. |
| **list-pinterest-boards-tool** | List boards for a Pinterest account (`account_id`). Returns `{ boards: [{ id, name }], truncated }`. Use a board `id` as `platforms[].meta.board_id` (**required to publish** Pinterest). Requires **create-post** permission (Owner / Admin / Member) — **Viewers** get `Not authorized to manage posts.` |
| **list-discord-channels-tool** | List text/announcement channels the bot can post to for a Discord account (`account_id`). Returns `{ channels: [{ id, name }] }`. Use a channel `id` as `platforms[].meta.channel_id` (**required to publish** Discord). Same permission as boards: create-post only — **Viewers** cannot call it. |
| **toggle-social-account-tool** | Toggle a social account active/inactive. Input: `account_id`. When inactive, the account is skipped during publishing. Requires manage-accounts (Owner / Admin). |
## Workspace
| Tool | Description |
| ---------------------- | --------------------------------------------------------- |
| **get-workspace-tool** | Get the current workspace details (id, name, timestamps). |
## API keys
| Tool | Description |
| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **list-api-keys-tool** | List Personal Access Tokens for the current workspace. Returns metadata only — the secret token value is shown only once at creation. OAuth-flow tokens (e.g. ChatGPT MCP sessions) are excluded. Requires **manageTeam** (Owner / Admin). |
| **create-api-key-tool** | Create a Personal Access Token. Input: `name` (required, max 255), optional `expires_at` (YYYY-MM-DD or ISO 8601; omit for non-expiring). Requires **manageTeam** (Owner / Admin). Response includes key metadata plus **`token`** (plain secret once) — note: REST create returns `plain_token`; MCP returns `token`. Store it immediately. |
| **delete-api-key-tool** | Revoke a Personal Access Token. Input: `api_key_id` (required). The current OAuth session token cannot be revoked through this tool. Marked `IsDestructive`. Requires manageTeam. |
## Per-platform `meta` (MCP)
`platforms[].meta` uses the **same keys** as the REST API. Full tables, limits, and examples: [Per-platform meta](/api-reference/endpoint/update-post#per-platform-meta).
Quick checklist for agents:
| Platform | Set in `meta` | Required before publish |
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
| Instagram / Facebook | `aspect_ratio` — exact strings `"1:1"`, `"4:5"`, `"16:9"`, `"original"` | No |
| LinkedIn / LinkedIn Page | `document_title` (PDF document posts, max 300) | No |
| TikTok | `privacy_level` — exact strings `"PUBLIC_TO_EVERYONE"`, `"MUTUAL_FOLLOW_FRIENDS"`, `"FOLLOWER_OF_CREATOR"`, `"SELF_ONLY"`; plus booleans `allow_comments`, `allow_duet`, `allow_stitch`, `auto_add_music`, `is_aigc`, `disclose`, `brand_content_toggle`, `brand_organic_toggle` | `privacy_level` |
| Pinterest | `board_id`, optional `title` (max 100), `link` (http/https). Pin description = post `content`. | `board_id` |
| Discord | `channel_id`, optional `channel_name`, `mentions` (`[{token,label}]`), `embeds` (max 10) | `channel_id` |
Enum-like fields are **JSON strings**. Send `"privacy_level": "PUBLIC_TO_EVERYONE"`, not an integer or a renamed value. Unknown meta keys are dropped.
## Notes
* **Workspace scoping** — every tool operates on the user's currently-selected workspace. To act on a different workspace, switch it in the TryPost UI (or, for personal API tokens, create a new key in that workspace).
* **Roles** — **Viewers** are read-only. They cannot create/update/publish posts, and they also cannot call compose helpers that gate on create-post permission — including `list-pinterest-boards-tool` and `list-discord-channels-tool` (error: `Not authorized to manage posts.`). Owner, Admin, and Member can. See [Team](/knowledge-base/team).
* **Cross validation** — `create-post-tool` and `update-post-tool` reject mismatched combos (e.g. `content_type=x_post` on a LinkedIn account). Inactive social accounts are also rejected at validation time.
* **Destructive tools** — operations that delete data or publish to the world (`publish-post-tool`, `delete-*`) are marked `IsDestructive`, which lets the AI client (ChatGPT, Claude Desktop, etc.) prompt you for confirmation before executing.
* **Local-file uploads** — `request-media-upload-tool` returns a single-use signed URL (default TTL 15 minutes). `max_bytes` is the video ceiling (default 1 GB); per-type caps are in `max_bytes_by_type` (image / video / document). POST the file as multipart field `media`, then call `attach-media-from-upload-tool` with the same `upload_token` (optional `alt` for images). See [`POST /uploads/{token}`](/api-reference/endpoint/request-media-upload).
* **Media alt text** — image accessibility text lives on `media[].meta.alt_text`, not on `platforms[].meta`. Via MCP, set it with `attach-media-from-url-tool` (`urls[].alt`) or `attach-media-from-upload-tool` (`alt`). The REST create/update endpoints also accept `media[].meta.alt_text` when hosting from URL.
* **No inline `media[]` on MCP create/update** — unlike REST `POST`/`PUT /posts`, the MCP create/update tools do not accept a `media` array. Always use the attach tools after create.
# VS Code
Source: https://docs.trypost.it/ai/vs-code
Connect TryPost to VS Code's native MCP support in Copilot Chat
Visual Studio Code supports MCP servers natively (Copilot Chat in agent mode, plus any MCP-compatible extension). You can connect TryPost in a few clicks.
## Prerequisites
* VS Code 1.101 or later (earlier versions don't support OAuth for remote MCP servers)
* GitHub Copilot extension (or another MCP-compatible extension)
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
In your project root, create or edit `.vscode/mcp.json`:
```json theme={null}
{
"servers": {
"trypost": {
"type": "http",
"url": "https://app.trypost.it/mcp/trypost"
}
}
}
```
No `Authorization` header — VS Code discovers OAuth from the URL.
Reload the window so VS Code picks up the new configuration.
Open the **Accounts** menu (bottom-left gear) or the Copilot Chat panel — VS Code prompts you to sign in to TryPost. Approve in the browser and the server flips to **Connected**.
Open Copilot Chat in agent mode and ask:
```
List all my TryPost social accounts
```
VS Code uses `"servers"` as the top-level key (not `"mcpServers"`). This is different from most other MCP clients.
## Global configuration
To make TryPost available across all projects, open the command palette and run **MCP: Open User Configuration** to add the server to your user-level settings.
# Windsurf
Source: https://docs.trypost.it/ai/windsurf
Connect TryPost to Windsurf for AI-powered social media management with Cascade
[Windsurf](https://windsurf.com) is an AI-powered editor with Cascade, a deep context-aware AI assistant. You can connect it to TryPost's MCP server to manage your social media scheduling.
## Prerequisites
* Windsurf editor installed
* A TryPost Cloud account with an active trial or subscription ([start a 7-day free trial](/knowledge-base/plans-and-billing))
## Setup
Edit `~/.codeium/windsurf/mcp_config.json`:
```json theme={null}
{
"mcpServers": {
"trypost": {
"serverUrl": "https://app.trypost.it/mcp/trypost"
}
}
}
```
No `Authorization` header — Windsurf discovers OAuth from the URL.
Reload the window so Windsurf picks up the new configuration.
The first time Cascade calls a TryPost tool, Windsurf opens a browser window for the TryPost sign-in. Approve the request and Cascade continues automatically.
Open Cascade and ask:
```
List all my TryPost posts
```
## Example prompts
Use Cascade to interact with TryPost:
```
Create a signature called "Product Launch" with #launch #newproduct #announcement
```
```
Show me all my connected social accounts
```
```
Create a label called "Blog" with color #3b82f6
```
# Attach media from URLs
Source: https://docs.trypost.it/api-reference/endpoint/attach-media-from-url
POST /posts/{post}/media/from-url
Download images, videos, or PDF documents from public URLs and attach them to a post.
For uploading a file directly from your machine instead of fetching a URL, use [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media). MCP agents use the same contract via [`attach-media-from-url-tool`](/ai/tools-reference#posts).
## Path parameters
The UUID of the post to attach media to.
## Request
Media to download. **Max 10 items per call.** Each item is an object (not a bare string):
Public `http`/`https` URL. Validated with `active_url` (must resolve).
Optional accessibility alt text (max 2000 characters). Applied to **images** only — ignored for video/PDF.
Per-type size caps (from `trypost.media.max_size_mb` / env):
| Type | Default max | MIME allow-list |
| ------------ | ----------- | ---------------------------------------------------- |
| **image** | **10 MB** | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |
| **video** | **1 GB** | `video/mp4`, `video/quicktime` (MOV) |
| **document** | **100 MB** | `application/pdf` |
URLs are downloaded server-side and stored as Media records on the workspace.
## Behaviour
* The post must belong to the caller's current workspace; otherwise the response is `404`.
* Allowed media types are **intersected with the platforms enabled on the post**. If an enabled platform doesn't support the file type (e.g. video on a platform that's image-only), that file is rejected and listed in `failed_urls`.
* Successful attachments are appended to the post's `media[]` array (image `alt` becomes `media[].meta.alt_text`).
## Response
Returns an object with three fields:
The updated post — same shape as [`GET /posts/{post}`](/api-reference/endpoint/get-post), with newly-attached items now in the `media[]` array.
Number of URLs that were successfully downloaded and attached.
URLs that were rejected (unreachable, wrong MIME type, too large, or not allowed by any enabled platform). Each entry is the original URL string.
```bash theme={null}
curl -X POST https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/media/from-url \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
{ "url": "https://cdn.example.com/photo.jpg", "alt": "Product hero shot" },
{ "url": "https://cdn.example.com/deck.pdf" }
]
}'
```
```json theme={null}
{
"post": {
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "Launch day!",
"media": [
{
"id": "01HX1A2B3C4D5E6F7G8H9I0J1K",
"path": "medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"url": "https://media.trypost.it/medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"type": "image",
"mime_type": "image/jpeg",
"original_filename": "photo.jpg",
"meta": { "alt_text": "Product hero shot" }
}
],
"status": "draft",
"scheduled_at": null,
"published_at": null,
"platforms": [],
"labels": [],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:30:00"
},
"attached_count": 1,
"failed_urls": [
"https://cdn.example.com/deck.pdf"
]
}
```
# Create API key
Source: https://docs.trypost.it/api-reference/endpoint/create-api-key
POST /api-keys
Create a new API key. Returns the plain text token which is only shown once.
## Request
Requires **manage team** permission on the workspace (Owner / Admin). Members and Viewers get `403`.
The API key name. Maximum 255 characters.
Optional expiration. Any date string the server can parse (e.g. `2026-12-31`, `2026-12-31T15:00:00Z`), must be **today or later** (`after_or_equal:today`). Date-only values are stored as end of that day. Omit for a non-expiring key.
## Response
Returns `201` with the new key plus the plain token. The plain token is shown only here — store it immediately.
Same shape as one entry in [`GET /api-keys`](/api-reference/endpoint/list-api-keys) — `id`, `name`, `last_used_at`, `expires_at`, `created_at` (all timestamps ISO 8601).
The opaque bearer token to send in `Authorization: Bearer …`. Never returned again.
Copy the `plain_token` immediately after creation. TryPost stores a hash of your API key, not the key itself. If you lose it, you'll need to create a new one.
```bash theme={null}
curl -X POST https://app.trypost.it/api/api-keys \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD Pipeline",
"expires_at": "2025-12-31"
}'
```
```json theme={null}
{
"token": {
"id": "a7b8c9d0-e1f2-3a4b-5c6d-7e8f9a0b1c2d",
"name": "CI/CD Pipeline",
"last_used_at": null,
"expires_at": "2025-12-31T00:00:00+00:00",
"created_at": "2025-01-14T12:00:00+00:00"
},
"plain_token": "9f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a..."
}
```
# Create label
Source: https://docs.trypost.it/api-reference/endpoint/create-label
POST /labels
Create a new label with a name and color.
## Request
The label name. Maximum 255 characters.
Hex color code (e.g., `#FF5733`). Must match the format `#RRGGBB`.
## Response
Returns `201` with the created label. Same shape as one entry in [`GET /labels`](/api-reference/endpoint/list-labels).
```bash theme={null}
curl -X POST https://app.trypost.it/api/labels \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Launch",
"color": "#4f46e5"
}'
```
```json theme={null}
{
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"name": "Product Launch",
"color": "#4f46e5",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 08:00:00"
}
```
# Create post
Source: https://docs.trypost.it/api-reference/endpoint/create-post
POST /posts
Create a new draft post targeting one or more social accounts. Returns the created post with status 201.
Creates a `draft`. To schedule or publish, follow up with [`PUT /posts/{post}`](/api-reference/endpoint/update-post) using `status=scheduled` or `status=publishing`.
Per-platform settings go in `platforms[].meta` and are accepted here on create. A few are **required to publish** — Pinterest `board_id`, TikTok `privacy_level` (string enum values like `"PUBLIC_TO_EVERYONE"`), Discord `channel_id` — but they're only enforced when you move the post to `scheduled`/`publishing`, not on this draft create. Full key list, limits, and examples: [`PUT /posts/{post}` → Per-platform meta](/api-reference/endpoint/update-post#per-platform-meta).
## Request
At least one platform entry (`min:1`). Each entry pins this post to a connected social account and a `content_type`.
UUID of an active social account in the current workspace. Inactive or external accounts are rejected.
Format for this platform — must match the social account's platform. Discover the live catalog with [`GET /content-types`](/api-reference/endpoint/list-content-types).
Allowed values today: `linkedin_post`, `linkedin_page_post`, `x_post`, `facebook_post`, `facebook_reel`, `facebook_story`, `instagram_feed`, `instagram_reel`, `instagram_story`, `tiktok_video`, `tiktok_photo`, `youtube_short`, `threads_post`, `pinterest_pin`, `pinterest_video_pin`, `pinterest_carousel`, `bluesky_post`, `mastodon_post`, `telegram_post`, `discord_message`.
There is **no** `linkedin_carousel` / `instagram_carousel` — use `linkedin_post` + PDF for LinkedIn documents, and `instagram_feed` with multiple images for Instagram carousels.
Optional per-platform settings. Only documented keys are stored; unknown keys are dropped.
Summary (see [`PUT /posts/{post}`](/api-reference/endpoint/update-post#per-platform-meta) for the full contract):
* **Instagram / Facebook** — `aspect_ratio` (`"1:1"` | `"4:5"` | `"16:9"` | `"original"`)
* **LinkedIn / LinkedIn Page** — `document_title` (max 300; PDF document posts)
* **TikTok** — `privacy_level` (**required to publish**; exact strings `"PUBLIC_TO_EVERYONE"`, `"MUTUAL_FOLLOW_FRIENDS"`, `"FOLLOWER_OF_CREATOR"`, `"SELF_ONLY"`) plus booleans `allow_comments`, `allow_duet`, `allow_stitch`, `auto_add_music`, `is_aigc`, `disclose`, `brand_content_toggle`, `brand_organic_toggle`
* **Pinterest** — `board_id` (**required to publish** — [`GET /social-accounts/{account}/boards`](/api-reference/endpoint/list-pinterest-boards)), `title` (max 100), `link` (http/https URL, max 2048). Pin description = post `content`.
* **Discord** — `channel_id` (**required to publish** — [`GET /social-accounts/{account}/channels`](/api-reference/endpoint/list-discord-channels)), `channel_name`, `mentions` (`[{token, label}]`), `embeds` (up to 10)
The post body shared across all platforms. Up to **10 000** characters at the API level (per-platform hard caps are enforced separately — see [`GET /content-types`](/api-reference/endpoint/list-content-types)). Defaults to empty. For Pinterest, this is the pin description.
Optional media to attach on create. Each item must include a public `url` — the API downloads and hosts it.
Public `http`/`https` URL of the file (max 2048).
Optional. `meta.alt_text` (string, max **2000**) for image accessibility. Not the same as `platforms[].meta`.
Optional — usually omitted on create (assigned after host).
Optional — usually omitted on create.
Optional hint: `image`, `video`, or `document`.
Optional MIME hint.
Optional display filename (max 500).
Optional size in bytes.
Optional: `ai`, `unsplash`, or `giphy`.
Optional opaque source metadata.
Prefer dedicated endpoints when practical: [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) (file) or [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url).
ISO 8601 datetime in the future (e.g. `2026-05-10T15:30:00Z`). Stored on the draft so a follow-up `PUT` with `status=scheduled` doesn't have to repeat it. **If omitted, the draft has `scheduled_at: null`** (no default time).
Workspace label UUIDs to attach. Must belong to the same workspace as the API key.
## Response
Returns `201` with the created post (including `platforms[].meta`). Response shape matches [`GET /posts/{post}`](/api-reference/endpoint/get-post). Labels may not be eager-loaded on create — call `GET` if you need them immediately after setting `label_ids`.
```bash theme={null}
curl -X POST https://app.trypost.it/api/posts \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "New pin from the TryPost API",
"platforms": [
{
"social_account_id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"content_type": "pinterest_pin",
"meta": {
"board_id": "1234567890123456789",
"title": "Summer lookbook",
"link": "https://example.com/shop/summer"
}
}
],
"scheduled_at": "2026-05-10T15:30:00Z"
}'
```
```json theme={null}
{
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "New pin from the TryPost API",
"media": [],
"status": "draft",
"scheduled_at": "2026-05-10 15:30:00",
"published_at": null,
"platforms": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "pinterest",
"content_type": "pinterest_pin",
"meta": {
"board_id": "1234567890123456789",
"title": "Summer lookbook",
"link": "https://example.com/shop/summer"
},
"status": "pending",
"enabled": true,
"platform_url": null,
"published_at": null,
"error_message": null,
"display_name": "Acme Brand",
"display_username": "acme",
"display_avatar": "https://i.pinimg.com/.../avatar.jpg",
"social_account": {
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "pinterest",
"display_name": "Acme Brand",
"username": "acme",
"is_active": true,
"status": "connected"
}
}
],
"labels": [],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:00:00"
}
```
# Create signature
Source: https://docs.trypost.it/api-reference/endpoint/create-signature
POST /signatures
Create a new signature.
Signatures are reusable text blocks (hashtags, links, custom text) that can be appended to posts.
## Request
The signature name. Maximum 255 characters.
The signature content (e.g. `#tech #ai #startup`, a link, a tagline — anything you want to append to posts).
## Response
Returns `201` with the created signature. Same shape as one entry in [`GET /signatures`](/api-reference/endpoint/list-signatures).
```bash theme={null}
curl -X POST https://app.trypost.it/api/signatures \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Tech Stack",
"content": "#tech #ai #startup #saas"
}'
```
```json theme={null}
{
"id": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"name": "Tech Stack",
"content": "#tech #ai #startup #saas",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 08:00:00"
}
```
# Delete API key
Source: https://docs.trypost.it/api-reference/endpoint/delete-api-key
DELETE /api-keys/{apiToken}
Delete an API key by ID. This action is immediate and irreversible.
## Request
The API key ID to delete.
## Response
Returns `204 No Content` on success. Any application still using this key will immediately start receiving `401` errors.
```bash theme={null}
curl -X DELETE https://app.trypost.it/api/api-keys/f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Delete label
Source: https://docs.trypost.it/api-reference/endpoint/delete-label
DELETE /labels/{label}
Delete a label by ID.
## Request
The label ID to delete.
## Response
Returns `204 No Content` on success.
```bash theme={null}
curl -X DELETE https://app.trypost.it/api/labels/c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Delete post
Source: https://docs.trypost.it/api-reference/endpoint/delete-post
DELETE /posts/{post}
Delete a post by ID.
## Request
The post ID to delete.
## Response
Returns `204 No Content` on success.
```bash theme={null}
curl -X DELETE https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Delete signature
Source: https://docs.trypost.it/api-reference/endpoint/delete-signature
DELETE /signatures/{signature}
Delete a signature by ID.
## Request
The signature ID to delete.
## Response
Returns `204 No Content` on success.
```bash theme={null}
curl -X DELETE https://app.trypost.it/api/signatures/e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Get post
Source: https://docs.trypost.it/api-reference/endpoint/get-post
GET /posts/{post}
Get a specific post by ID with all its platform entries, media, and labels.
## Request
The post UUID.
## Response
Returns the full post. Response shape is identical to one entry in [`GET /posts`](/api-reference/endpoint/list-posts) — see that endpoint for full field documentation (including `platforms[].meta`).
```bash theme={null}
curl -X GET https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "Excited to announce our new feature!",
"media": [
{
"id": "01HX1A2B3C4D5E6F7G8H9I0J1K",
"path": "medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"url": "https://media.trypost.it/medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"type": "image",
"mime_type": "image/jpeg",
"original_filename": "photo.jpg"
}
],
"status": "draft",
"scheduled_at": "2025-01-15 10:00:00",
"published_at": null,
"platforms": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "linkedin",
"content_type": "linkedin_post",
"meta": {},
"status": "pending",
"enabled": true,
"platform_url": null,
"published_at": null,
"error_message": null,
"display_name": "John Doe",
"display_username": "johndoe",
"display_avatar": "https://media.licdn.com/.../avatar.jpg",
"social_account": {
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "linkedin",
"display_name": "John Doe",
"username": "johndoe",
"is_active": true,
"status": "connected"
}
}
],
"labels": [
{
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"name": "Product Launch",
"color": "#4f46e5",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 08:00:00"
}
],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:00:00"
}
```
# Get post metrics
Source: https://docs.trypost.it/api-reference/endpoint/get-post-metrics
GET /posts/{post}/metrics
Fetch engagement metrics (likes, comments, shares, etc.) for a published post across every platform.
## Path parameters
The UUID of the post.
## Behaviour
* The post must belong to the caller's current workspace; otherwise the response is `404`.
* Returns one entry per **enabled** platform on the post.
* A platform entry returns `metrics: { unsupported: true, reason: "not_published" }` when the platform hasn't been published yet, and `{ unsupported: true, reason: "platform_not_supported" }` for platforms that don't expose post-level metrics. Today that's **TikTok** and **personal LinkedIn** (`linkedin`); LinkedIn Pages (`linkedin-page`) and every other platform return real metrics.
* **Telegram and Discord metrics use a different shape.** Neither Bot API exposes likes, shares, or views, so each metric entry carries an extra `kind` field: `"subscribers"` for the Telegram channel's member count (and the Discord server's member count), `"reaction"` for each reaction type (where `label` is the emoji), and — for Discord only — `"comments"` for the post thread's reply count. Standard platforms omit `kind` and return likes/comments/shares.
* Successful metrics responses are cached for 5 minutes per `post_platform`.
## Response
```json theme={null}
{
"post_id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"platforms": [
{
"post_platform_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "linkedin",
"status": "published",
"platform_post_id": "urn:li:share:7263...",
"platform_url": "https://www.linkedin.com/feed/update/urn:li:share:7263...",
"metrics": [
{ "label": "Likes", "value": 42 },
{ "label": "Comments", "value": 7 }
]
},
{
"post_platform_id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "x",
"status": "pending",
"platform_post_id": null,
"platform_url": null,
"metrics": {
"unsupported": true,
"reason": "not_published"
}
},
{
"post_platform_id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"platform": "telegram",
"status": "published",
"platform_post_id": "42",
"platform_url": "https://t.me/c/1234567890/42",
"metrics": [
{ "label": "Subscribers", "value": 1280, "kind": "subscribers" },
{ "label": "👍", "value": 34, "kind": "reaction" },
{ "label": "🔥", "value": 12, "kind": "reaction" }
]
}
]
}
```
```bash theme={null}
curl https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/metrics \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Preview post per platform
Source: https://docs.trypost.it/api-reference/endpoint/get-post-preview
GET /posts/{post}/preview
Preview how a post will render on each enabled platform — applies platform-specific HTML sanitization and shows length stats against each platform's max.
## Path parameters
The UUID of the post.
## Behaviour
* The post must belong to the caller's current workspace; otherwise the response is `404`.
* Returns the original content alongside per-platform sanitized versions and length statistics.
* `original_content` is the plain text stored on the post (the editor saves plain text, not HTML).
* `sanitized_content` is the platform-specific rendering of that text. For most platforms this is identical to the original. Exceptions:
* **LinkedIn / LinkedIn Page** — Unicode bold/underline substitution if the source contained ``/``/`` markers (used by the AI generator).
* **Mastodon** — a small allow-list of inline HTML is preserved (`p`, `strong`, `em`, `a`, `br`, `span`).
* **Telegram** — rendered as Telegram's HTML allow-list (`b`, `i`, `u`, `s`, `a`, `code`, `pre`); block elements become line breaks and `@mentions` are kept.
* `truncated` is `true` only when sanitization actually shortened the byte count (e.g. an HTML entity got decoded). It is **not** a hard-cap signal — over-length content is rejected at publish time, never silently truncated. To detect over-limit content, compare `sanitized_length` against `max_content_length` yourself.
## Response
```json theme={null}
{
"post_id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"original_content": "Launch day!\n\nExcited to share what we built.",
"original_length": 44,
"platforms": [
{
"post_platform_id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "x",
"content_type": "x_post",
"sanitized_content": "Launch day!\n\nExcited to share what we built.",
"sanitized_length": 44,
"max_content_length": 280,
"truncated": false
},
{
"post_platform_id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "linkedin",
"content_type": "linkedin_post",
"sanitized_content": "Launch day!\n\nExcited to share what we built.",
"sanitized_length": 44,
"max_content_length": 3000,
"truncated": false
}
]
}
```
```bash theme={null}
curl https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/preview \
-H "Authorization: Bearer YOUR_API_KEY"
```
# Get workspace
Source: https://docs.trypost.it/api-reference/endpoint/get-workspace
GET /workspace
Get the current workspace details.
## Response
Returns the workspace bound to the API key.
The workspace UUID.
The workspace name.
Creation timestamp in `Y-m-d H:i:s` format.
Last update timestamp in `Y-m-d H:i:s` format.
```bash theme={null}
curl -X GET https://app.trypost.it/api/workspace \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"name": "My Brand",
"created_at": "2025-01-01 00:00:00",
"updated_at": "2025-01-01 00:00:00"
}
```
# List API keys
Source: https://docs.trypost.it/api-reference/endpoint/list-api-keys
GET /api-keys
List your Personal Access Tokens scoped to the current workspace, latest first. Returns a plain array (no pagination).
## Response
Plain array of API key objects. The plain token value is only shown once at creation — these objects are metadata only.
The list is scoped to **the calling user in the current workspace**. Tokens created by other workspace members are not returned, even though they may have access to the same workspace.
API key UUID.
API key label.
ISO 8601 timestamp of the last time this key authenticated a request. `null` if never used.
ISO 8601 expiration timestamp. `null` if the key does not expire.
ISO 8601 creation timestamp.
```bash theme={null}
curl -X GET https://app.trypost.it/api/api-keys \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
[
{
"id": "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c",
"name": "Production",
"last_used_at": "2025-01-14T12:00:00+00:00",
"expires_at": null,
"created_at": "2025-01-01T00:00:00+00:00"
}
]
```
# List content types
Source: https://docs.trypost.it/api-reference/endpoint/list-content-types
GET /content-types
List the valid content_types per platform plus their constraints (length budgets, allowed media types, default content_type).
## Why this endpoint
`POST /api/posts` and `PUT /api/posts/{id}` accept a `platforms[]` array where each entry has a `social_account_id` (create) or `id` (update) and a `content_type`. Valid `content_type` values depend on the platform.
This endpoint exposes the live catalog so clients and agents can build correct payloads without hard-coding the matrix. Prefer this over any static list in docs.
## Response
Returns `{ "platforms": [ ... ] }` — one object per social platform.
### Platform object
| Field | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `platform` | Identifier used in `social_accounts.platform` (e.g. `linkedin`, `linkedin-page`, `x`, `instagram`). |
| `label` | Human-readable name. |
| `max_content_length` | Hard caption cap enforced at schedule/publish. Exceeding it rejects — it is not truncated. |
| `recommended_content_length` | Soft target for AI generation. |
| `allowed_media_types` | Platform-level media kinds (`image`, `video`, `document`). Prefer the per–content-type `accept_*` flags for what a specific format allows. |
| `default_content_type` | Value to use when the caller does not pick one. |
| `content_types` | Array of format rows (see below). |
### `content_types[]` row
| Field | Description |
| ------------------------ | -------------------------------------------------------- |
| `value` | Pass as `platforms[].content_type`. |
| `label` | Short UI label. |
| `description` | Longer description. |
| `max_media_count` | Max media items for this format. |
| `min_media_count` | Minimum media items (0 = optional). |
| `requires_media` | Whether at least one media item is required. |
| `accept_images` | Whether images are allowed. |
| `accept_videos` | Whether videos are allowed. |
| `accept_documents` | Whether PDFs are allowed (e.g. LinkedIn document posts). |
| `accepts_gif` | Whether GIF is accepted as an image. |
| `forbids_mixed_media` | When `true`, do not mix images and videos in one post. |
| `max_video_duration_sec` | Max video length in seconds, or `null`. |
| `max_image_bytes` | Max image size in bytes, or `null`. |
| `max_video_bytes` | Max video size in bytes, or `null`. |
| `max_document_bytes` | Max PDF size in bytes, or `null`. |
### Current `content_type` values (20)
| Platform | Values |
| ------------------------------ | ------------------------------------------------------------ |
| LinkedIn | `linkedin_post` |
| LinkedIn Page | `linkedin_page_post` |
| X | `x_post` |
| Facebook | `facebook_post`, `facebook_reel`, `facebook_story` |
| Instagram / Instagram Facebook | `instagram_feed`, `instagram_reel`, `instagram_story` |
| TikTok | `tiktok_video`, `tiktok_photo` |
| YouTube | `youtube_short` |
| Threads | `threads_post` |
| Pinterest | `pinterest_pin`, `pinterest_video_pin`, `pinterest_carousel` |
| Bluesky | `bluesky_post` |
| Mastodon | `mastodon_post` |
| Telegram | `telegram_post` |
| Discord | `discord_message` |
There is **no** `linkedin_carousel`, `linkedin_page_carousel`, or `instagram_carousel` content type. LinkedIn PDF/document posts use `linkedin_post` / `linkedin_page_post` with a PDF + optional `meta.document_title`. Instagram multi-image posts use `instagram_feed` with multiple images.
```json theme={null}
{
"platforms": [
{
"platform": "linkedin",
"label": "LinkedIn",
"max_content_length": 3000,
"recommended_content_length": 1200,
"allowed_media_types": ["image", "video", "document"],
"default_content_type": "linkedin_post",
"content_types": [
{
"value": "linkedin_post",
"label": "Post",
"description": "A standard LinkedIn post.",
"max_media_count": 10,
"min_media_count": 0,
"requires_media": false,
"accept_images": true,
"accept_videos": true,
"accept_documents": true,
"accepts_gif": false,
"forbids_mixed_media": true,
"max_video_duration_sec": 600,
"max_image_bytes": 5242880,
"max_video_bytes": 1073741824,
"max_document_bytes": 104857600
}
]
}
]
}
```
```bash theme={null}
curl https://app.trypost.it/api/content-types \
-H "Authorization: Bearer YOUR_API_KEY"
```
# List Discord channels
Source: https://docs.trypost.it/api-reference/endpoint/list-discord-channels
GET /social-accounts/{account}/channels
List text and announcement channels the bot can post to for a connected Discord server. Use a returned channel id as platforms[].meta.channel_id.
Returns Discord channels available for publishing for this connected server (guild).
## Request
UUID of a connected Discord social account in the current workspace. Non-Discord accounts return `422`.
## Response
Channels the TryPost bot can post in (text and announcement channels with View Channel + Send Messages). Each item:
Channel ID — send this as `platforms[].meta.channel_id` (JSON string).
Channel name (without the `#`).
Returns `502` if Discord is unavailable upstream, and `422` if the account is not Discord.
```bash theme={null}
curl -X GET https://app.trypost.it/api/social-accounts/b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e/channels \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"channels": [
{ "id": "444555666777888999", "name": "announcements" },
{ "id": "111222333444555666", "name": "general" }
]
}
```
## Tips
* `channel_id` is **required to publish** a Discord message (`scheduled` / `publishing`). Draft create can omit it.
* You may also store `channel_name` in `platforms[].meta` for display; publishing uses `channel_id` only.
* MCP equivalent: `list-discord-channels-tool` with `account_id`. That MCP tool requires **create-post** permission (Owner / Admin / Member) — Viewers cannot call it.
# List labels
Source: https://docs.trypost.it/api-reference/endpoint/list-labels
GET /labels
List all labels for the current workspace, latest first. Returns a plain array (no pagination).
## Response
Plain array of label objects. Each item has these fields:
Label UUID.
Label name.
Hex color code in `#RRGGBB` format (e.g. `#4f46e5`).
Creation timestamp in `Y-m-d H:i:s` format.
Last update timestamp in `Y-m-d H:i:s` format.
```bash theme={null}
curl -X GET https://app.trypost.it/api/labels \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
[
{
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"name": "Product Launch",
"color": "#4f46e5",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 08:00:00"
},
{
"id": "f6a7b8c9-d0e1-2f3a-4b5c-6d7e8f9a0b1c",
"name": "Urgent",
"color": "#ef4444",
"created_at": "2025-01-12 14:00:00",
"updated_at": "2025-01-12 14:00:00"
}
]
```
# List Pinterest boards
Source: https://docs.trypost.it/api-reference/endpoint/list-pinterest-boards
GET /social-accounts/{account}/boards
List boards for a connected Pinterest account. Use a returned board id as platforms[].meta.board_id when creating or updating a Pinterest post.
Returns the boards TryPost can publish pins to for this Pinterest account.
## Request
UUID of a connected Pinterest social account in the current workspace. Non-Pinterest accounts return `422`.
## Response
Board list. Each item:
Board ID — send this as `platforms[].meta.board_id` (JSON string).
Human-readable board name.
`true` when Pinterest returned more boards than TryPost fetched in this call. Prefer picking from the returned list, or reconnect/retry if the board you need is missing.
Returns `401` if the Pinterest token is expired, `403`/`429`/`502` on upstream Pinterest errors, and `422` if the account is not Pinterest.
```bash theme={null}
curl -X GET https://app.trypost.it/api/social-accounts/b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e/boards \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"boards": [
{ "id": "1234567890123456789", "name": "Product shots" },
{ "id": "9876543210987654321", "name": "Inspiration" }
],
"truncated": false
}
```
## Tips
* `board_id` is **required to publish** a Pinterest pin (`scheduled` / `publishing`). Draft create can omit it.
* MCP equivalent: `list-pinterest-boards-tool` with `account_id`. That MCP tool requires **create-post** permission (Owner / Admin / Member) — Viewers cannot call it.
* Do **not** call Pinterest's public API directly for this — use this endpoint so auth and scoping stay with TryPost.
# List posts
Source: https://docs.trypost.it/api-reference/endpoint/list-posts
GET /posts
List all posts for the current workspace, paginated 15 per page.
## Request
Page number for pagination. Defaults to `1`.
## Response
Returns posts ordered by `scheduled_at` descending (latest first), 15 per page.
UUID of the post.
The post text body. May be `null` for drafts.
Media items attached to the post. Each item has `id`, `path`, `url`, `type` (`image` | `video` | `document`), `mime_type`, `original_filename`, and optional `meta` (e.g. `alt_text` for images — this is media meta, not `platforms[].meta`). Empty array if none.
One of: `draft`, `scheduled`, `publishing`, `published`, `partially_published`, `failed`.
Scheduled publish time in `Y-m-d H:i:s` format. May be `null` for unscheduled drafts.
Publish completion time in `Y-m-d H:i:s` format. `null` until fully published.
One entry per `post_platform` (one social account targeted by this post).
UUID of the post\_platform row. Use this in `PUT /posts/{post}` to enable/disable platforms.
Platform identifier — e.g. `linkedin`, `linkedin-page`, `x`, `facebook`, `instagram`, `instagram-facebook`, `tiktok`, `youtube`, `threads`, `pinterest`, `bluesky`, `mastodon`, `telegram`, `discord`.
Format used for this platform — e.g. `linkedin_post`, `instagram_reel`. See [`GET /content-types`](/api-reference/endpoint/list-content-types).
Per-platform settings (Pinterest `board_id`/`title`/`link`, TikTok `privacy_level`, Discord `channel_id`, etc.). May be `null` or `{}`. Full key reference: [`PUT /posts/{post}` → Per-platform meta](/api-reference/endpoint/update-post#per-platform-meta).
Per-platform publish status: `pending`, `publishing`, `published`, or `failed`.
Whether this platform will be published to.
Public URL of the published post. `null` until published successfully.
Per-platform publish time in `Y-m-d H:i:s` format. `null` until published.
Error reason from the last failed publish attempt. `null` on success.
Snapshot of the social account's display name (preserved if the account is later disconnected).
Snapshot of the username/handle.
Snapshot URL of the avatar.
The connected social account. Fields: `id`, `platform`, `display_name`, `username`, `is_active`, `status` (`connected` | `disconnected` | `token_expired`).
Workspace labels attached to the post. Each item has `id`, `name`, `color`, `created_at`, `updated_at`.
Creation timestamp in `Y-m-d H:i:s` format.
Last update timestamp in `Y-m-d H:i:s` format.
```bash theme={null}
curl -X GET https://app.trypost.it/api/posts \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"data": [
{
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "Excited to announce our new feature!",
"media": [],
"status": "scheduled",
"scheduled_at": "2025-01-15 10:00:00",
"published_at": null,
"platforms": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "linkedin",
"content_type": "linkedin_post",
"meta": {},
"status": "pending",
"enabled": true,
"platform_url": null,
"published_at": null,
"error_message": null,
"display_name": "John Doe",
"display_username": "johndoe",
"display_avatar": "https://media.licdn.com/.../avatar.jpg",
"social_account": {
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "linkedin",
"display_name": "John Doe",
"username": "johndoe",
"is_active": true,
"status": "connected"
}
}
],
"labels": [],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:00:00"
}
],
"links": {
"first": "https://app.trypost.it/api/posts?page=1",
"last": "https://app.trypost.it/api/posts?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"to": 1,
"last_page": 1,
"per_page": 15,
"total": 1,
"path": "https://app.trypost.it/api/posts",
"links": [
{ "url": null, "label": "« Previous", "page": null, "active": false },
{ "url": "https://app.trypost.it/api/posts?page=1", "label": "1", "page": 1, "active": true },
{ "url": null, "label": "Next »", "page": null, "active": false }
]
}
}
```
# List signatures
Source: https://docs.trypost.it/api-reference/endpoint/list-signatures
GET /signatures
List all signatures for the current workspace, latest first. Returns a plain array (no pagination).
Signatures are reusable text blocks (hashtag sets, link callouts, CTAs) appended to a post in one click.
## Response
Plain array of signature objects. Each item has these fields:
Signature UUID.
Signature name.
The text appended to posts when this signature is applied.
Creation timestamp in `Y-m-d H:i:s` format.
Last update timestamp in `Y-m-d H:i:s` format.
```bash theme={null}
curl -X GET https://app.trypost.it/api/signatures \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
[
{
"id": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"name": "Tech Stack",
"content": "#tech #ai #startup #saas",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 08:00:00"
}
]
```
# List social accounts
Source: https://docs.trypost.it/api-reference/endpoint/list-social-accounts
GET /social-accounts
List all connected social accounts for the current workspace, ordered by platform. Returns a plain array (no pagination).
## Response
Plain array of social account objects. Each item has these fields:
Social account UUID.
Platform identifier. One of: `linkedin`, `linkedin-page`, `x`, `facebook`, `instagram`, `instagram-facebook`, `tiktok`, `youtube`, `threads`, `pinterest`, `bluesky`, `mastodon`, `telegram`, `discord`.
The account's display name.
The account's username or handle.
Whether the account is active for publishing. Toggle with [`PUT /social-accounts/{id}/toggle`](/api-reference/endpoint/toggle-social-account).
Connection status: `connected`, `disconnected`, or `token_expired`. Disconnected/expired accounts are skipped at publish time.
```bash theme={null}
curl -X GET https://app.trypost.it/api/social-accounts \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
[
{
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "linkedin",
"display_name": "John Doe",
"username": "johndoe",
"is_active": true,
"status": "connected"
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"platform": "x",
"display_name": "John",
"username": "@johndoe",
"is_active": true,
"status": "connected"
}
]
```
# Upload media via signed URL
Source: https://docs.trypost.it/api-reference/endpoint/request-media-upload
POST /uploads/{token}
Receive an MCP-issued one-shot signed URL upload. Normally invoked by the request-media-upload-tool flow, not called directly.
This endpoint is the **destination** of a signed POST URL issued by the MCP [`request-media-upload-tool`](/ai/tools-reference#posts). It exists to let AI agents and their clients upload local files to your workspace without provisioning a Personal Access Token — the signed URL itself is the auth.
You normally don't call this endpoint directly. The flow is:
1. AI agent calls `request-media-upload-tool` → receives `upload_url` + `upload_token` (+ size caps)
2. The agent (or the user) POSTs the file to `upload_url` as multipart field `media`
3. AI agent calls [`attach-media-from-upload-tool`](/ai/tools-reference#posts) with the same `upload_token` (and optional `alt` for images) to attach the resulting Media to a post
If you already have a Personal Access Token, the simpler endpoint is [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) — direct upload to a specific post, no signed URL.
## Path parameter
The single-use UUID baked into the signed URL by `request-media-upload-tool`. Must be a valid UUID; non-UUID values 404.
## Query parameters (added by the signed URL)
These are added to the URL by Laravel's `URL::temporarySignedRoute` and are covered by the HMAC signature — never set them yourself.
The workspace this upload belongs to. Tampering with this value invalidates the signature and the endpoint returns `403`.
Unix timestamp when the URL expires. Default TTL is **15 minutes** from issuance (configurable via `MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES`, legacy fallback `MCP_UPLOAD_URL_TTL_MINUTES`).
HMAC-SHA256 of the URL + query parameters, signed with the app's `APP_KEY`. Verified by Laravel's `signed` middleware before the controller runs.
## Request
Send as `multipart/form-data` with a single `media` field.
The file to upload. Per-type size caps match the rest of TryPost (from `trypost.media.max_size_mb` / env):
| Type | Default max | MIME allow-list |
| ------------ | ----------------------------------------- | ---------------------------------------------------- |
| **image** | **10 MB** (`MEDIA_IMAGE_MAX_SIZE_MB`) | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |
| **video** | **1 GB** (`MEDIA_VIDEO_MAX_SIZE_MB`) | `video/mp4`, `video/quicktime` (MOV) |
| **document** | **100 MB** (`MEDIA_DOCUMENT_MAX_SIZE_MB`) | `application/pdf` |
The FormRequest upper bound is the video ceiling; the per-type cap is enforced after MIME detection. MIME is detected from the file's magic bytes (not the extension or `Content-Type` header).
`request-media-upload-tool` also returns these limits as `max_bytes` (video ceiling) and `max_bytes_by_type`.
## Authentication
**No Bearer token.** The signed URL is the credential.
* The `signed` middleware verifies the HMAC against `APP_KEY` and rejects expired URLs with `403`.
* An atomic `Cache::add` plus a `UNIQUE` constraint on `media.upload_token` ensure the URL is **single-use**: the second POST with the same `token` returns `409`.
* Rate-limited per workspace and per IP (`MEDIA_SIGNED_UPLOAD_PER_WORKSPACE_PER_MINUTE` / `MEDIA_SIGNED_UPLOAD_PER_IP_PER_MINUTE`).
## Behaviour
* The file is streamed to the workspace's configured filesystem disk (`local`, `s3`, `r2`, etc.) at `medias/{uuid}.{ext}` — not loaded fully into memory (videos can be up to 1 GB). The path is generated server-side — client filenames are never used in the path.
* PNG and WebP still images are normalized to **JPEG (q100)** for universal platform compatibility. GIF is preserved (animation kept for X / Bluesky / Mastodon / Telegram / Discord). Videos and PDFs are stored as-is.
* The resulting `Media` row is tagged with the `upload_token` so the AI agent can reference it later via `attach-media-from-upload-tool`. The Media is created as a **workspace asset** — not yet attached to any post.
* This endpoint is **not** triggered by the [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) flow. It only handles MCP-issued signed URLs.
## Response
Echo of the `{token}` path parameter, for client convenience.
The UUID of the created `Media` record. Pass the `upload_token` to `attach-media-from-upload-tool` to attach to a post.
`image`, `video`, or `document`.
The detected MIME type. Note that PNG/WebP inputs are reported as `image/jpeg` due to the normalization step.
The filename the client sent (stored as metadata, never used for the storage path).
## Status codes
| Code | Meaning |
| ----- | --------------------------------------------------------------------- |
| `201` | Upload stored, Media created. |
| `403` | Signature invalid, expired, or any query parameter was tampered with. |
| `409` | Token already used (single-use enforcement). |
| `422` | File missing, too large for its type, or disallowed MIME type. |
| `429` | Rate limit exceeded. |
```bash theme={null}
# Assume {upload_url} came from request-media-upload-tool
curl -X POST "{upload_url}" \
-F "media=@./photo.jpg"
```
```json theme={null}
{
"upload_token": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"media_id": "01HX1A2B3C4D5E6F7G8H9I0J1K",
"type": "image",
"mime_type": "image/jpeg",
"original_filename": "photo.jpg"
}
```
# Toggle social account
Source: https://docs.trypost.it/api-reference/endpoint/toggle-social-account
PUT /social-accounts/{account}/toggle
Toggle a social account between active and inactive states.
## Request
The social account ID.
## Response
Returns the updated social account. Same shape as one entry in [`GET /social-accounts`](/api-reference/endpoint/list-social-accounts).
Returns `404` with `{ "message": "Account not found." }` if the account doesn't exist or belongs to a different workspace.
```bash theme={null}
curl -X PUT https://app.trypost.it/api/social-accounts/b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e/toggle \
-H "Authorization: Bearer YOUR_API_KEY"
```
```json theme={null}
{
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "linkedin",
"display_name": "John Doe",
"username": "johndoe",
"is_active": false,
"status": "connected"
}
```
# Update label
Source: https://docs.trypost.it/api-reference/endpoint/update-label
PUT /labels/{label}
Update a label's name or color.
## Request
The label ID.
The new label name. Maximum 255 characters.
Hex color code (e.g., `#FF5733`). Must match the format `#RRGGBB`.
## Response
Returns the updated label. Same shape as one entry in [`GET /labels`](/api-reference/endpoint/list-labels).
```bash theme={null}
curl -X PUT https://app.trypost.it/api/labels/c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Launch Day",
"color": "#22c55e"
}'
```
```json theme={null}
{
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"name": "Launch Day",
"color": "#22c55e",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 09:00:00"
}
```
# Update post
Source: https://docs.trypost.it/api-reference/endpoint/update-post
PUT /posts/{post}
Update a post's content, schedule, enabled platforms, or labels — and trigger publishing.
## Request
The post ID.
The post status. One of:
* `draft` — keep as draft
* `scheduled` — schedule for the time given in `scheduled_at` (must be in the future)
* `publishing` — **publish immediately**: dispatches the publish job to all enabled platforms
Cross-validated: `scheduled` requires a `scheduled_at` in the future.
The post caption/text body (max **10 000** characters at the API level; per-platform hard caps still apply via [`GET /content-types`](/api-reference/endpoint/list-content-types)). For Pinterest pins, this becomes the pin **description** (not `meta.title`).
Replace the post's media set. Each item must include a public `url` — the API downloads and hosts it (same shape as [`POST /posts`](/api-reference/endpoint/create-post)). Nested `meta.alt_text` (string, max **2000**) is allowed for accessibility — that is **media** meta, not `platforms[].meta`.
Prefer dedicated endpoints when practical: [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) (multipart file) or [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url).
An array of platform entries to enable. Any platform NOT listed here will be **disabled**. Pass an empty array to disable all.
The post\_platform entry ID (from `GET /posts/{id}` or `GET /posts`).
Override the content\_type for this platform. Cross-validated against the social account's platform. Discover the live catalog with [`GET /content-types`](/api-reference/endpoint/list-content-types).
Allowed values: `linkedin_post`, `linkedin_page_post`, `x_post`, `facebook_post`, `facebook_reel`, `facebook_story`, `instagram_feed`, `instagram_reel`, `instagram_story`, `tiktok_video`, `tiktok_photo`, `youtube_short`, `threads_post`, `pinterest_pin`, `pinterest_video_pin`, `pinterest_carousel`, `bluesky_post`, `mastodon_post`, `telegram_post`, `discord_message`.
There is **no** `linkedin_carousel` / `instagram_carousel` — use `linkedin_post` + PDF (and optional `meta.document_title`) for LinkedIn documents, and `instagram_feed` with multiple images for Instagram carousels.
Per-platform settings. See [Per-platform `meta`](#per-platform-meta) below for the full contract.
ISO 8601 datetime. Required when `status=scheduled`.
An array of label UUIDs to assign to the post. Replaces existing labels.
## Per-platform `meta`
`platforms[].meta` holds settings that only apply to one social account on the post. The same keys are accepted on [`POST /posts`](/api-reference/endpoint/create-post) and by the MCP create/update tools.
### Behaviour
* **Only the keys listed below are accepted.** Unknown keys are dropped (they never persist).
* On update, `meta` is **merged** into the platform's existing object. Send a key as `null` to remove it (e.g. `"title": null`).
* Required-to-publish keys are enforced on this request when `status` is `scheduled` or `publishing` **and** you include a `platforms[]` array in the body (each submitted row is checked). Drafts are not checked. A missing key returns `422` on `platforms.{i}.meta.{field}` (e.g. `platforms.0.meta.channel_id`).
* If you publish with only `{ "status": "publishing" }` (no `platforms[]`), this endpoint does **not** re-validate stored meta — ensure `board_id` / `privacy_level` / `channel_id` were saved earlier. The MCP `publish-post-tool` always validates stored meta before publishing.
* Enum-like fields are **JSON strings** — send the exact literal (e.g. `"privacy_level": "PUBLIC_TO_EVERYONE"`), not an integer or a different casing.
* Platforms with **no** `meta` keys: X, YouTube, Threads, Bluesky, Mastodon, Telegram.
### Instagram / Facebook
| Key | Type | Required to publish? | Description |
| -------------- | ------ | -------------------- | ---------------------------------------------------------------------------------------- |
| `aspect_ratio` | string | No | Crop ratio for feed-style posts. Exact values: `"1:1"`, `"4:5"`, `"16:9"`, `"original"`. |
### LinkedIn / LinkedIn Page
| Key | Type | Required to publish? | Description |
| ---------------- | ---------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `document_title` | string (max 300) | No | Title shown on a **PDF document** post. If omitted, TryPost falls back to the PDF filename, then `"Document"`. Only used when the post has a PDF attached. |
### TikTok
| Key | Type | Required to publish? | Applies to | Description |
| ---------------------- | ------- | -------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `privacy_level` | string | **Yes** | Video + photo | Exact values: `"PUBLIC_TO_EVERYONE"`, `"MUTUAL_FOLLOW_FRIENDS"`, `"FOLLOWER_OF_CREATOR"`, `"SELF_ONLY"`. |
| `allow_comments` | boolean | No | Video + photo | When `true`, comments are allowed. TryPost sends the inverted TikTok flag (`disable_comment`). |
| `allow_duet` | boolean | No | **Video only** | Ignored for `tiktok_photo`. |
| `allow_stitch` | boolean | No | **Video only** | Ignored for `tiktok_photo`. |
| `auto_add_music` | boolean | No | **Photo only** | Ignored for `tiktok_video`. |
| `is_aigc` | boolean | No | **Video only** | Marks the post as AI-generated content. Ignored for `tiktok_photo`. |
| `disclose` | boolean | No | Video + photo | Stored for branded-content UI. **Not** sent to TikTok's API — use the `brand_*` toggles below for the actual disclosure. |
| `brand_content_toggle` | boolean | No | Video + photo | Paid partnership / branded content disclosure. |
| `brand_organic_toggle` | boolean | No | Video + photo | Your brand / organic promotional disclosure. |
Example TikTok meta:
```json theme={null}
{
"privacy_level": "PUBLIC_TO_EVERYONE",
"allow_comments": true,
"allow_duet": true,
"allow_stitch": false,
"is_aigc": false,
"brand_content_toggle": false,
"brand_organic_toggle": false
}
```
### Pinterest
| Key | Type | Required to publish? | Description |
| ---------- | ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `board_id` | string | **Yes** | Target board. Resolve IDs with [`GET /social-accounts/{account}/boards`](/api-reference/endpoint/list-pinterest-boards) (or MCP `list-pinterest-boards-tool`). At publish time, if `board_id` is missing, TryPost may fall back to the account's dashboard default board — API/MCP callers should still set `board_id` explicitly. |
| `title` | string (max 100) | No | Pin title. |
| `link` | string (max 2048) | No | Destination URL. Must be `http` or `https`. |
The pin **description** is the post `content`, not a meta field.
Example Pinterest meta:
```json theme={null}
{
"board_id": "1234567890123456789",
"title": "Summer lookbook",
"link": "https://example.com/shop/summer"
}
```
### Discord
| Key | Type | Required to publish? | Description |
| -------------- | -------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `channel_id` | string | **Yes** | Target text/announcement channel. Resolve IDs with [`GET /social-accounts/{account}/channels`](/api-reference/endpoint/list-discord-channels) (or MCP `list-discord-channels-tool`). |
| `channel_name` | string | No | Display label only — not used when publishing. |
| `mentions` | array | No | Mention chips. Each item: `{ "token": string, "label"?: string }`. `token` is required. |
| `embeds` | array (max 10) | No | Rich embeds. Each item may include `title` (max 256), `description` (max 4096), `url`, `image` (image URL), `color` (`#RRGGBB` or `RRGGBB`). |
**Mention `token` formats** (send these exact Discord markup strings):
| Target | `token` example |
| -------- | -------------------------- |
| Everyone | `"@everyone"` |
| Here | `"@here"` |
| User | `"<@123456789012345678>"` |
| Role | `"<@&987654321098765432>"` |
There is no public REST endpoint to search Discord members/roles — construct tokens yourself (or use the dashboard mention picker).
Example Discord meta:
```json theme={null}
{
"channel_id": "444555666777888999",
"channel_name": "announcements",
"mentions": [
{ "token": "@everyone", "label": "@everyone" }
],
"embeds": [
{
"title": "Launch day",
"description": "We're live.",
"url": "https://example.com",
"image": "https://example.com/hero.png",
"color": "#7c3aed"
}
]
}
```
## Response
Returns the updated post (including `platforms[].meta`).
Returns `422` with a `message` when the post is already finalized and cannot be edited (`published`, `publishing`, `partially_published`, or `failed`). English copy: *"This post has already been processed and cannot be re-published. Duplicate it to try again."*
```bash theme={null}
curl -X PUT https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"status": "publishing",
"content": "New pin from the TryPost API",
"platforms": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"content_type": "pinterest_pin",
"meta": {
"board_id": "1234567890123456789",
"title": "Summer lookbook",
"link": "https://example.com/shop/summer"
}
}
]
}'
```
```json theme={null}
{
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "New pin from the TryPost API",
"media": [],
"status": "publishing",
"scheduled_at": "2025-01-15 10:30:00",
"published_at": null,
"platforms": [
{
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"platform": "pinterest",
"content_type": "pinterest_pin",
"meta": {
"board_id": "1234567890123456789",
"title": "Summer lookbook",
"link": "https://example.com/shop/summer"
},
"status": "publishing",
"enabled": true,
"platform_url": null,
"published_at": null,
"error_message": null,
"display_name": "Acme Brand",
"display_username": "acme",
"display_avatar": "https://i.pinimg.com/.../avatar.jpg",
"social_account": {
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"platform": "pinterest",
"display_name": "Acme Brand",
"username": "acme",
"is_active": true,
"status": "connected"
}
}
],
"labels": [],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:30:00"
}
```
## Tips
* To publish an existing draft, send `{ "status": "publishing" }` — content/platforms already saved are kept. Required meta (`board_id`, `privacy_level`, `channel_id`) must already be stored; include `platforms[]` on this request if you want the API to 422 when a key is missing.
* To schedule, send `{ "status": "scheduled", "scheduled_at": "2025-12-31T15:30:00Z" }`.
* To toggle which platforms are active without changing content, send only the `platforms[]` array.
* Clear a meta field with `null`: `{ "meta": { "title": null, "link": null } }`.
# Update signature
Source: https://docs.trypost.it/api-reference/endpoint/update-signature
PUT /signatures/{signature}
Update a signature's name or content.
## Request
The signature ID.
The new signature name. Maximum 255 characters.
The new signature content.
## Response
Returns the updated signature. Same shape as one entry in [`GET /signatures`](/api-reference/endpoint/list-signatures).
```bash theme={null}
curl -X PUT https://app.trypost.it/api/signatures/e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Tech & AI",
"content": "#tech #ai #machinelearning #startup"
}'
```
```json theme={null}
{
"id": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"name": "Tech & AI",
"content": "#tech #ai #machinelearning #startup",
"created_at": "2025-01-10 08:00:00",
"updated_at": "2025-01-10 09:00:00"
}
```
# Upload media file
Source: https://docs.trypost.it/api-reference/endpoint/upload-media
POST /posts/{post}/media
Upload an image, video, or PDF from your machine and attach it to a post.
For attaching media from a public URL instead of uploading bytes, use [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url).
## Path parameters
The UUID of the post to attach the file to.
## Request
Send as `multipart/form-data` with a single `media` field.
The file to upload. Must be one of the allowed types:
* **Images**: `image/jpeg`, `image/png`, `image/gif`, `image/webp` — up to **10 MB** (`MEDIA_IMAGE_MAX_SIZE_MB`)
* **Videos**: `video/mp4`, `video/quicktime` (MOV) — up to **1 GB** (`MEDIA_VIDEO_MAX_SIZE_MB`)
* **Documents**: `application/pdf` — up to **100 MB** (`MEDIA_DOCUMENT_MAX_SIZE_MB`)
## Behaviour
* The post must belong to the caller's current workspace; otherwise the response is `404`.
* The file's MIME type is **intersected with the platforms enabled on the post**. For example, uploading an image to a TikTok-only post returns `422` unless the post targets `tiktok_photo`. Uploading a PDF only succeeds on platforms that accept documents (e.g. LinkedIn `linkedin_post`).
* Per-type size caps are enforced after MIME detection. The FormRequest uses the video ceiling as an overall bound; per-type limits still apply.
* PNG and WebP still images are normalized to **JPEG (q100)** at storage time for universal platform compatibility. GIF is preserved (animation kept for X / Bluesky / Mastodon / Telegram / Discord). MP4, MOV, and PDF are stored as-is.
* The new media is stored under `medias/{uuid}.{ext}` on the configured filesystem disk (`local`, `s3`, or `r2`) and appended to the post's `media[]` array.
## Response
Returns the updated post — same shape as [`GET /posts/{post}`](/api-reference/endpoint/get-post), with the newly-uploaded item in `media[]`.
On validation failure (unsupported type, file too large, no enabled platform accepts the type) the response is `422` with `errors.media[]`.
```bash theme={null}
curl -X POST https://app.trypost.it/api/posts/9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c/media \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Accept: application/json" \
-F "media=@./photo.jpg"
```
```json theme={null}
{
"id": "9f1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"content": "Launch day!",
"media": [
{
"id": "01HX1A2B3C4D5E6F7G8H9I0J1K",
"path": "medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"url": "https://media.trypost.it/medias/2681a1bf-131f-41b1-9866-755c1cb51f97.jpg",
"type": "image",
"mime_type": "image/jpeg",
"original_filename": "photo.jpg"
}
],
"status": "draft",
"scheduled_at": null,
"published_at": null,
"platforms": [],
"labels": [],
"created_at": "2025-01-14 09:00:00",
"updated_at": "2025-01-14 09:30:00"
}
```
# Introduction
Source: https://docs.trypost.it/api-reference/introduction
The TryPost API lets you manage social media posts, signatures, labels, and more with a simple REST API.
## Base URL
The TryPost API is available at:
```
https://app.trypost.it/api
```
For self-hosted instances, the base URL is your `APP_URL` followed by `/api` (e.g., `https://trypost.yourdomain.com/api`).
## Authentication
All API requests require a Bearer token in the `Authorization` header. You can create API keys (Personal Access Tokens) from [Settings → API Keys](https://app.trypost.it/settings/workspace/api-keys) or via the [`POST /api-keys`](/api-reference/endpoint/create-api-key) endpoint.
```bash theme={null}
Authorization: Bearer YOUR_API_KEY
```
Keep your API key secret. Do not expose it in client-side code or public repositories. The plain token value is only shown once at creation — store it immediately.
## Workspace scoping
Every API key is bound to a specific workspace at creation time. All requests act on that workspace — you cannot list or modify resources from other workspaces with the same key. To work with multiple workspaces, create one API key per workspace.
API keys authenticate the REST API only. The [MCP server](/ai/introduction) uses OAuth (`mcp:use`), not Personal Access Tokens.
## Rate limiting
API requests are throttled to prevent abuse. When you exceed the limit, the API returns a `429` status code with a `Retry-After` header indicating how many seconds to wait.
## Errors
The API uses conventional HTTP status codes. All error responses include a `message` field.
| Status | Description |
| ------ | --------------------------------------------------------------------------------- |
| `200` | Success |
| `201` | Created |
| `204` | No content (successful deletion) |
| `401` | Missing or invalid API key |
| `402` | Active subscription required (Cloud only — self-hosted instances skip this check) |
| `404` | Resource not found, or resource not in the key's workspace |
| `422` | Validation error |
| `429` | Rate limit exceeded |
```json theme={null}
{
"message": "The name field is required."
}
```
## Response shape
Resources are returned **unwrapped** — no `data:` envelope. A single resource looks like:
```json theme={null}
{
"id": "9f1a2b3c-...",
"name": "...",
"...": "..."
}
```
A non-paginated collection looks like a plain array:
```json theme={null}
[
{ "id": "9f1a2b3c-...", "name": "..." },
{ "id": "a1b2c3d4-...", "name": "..." }
]
```
The only endpoint that wraps in `data:` is `GET /posts`, because it's paginated.
## Pagination
The [`GET /posts`](/api-reference/endpoint/list-posts) endpoint returns 15 posts per page. The response uses Laravel's standard pagination envelope:
```json theme={null}
{
"data": [...],
"links": {
"first": "https://app.trypost.it/api/posts?page=1",
"last": "https://app.trypost.it/api/posts?page=5",
"prev": null,
"next": "https://app.trypost.it/api/posts?page=2"
},
"meta": {
"current_page": 1,
"from": 1,
"to": 15,
"last_page": 5,
"per_page": 15,
"total": 72,
"path": "https://app.trypost.it/api/posts",
"links": [
{ "url": null, "label": "« Previous", "page": null, "active": false },
{ "url": "https://app.trypost.it/api/posts?page=1", "label": "1", "page": 1, "active": true },
{ "url": null, "label": "Next »", "page": null, "active": false }
]
}
}
```
Use the `page` query parameter to navigate. Other list endpoints (signatures, labels, social accounts, API keys) return all items without pagination, as a plain array.
## Quick reference
| Resource | Endpoints |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Posts | `GET /posts`, `POST /posts`, `GET /posts/{id}`, `PUT /posts/{id}`, `DELETE /posts/{id}`, `POST /posts/{id}/media`, `POST /posts/{id}/media/from-url`, `GET /posts/{id}/metrics`, `GET /posts/{id}/preview` |
| Platforms | `GET /content-types` |
| Workspace | `GET /workspace` |
| Signatures | `GET /signatures`, `POST /signatures`, `PUT /signatures/{id}`, `DELETE /signatures/{id}` |
| Labels | `GET /labels`, `POST /labels`, `PUT /labels/{id}`, `DELETE /labels/{id}` |
| Social accounts | `GET /social-accounts`, `PUT /social-accounts/{id}/toggle`, `GET /social-accounts/{id}/boards` (Pinterest), `GET /social-accounts/{id}/channels` (Discord) |
| API keys | `GET /api-keys`, `POST /api-keys`, `DELETE /api-keys/{id}` |
# Contributing
Source: https://docs.trypost.it/contributing
How to contribute to TryPost
# Contributing to TryPost
Thank you for your interest in contributing to TryPost! This guide will help you get started.
## Ways to Contribute
* **Report bugs** - Found a bug? Open an issue
* **Suggest features** - Have an idea? Start a discussion
* **Improve docs** - Fix typos, add examples, clarify instructions
* **Write code** - Fix bugs or implement new features
## Tech stack
TryPost is a Laravel app with an Inertia.js + **Vue 3** front-end. Queues run on Horizon; real-time updates use Reverb.
## Development Setup
### Prerequisites
* PHP 8.2+
* Node.js 20+
* PostgreSQL or MySQL
* Redis
* Composer
### Setup
1. Fork and clone the repository:
```bash theme={null}
git clone https://github.com/YOUR_USERNAME/trypost.git
cd trypost
```
2. Install dependencies and run first-time setup:
```bash theme={null}
composer setup
```
This runs `composer install`, copies `.env`, generates an app key, migrates the database, runs `npm install`, and builds the front-end.
3. Start all dev processes with one command:
```bash theme={null}
composer dev
```
That runs `php artisan serve`, `php artisan queue:listen`, `php artisan pail` (logs), and `npm run dev` concurrently. For SSR development use `composer dev:ssr` instead.
## Code Style
### PHP
We use [Laravel Pint](https://laravel.com/docs/pint) for PHP code formatting:
```bash theme={null}
# Check code style
composer test:lint
# Fix code style
composer lint
```
### JavaScript/TypeScript
We use ESLint and Prettier:
```bash theme={null}
# Lint
npm run lint
# Format
npm run format
```
## Testing
Run the full suite (lint + tests) with:
```bash theme={null}
composer test
```
To run just the test suite:
```bash theme={null}
php artisan test
```
## Pull Request Process
1. **Create a branch** from `main`:
```bash theme={null}
git checkout -b feature/your-feature-name
```
2. **Make your changes** and commit with clear messages
3. **Run tests** and ensure they pass
4. **Run code formatters**:
```bash theme={null}
composer lint
npm run lint
npm run format
```
5. **Push** and create a pull request against the `main` branch
6. **Describe your changes** in the PR description
## Commit Messages
Write clear commit messages:
* `fix: resolve login redirect issue`
* `feat: add Pinterest scheduling support`
* `docs: update installation guide`
* `refactor: simplify post creation logic`
## Questions?
* [GitHub Discussions](https://github.com/trypostit/trypost/discussions) - For questions and ideas
* [GitHub Issues](https://github.com/trypostit/trypost/issues) - For bugs and feature requests
## License
By contributing, you agree that your contributions will be licensed under the same license as the project — **GNU AGPL v3** (see `LICENSE.md` in the repo).
# Discussions
Source: https://docs.trypost.it/discussions
Ask questions and join the conversation on GitHub Discussions
# Quickstart
Source: https://docs.trypost.it/getting-started/quickstart
Get started with TryPost in under 5 minutes
Get your social media scheduling up and running in minutes.
Sign up at [app.trypost.it/register](https://app.trypost.it/register). You'll get a **7-day free trial** with every feature included. Verify your email when the link arrives.
After signing in, set up a workspace (one per brand, client, or project). The workspace holds your social accounts, posts, labels, signatures, and team members.
Go to **Accounts** and connect your social media profiles. TryPost supports LinkedIn, X, Facebook, Instagram, TikTok, YouTube, Threads, Pinterest, Bluesky, Mastodon, Telegram, and Discord.
See the [platform guides](/) for details on each platform.
Click **Create Post**, write your content, and choose to post now or schedule for later.
* Pick which connected accounts to publish to (each becomes a platform entry)
* Add images or videos from your machine, the asset library, Unsplash, or Giphy
* Assign labels to organize your content
Set a date and time, and TryPost will automatically publish your post at the right moment across all selected platforms.
## What's next?
Connect your social accounts
Automate with the REST API
Connect AI assistants via MCP
# Home
Source: https://docs.trypost.it/index
TryPost documentation — schedule and publish social media posts across 12 platforms.
# TryPost Documentation
TryPost is a social media scheduling platform that lets you create, schedule, and publish posts across LinkedIn, X, Facebook, Instagram, TikTok, YouTube, Threads, Pinterest, Bluesky, Mastodon, Telegram, and Discord. LinkedIn and Instagram each support two connection types (profile vs. company page, standalone vs. Facebook Business), giving you **14 connection identifiers** in total.
Get started with TryPost in under 5 minutes.
## Platforms
Posts and image carousels on profiles and pages
Posts with images or video
Posts, reels, and stories
Feed, carousels, reels, and stories
Video and photo carousels
YouTube Shorts
Text posts with images or video
Pins, video pins, and carousels
Posts with images or video
Posts with media on any instance
Channel and group posts with media albums
Channel messages with media, mentions, and embeds
## API & Integrations
Manage posts, signatures, and more via REST API
Connect AI assistants via MCP
Guides and best practices
## Self-Hosting
TryPost is open-source and can be self-hosted. [Learn more](/self-hosting/overview).
## Support
* [Support](/support) - Get help with Cloud and self-hosted
* [GitHub Issues](https://github.com/trypostit/trypost/issues) - Report bugs
* [GitHub Discussions](https://github.com/trypostit/trypost/discussions) - Ask questions
* [Contributing Guide](/contributing) - How to contribute
# Account & security
Source: https://docs.trypost.it/knowledge-base/account-and-security
Manage your personal account: profile, language, password, sessions, connected logins.
Your TryPost account is separate from your workspaces — one user can belong to many workspaces, and personal account settings live under **Settings > Profile** and **Settings > Authentication**.
## Profile
**Settings > Profile** lets you update:
| Field | Notes |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| **Name** | Display name shown to teammates and on comments |
| **Email** | Used for login and notifications. Changing it triggers a re-verification email. |
| **Profile photo** | Upload from your machine. Stored on the configured filesystem (same as post media). |
| **Language** | `en`, `pt-BR`, or `es`. Sets the dashboard UI language; persisted as a cookie + on your user record. |
You can also delete your account from this page — see [Deleting your account](#deleting-your-account) below.
## Authentication
**Settings > Authentication** is the single page for everything sign-in related.
### Password
* If you signed up with email/password, you can change it here.
* If you signed up with Google or GitHub and never set a password, the page shows a "Set password" form instead. Setting one lets you log in either way.
### Active sessions
Every device or browser you've signed in from shows up as a session, with IP address, last-active timestamp, browser/OS, and a marker on the current session. Click **Sign out other devices** to invalidate every session except this one — useful if you suspect a leaked credential.
This calls `DELETE /settings/authentication/sessions` and forces re-login on every other device.
### Connected accounts
You can link or unlink third-party logins here:
* **Google** — available on Cloud; self-hosted needs `GOOGLE_AUTH_ENABLED=true` plus `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` / `GOOGLE_AUTH_CALLBACK`
* **GitHub** — available on Cloud; self-hosted needs `GITHUB_AUTH_ENABLED=true` plus `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` / `GITHUB_AUTH_CALLBACK`
Unlinking is blocked if it would leave you with no way to sign in (e.g. you have no password set and only one provider is linked).
### Email verification
New accounts must verify their email before scheduling posts. The signup flow sends a verification link automatically. To resend, hit **Resend verification email** on the banner shown on every page until you verify.
## Deleting your account
From **Settings > Profile**, scroll to the danger zone. Account deletion is **immediate and irreversible**:
* Removes your user record
* Cancels any subscription on your account (Cloud)
* Deletes workspaces you own (along with their posts, accounts, signatures, labels) — workspaces you only belong to as admin/member are unaffected; you're just removed from them
* Disconnects your social accounts on TryPost's side. Posts already published to social platforms are unaffected — TryPost has no way to retract them.
You'll be asked to type your password (or "DELETE", if your account only signs in via Google or GitHub) to confirm.
## Notifications
See [Notifications](/knowledge-base/notifications) for the dedicated page on how alerts work and what you can toggle.
# AI features
Source: https://docs.trypost.it/knowledge-base/ai-features
Generate, review, and create posts with AI — drawing on your workspace's brand profile.
## What's included
TryPost ships three AI flows in the post editor, all backed by your workspace's [brand profile](/knowledge-base/brand-profile) so generated content matches your voice:
| Flow | What it does | Where to find it |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ | --------------------------- |
| **Generate** | Draft or rewrite the post body from a short prompt and the current text | Post editor → AI button |
| **Review** | Read the current content and surface concrete, actionable suggestions before you publish | Post editor → Review button |
| **Create** | Wizard that builds an end-to-end post — picks a content\_type, drafts the caption, and generates images — from a single prompt | Calendar → "Create with AI" |
## Generate
`POST` your prompt and the current content; the AI returns a refined draft.
| Field | Required | Notes |
| ----------------- | -------- | ---------------------------------------------------- |
| `prompt` | Yes | Up to 2 000 chars. What you want changed or written. |
| `current_content` | No | Up to 10 000 chars. The existing post body, if any. |
Use it for:
* Turning rough notes into a polished caption
* Tightening a long draft
* Swapping tone (e.g. "make this more conversational")
## Review
Submit your draft and get a structured review back — strengths, weaknesses, and concrete edits.
| Field | Required | Notes |
| --------- | -------- | ------------------ |
| `content` | Yes | Up to 10 000 chars |
The review is shown side-by-side with the editor so you can apply suggestions one by one.
## Create (wizard)
The wizard creates a draft post from scratch — including selecting media — based on a target format and prompt.
| Field | Required | Notes |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `format` | Yes | AI-supported values: `instagram_feed`, `instagram_carousel` (wizard-only; draft is saved as `instagram_feed`), `instagram_story`, `linkedin_post`, `linkedin_page_post`, `x_post`, `threads_post`, `bluesky_post`, `mastodon_post`, `facebook_post`, `pinterest_pin`, `pinterest_carousel` |
| `social_account_id` | No | Pre-select which account to publish to |
| `image_count` | No | 0–10 images to generate alongside the caption (multi-image / carousel formats) |
| `prompt` | Yes | Up to 2 000 chars describing what the post is about |
Reels, Shorts, TikTok video, Facebook Story, and similar video-only formats are not in the AI Create wizard yet — the wizard intentionally only generates text + images.
## How brand profile is used
Each AI call is wrapped with your workspace's [brand profile](/knowledge-base/brand-profile): website, description, tone, voice notes, and content language. The prompt the model actually sees is your input plus those guardrails — that's why generated posts read in your voice and language without you having to spell it out every time.
## Credits
Every AI call deducts from your workspace's monthly credit balance. See [Plans & billing](/knowledge-base/plans-and-billing#ai-credits) for the per-action cost and how the monthly quota resets.
When the workspace runs out of credits, the API and dashboard respond with `402 Payment Required`. Wait for the next monthly reset, or add another workspace for more pooled credits.
## Self-hosted
AI features require an OpenAI (or compatible) API key configured in your `.env`:
```bash theme={null}
AI_TEXT_PROVIDER=openai
OPENAI_API_KEY=sk-...
AI_TEXT_MODEL=gpt-5.4
```
Without those, the AI buttons stay disabled. Self-hosted instances skip the credit check entirely (`SELF_HOSTED=true` in `.env`).
# Analytics
Source: https://docs.trypost.it/knowledge-base/analytics
See how your published posts and connected accounts are performing across each platform.
## What it shows
The **Analytics** dashboard pulls per-account engagement metrics straight from each platform's API and displays them in one place. Each connected account gets its own tab; the metrics shown depend on what the platform exposes.
Supported platforms today:
| Platform | Account-level metrics | Date-range filter |
| ------------------------------------------ | --------------------- | ----------------------------------- |
| Instagram (Standalone & Facebook Business) | Yes | Yes |
| Facebook | Yes | Yes |
| LinkedIn Page | Yes | Yes |
| Pinterest | Yes | Yes |
| Threads | Yes | Yes |
| X (Twitter) | Yes | Yes |
| YouTube | Yes | Yes |
| TikTok | Yes | No (TikTok API rolling window only) |
| Telegram | Subscribers only | No (single live count) |
| Discord | Members only | No (single live count) |
Personal LinkedIn, Bluesky, and Mastodon don't expose account-level analytics — those accounts won't show up in the sidebar. Telegram's tab shows only its subscriber count and Discord's only its server member count (neither Bot API exposes view metrics); per-post reactions — plus thread replies (comments) for Discord — appear on each published post's metrics instead.
## Date range
Most platforms accept a date-range picker (default: last 7 days). Pick any custom window and the dashboard refetches metrics. TikTok ignores the picker — it returns its rolling window regardless.
## Per-post metrics
Account-level analytics live in the **Analytics** dashboard. **Per-post** metrics (likes, comments, shares per published post) are available from:
* The post editor → Metrics tab on a `published` post
* API: [`GET /posts/{post}/metrics`](/api-reference/endpoint/get-post-metrics)
* MCP: `get-post-metrics-tool`
Per-post metrics are cached for 5 minutes per platform entry to keep API calls within rate limits.
## What to watch
* **Audience growth** — followers gained over the period
* **Reach / impressions** — unique users exposed to your content
* **Engagement** — likes, comments, shares, saves (varies per platform)
* **Top posts** — best-performing posts in the window (where the platform exposes them)
## Limitations
* Metrics depend on each platform's API stability. Outages or rate-limit hits may show stale or partial data.
* Some platforms (notably TikTok) only expose a fixed rolling window — historical comparison isn't always possible.
* Disconnected or token-expired accounts can't fetch metrics. Reconnect them from **Accounts**.
# API Keys
Source: https://docs.trypost.it/knowledge-base/api-keys
Create and manage API keys to authenticate your requests to the TryPost REST API.
## What are API keys?
API keys (Personal Access Tokens) authenticate requests to the TryPost **REST API**. Keys are scoped to a **single workspace** — an API key can only access the workspace it was created in.
API keys do **not** authenticate the [MCP server](/ai/introduction). MCP connections use OAuth (`mcp:use`) via browser sign-in. Use workspace **Settings → MCP** to connect assistants, and **Settings → API Keys** for REST / scripts.
## Creating an API key
Go to workspace **Settings → API Keys** and click **Create API Key**.
| Field | Required | Description |
| -------------- | -------- | -------------------------------------------------------------------- |
| **Name** | Yes | A label to identify the key (e.g., `Production`, `CI/CD`, `Scripts`) |
| **Expiration** | No | Optional expiration date. After this date, the key stops working |
After creation, the full key is shown **once**. Copy it immediately.
TryPost stores a hash of your API key, not the key itself. If you lose it, you'll need to create a new one.
## Where to use your API key
Pass the key as a Bearer token in the `Authorization` header for REST requests:
```bash theme={null}
curl -X GET https://app.trypost.it/api/posts \
-H "Authorization: Bearer YOUR_API_KEY"
```
See the [API reference](/api-reference/introduction) for endpoints. To connect Claude, ChatGPT, Cursor, and other assistants, follow [Build with AI](/ai/introduction) (OAuth).
## Expiration
API keys can be created with or without an expiration date:
* **No expiration** — The key remains active until you manually delete it
* **With expiration** — The key automatically becomes inactive after the set date. Requests return a `401` error
Expired keys are not deleted — they stay visible so you can track usage history. You can delete them when ready.
## Security best practices
Create different keys for different purposes: `Production API`, `CI/CD Pipeline`. If one is compromised, you only need to rotate one.
If you're sharing a key with a contractor or external tool, set an expiration date.
Store keys in environment variables, `.env` files (not committed), or secret managers. Never hardcode them.
Create a new key, update your integrations, verify the old key's **Last used** timestamp shows no recent activity, then delete it.
## Deleting an API key
You can delete an API key at any time from the dashboard or via the [API](/api-reference/endpoint/delete-api-key). Deletion is **immediate and irreversible** — any application using the key will immediately start receiving `401` errors.
Check the **Last used** column before deleting to make sure the key isn't actively in use.
## FAQ
There's no limit. Create as many as you need.
No. The full key is only shown once. TryPost stores a secure hash, not the original key.
No. API keys authenticate the REST API only. MCP requires OAuth — see [Build with AI](/ai/introduction).
The API returns a `401 Unauthorized` error. Create a new key to restore access.
No. Keys are scoped to the workspace where they were created. Create a separate key for each workspace.
Yes. API requests are throttled to prevent abuse. If you exceed the limit, you'll receive a `429` status code.
# Asset library
Source: https://docs.trypost.it/knowledge-base/assets
Reusable images and videos stored in your workspace, plus on-demand search via Unsplash and Giphy.
## What it is
The **Assets** page (sidebar → Assets) is your workspace's media library — a place to upload, search, and reuse images and videos across multiple posts without re-uploading every time. It's also the picker that opens from the post editor when you click "Add media".
## Sources
The picker has three tabs:
| Tab | Source | What it returns |
| ------------ | -------------------------------- | ---------------------------------------------------------------- |
| **Library** | Your workspace's uploaded assets | Everything you've uploaded or imported, scoped to this workspace |
| **Unsplash** | [Unsplash](https://unsplash.com) | High-quality stock photography, royalty-free |
| **Giphy** | [Giphy](https://giphy.com) | GIFs and animated short clips |
Search and trending feeds are powered by each provider's official API — results are rendered server-side so your browser never sees raw API keys. When you pick a Giphy or Unsplash result, TryPost downloads the file and saves it as one of your workspace's assets so it survives even if the original goes away.
## Uploading
Click **Upload** to add files from your machine. Supported formats:
| Type | MIME types accepted on upload |
| ----- | ---------------------------------------------------- |
| Image | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |
| Video | `video/mp4`, `video/quicktime` (MOV) |
Max file size: **10 MB for images, 1 GB for videos**. Files larger than \~1 MB are sent in chunks via `Content-Range` for reliability on flaky connections.
## Importing from URL
The post composer also accepts public URLs — TryPost downloads the file server-side, validates the MIME type and size against the enabled platforms on the post, and stores the result. From the API, this is [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url).
## Searching
The Library tab supports a substring search on the original filename. Unsplash and Giphy use their respective search APIs — pass any keyword phrase. Trending feeds (no query) are also available on both.
## Reusing assets
Pick an asset in the gallery and it's attached to the active post. The **same** asset can be attached to many posts — TryPost references the underlying file rather than copying it. Deleting an asset that's still in use is blocked from the UI to avoid breaking those posts.
## Deleting
From the Library tab, click any asset → trash icon. Deletion removes the file from storage and detaches it from the workspace. Posts that referenced the asset will lose the attachment; published posts on social platforms are unaffected.
## API access
There's no public API for the asset library yet. The closest equivalents are post-scoped: [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) for direct file uploads and [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url) for URL-based attachments. Both upload to workspace storage and append to the post's `media[]` array in one shot.
## Self-hosted
* Stock-photo and GIF tabs require valid `UNSPLASH_ACCESS_KEY` and `GIPHY_API_KEY` in your `.env`. Without them, those tabs stay disabled — the Library tab works regardless.
* Files are stored on the disk configured by `FILESYSTEM_DISK` (defaults to `public`). For production with multiple instances or heavy media, point this at S3, R2, Spaces, or any S3-compatible service. See the [Configuration guide](/self-hosting/configuration).
# Automations
Source: https://docs.trypost.it/knowledge-base/automations/introduction
Build flows that fetch, generate, and publish content on autopilot
Automations are in **beta**. The building blocks below are stable, but expect new node types and refinements over time.
Automations let you wire together a **flow** that runs on its own: a trigger starts it, action nodes fetch data and generate content, and output nodes publish posts or call webhooks. Think of it as a visual pipeline for content that would otherwise be manual.
A few things you can build:
* Turn a blog or news **RSS feed** into a daily AI-written post.
* React whenever you **publish a post** and notify an external service.
* Pull data from any **HTTP API** and generate a post from it on a schedule.
## How a flow works
Every automation is a graph of **nodes** connected by lines:
A schedule, or a post being published or scheduled. There is exactly one trigger per automation.
Fetch an RSS feed or HTTP endpoint, generate content with AI, branch on a condition, or wait.
Publish the generated post, or send a webhook to another system.
Data flows **downstream**: each node can use the data produced by the nodes connected before it. See [Variables and expressions](/knowledge-base/automations/variables) for the full picture.
## The editor
Open an automation to land in the **Workflow** editor — a canvas where you drag nodes from the sidebar and connect them. Click any node to open its settings in that same panel.
The detail screen has four tabs:
The visual editor where you build and save the flow.
A log of every real run, with per-node detail. See [Monitoring](/knowledge-base/automations/monitoring).
Run health and output charts over a date range.
Rename, activate or pause, and delete the automation.
## Build your first automation
Go to **Automations** and click **New automation**. It opens in the Workflow editor with a schedule trigger already in place.
Click the trigger node and pick when it fires — a schedule, or a post event. See [Triggers and nodes](/knowledge-base/automations/triggers-and-nodes).
Drag nodes from the sidebar onto the canvas and connect them by dragging from one node's handle to the next.
In any text field, type `{{` to autocomplete the data available at that point in the flow.
Click **Run test** to execute the flow end-to-end with synthesized data before going live.
Click **Save**, then open the **Settings** tab and flip the status switch to activate. The automation now runs on its own.
## Statuses
An automation is always in one of three states:
| Status | Meaning |
| ---------- | ------------------------------------------------------------- |
| **Draft** | Being built. It never runs. |
| **Active** | Live. The trigger fires and the flow runs. |
| **Paused** | Stopped. The trigger no longer fires until you reactivate it. |
Activating and pausing live in the **Settings** tab. You can pause anytime without losing your flow.
# Testing and monitoring
Source: https://docs.trypost.it/knowledge-base/automations/monitoring
Test runs, the invocations log, metrics, and lifecycle settings
Once a flow is built, you can test it before going live and watch how it performs once it's running.
## Testing
Click **Run test** in the Workflow editor to execute the whole flow end-to-end and inspect what each node does. The result panel shows every node's **input** and **output** as it runs — the fastest way to confirm your expressions resolve correctly.
There are two modes:
| Mode | What it does |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Dry run** (default) | Runs every node, including AI generation, but **never publishes** a post or triggers external side effects. The generated content is shown but not saved. |
| **With real data** | Actually publishes posts and fires webhooks. Use it for a final end-to-end check. |
A **With real data** test really publishes and fires side effects. Use it deliberately.
Test runs never appear in the Invocations log or Metrics — those report only on real, automatic executions.
## Invocations
The **Invocations** tab is the run log: every time the live automation fires, it lands here, newest first.
Each row shows:
* **Timestamp** — when the run started
* **Run** — its ID (click to copy)
* **Status** — completed, failed, running, waiting, pending, or cancelled
* **Last message** — a summary and the number of steps it ran
* **Duration** — how long it took
Click any row to expand it and see the **per-node breakdown** of that run. Use the **status filter** to narrow to failures, and the **search** box to find a run by ID.
Only real executions show up here. Tests you run from the editor are kept out so the log stays meaningful.
## Metrics
The **Metrics** tab charts the automation's health and output over a date range you pick with the range picker.
**Run health**
* Total runs, completed, failed, in progress
* Success rate and average duration
* A chart of runs started, completed, and failed over time
**Output**
* Posts created by the automation
* A breakdown of those posts by platform
Like Invocations, Metrics count only real runs — test runs never inflate the numbers.
## Settings and lifecycle
The **Settings** tab is where you operate the automation:
Change the automation's name without touching the flow.
Flip the status switch to go live or stop. Pausing keeps the flow intact — the trigger simply stops firing until you reactivate.
In the **danger zone**, permanently remove the automation and its run history. This can't be undone.
## Related
The big picture and a build walkthrough.
Pass data between nodes.
# Triggers and nodes
Source: https://docs.trypost.it/knowledge-base/automations/triggers-and-nodes
Every building block you can drop into a flow
A flow is built from **nodes**. One trigger starts it; the rest fetch data, shape the flow, or deliver output. Drag nodes from the editor sidebar and connect them by dragging from a node's handle to the next node.
## Triggers
Every automation has exactly one trigger. It decides **when** the flow runs.
| Trigger | Fires when | Provides |
| ------------------ | --------------------------------------------------------------------------------------------------- | ----------------------------------- |
| **Schedule** | A cron-style schedule you set (every N minutes/hours/days, weekly, monthly, or a custom expression) | `trigger.event`, `trigger.fired_at` |
| **Post published** | One of your posts is published | `trigger.post.*` |
| **Post scheduled** | One of your posts is scheduled | `trigger.post.*` |
The trigger is created with the automation and can't be removed — you can only change its type. The schedule editor lets you pick an interval, time, weekdays, or a custom cron expression, and the timezone defaults to your workspace timezone.
## Source nodes
Source nodes pull external data into the flow. Both expose two output handles — **has items** and **no items** — so you can branch on whether anything new came back.
Reads an RSS/Atom feed from a **Feed URL** and exposes the matched item as `fetched.title`, `fetched.link`, `fetched.description`, and `fetched.pubDate`.
TryPost remembers which items it has already seen, so the flow only runs for genuinely **new** entries.
Calls any HTTP endpoint. Configure the **URL**, **method** (GET/POST/PUT/PATCH/DELETE), authentication (none, bearer, basic, or API key), headers, and a body template. Auth tokens and passwords are stored encrypted.
The JSON response becomes `fetched`, so you reference any field with `{{ fetched. }}`. Optional **items path**, **item key path**, and **item date path** settings let it walk a list response and dedupe items the same way the RSS node does.
**First run sets a baseline.** The first time a source node polls an existing feed or endpoint, it records what's already there and produces nothing — only items that appear *after* that trigger the flow.
**New items fan out.** When a source node then finds several new items at once, each one runs the downstream nodes independently — five new feed entries produce five posts. A test run only ever processes the first item, so it stays predictable.
## Content node
Generates a post with AI and attaches it to one or more social accounts.
* **Accounts** — which connected accounts and content types to post to. The most restrictive selected platform (smallest character limit) drives the generated copy so it fits everywhere.
* **Prompt** — your instruction. Reference upstream data with `{{ }}`, e.g. `Write a post about {{ fetched.title }}`.
* **Image count** — `0` for text-only (LinkedIn, X, Threads), `1` for a single image, or `2–10` for a carousel. The picker only offers counts the selected accounts support.
* **Use brand voice** — when off, the post stays faithful to the source instead of being rewritten in your brand persona. Useful for news curation.
* **Use brand visuals** — when off, generated images skip your brand colors and identity.
Provides `generated.content` and `generated.post_url`.
The Generate node uses the same content engine as **Create with AI** — see [AI features](/knowledge-base/ai-features) and [Brand profile](/knowledge-base/brand-profile).
## Flow nodes
These shape the path the run takes.
Branches the flow. Compares a **field** (any `{{ }}` expression) against a **value** using one of: `contains`, `not contains`, `equals`, `not equals`, `matches` (regex), `greater than`, or `less than`. The flow continues down the matching branch.
Pauses the flow for a **duration** in minutes, hours, or days before continuing. Useful for spacing out actions.
## Output nodes
Publishes the post created by an upstream Generate node. Choose a **mode**:
| Mode | Result |
| ------------- | ------------------------------------------------------- |
| **Now** | Publishes immediately |
| **Scheduled** | Schedules it, offset by N minutes from now (default 60) |
| **Draft** | Leaves it as a draft for you to review |
Sends an HTTP request to an external system. Configure the **URL**, **method**, headers, and a **payload template** where you can inject any `{{ }}` expression.
Stops the automation at that point. Optional — a branch ends on its own when it has no more nodes.
## What's next
Pass data between nodes with `{{ }}`.
Test runs, the invocations log, and metrics.
# Variables and expressions
Source: https://docs.trypost.it/knowledge-base/automations/variables
Pass data between nodes with double-brace expressions and reusable workflow variables
Automations move data between nodes with **expressions**. Wrap a path in double braces and TryPost replaces it at run time with the real value:
```text theme={null}
Write a short post about {{ fetched.title }} and link to {{ fetched.link }}.
```
Expressions work in **any text field** of any node — prompts, webhook payloads, HTTP bodies, condition fields, and more.
## What data is in scope
This is the rule that trips people up most:
A node can only use data from nodes connected **before** it (upstream). If a node isn't on the path leading into the current node, its data isn't available.
So a Generate node placed after a **Fetch RSS** node can use `{{ fetched.title }}`, but a node that runs *before* the fetch cannot — the data doesn't exist yet at that point in the flow.
In any field, type `{{` to open an autocomplete of exactly what's in scope at that node. Known references are highlighted; unknown ones are flagged so you catch typos before running.
## Expression reference
Which expressions exist depends on which nodes run upstream.
### Schedule trigger
Available when the flow starts from a **Schedule** trigger.
| Expression | Value |
| ------------------------ | ---------------------- |
| `{{ trigger.event }}` | The trigger event name |
| `{{ trigger.fired_at }}` | When the trigger fired |
### Post trigger
Available when the flow starts from a **Post published** or **Post scheduled** trigger.
| Expression | Value |
| --------------------------------- | ------------------------ |
| `{{ trigger.post.id }}` | The triggering post's ID |
| `{{ trigger.post.content }}` | Its content |
| `{{ trigger.post.status }}` | Its status |
| `{{ trigger.post.scheduled_at }}` | When it's scheduled |
| `{{ trigger.post.published_at }}` | When it was published |
### Fetch RSS
Available downstream of a **Fetch RSS** node.
| Expression | Value |
| --------------------------- | --------------------- |
| `{{ fetched.title }}` | The feed item's title |
| `{{ fetched.link }}` | Its link |
| `{{ fetched.description }}` | Its description |
| `{{ fetched.pubDate }}` | Its publish date |
### HTTP Request
Available downstream of an **HTTP Request** node. The whole JSON response is exposed as `fetched`, so you append whatever path you need:
```text theme={null}
{{ fetched.data.0.title }}
{{ fetched.user.name }}
```
| Expression | Value |
| ----------------------- | -------------------------------- |
| `{{ fetched. }}` | Any field from the JSON response |
### Generate
Available downstream of a **Generate** node.
| Expression | Value |
| -------------------------- | ----------------------------- |
| `{{ generated.content }}` | The AI-generated post content |
| `{{ generated.post_url }}` | The generated post's URL |
### Always available
These work anywhere in any flow.
| Expression | Value |
| -------------------------- | ------------------------------- |
| `{{ variables.YOUR_KEY }}` | A workflow variable (see below) |
| `{{ now }}` | The current date and time |
## Workflow variables
Workflow variables are **reusable values** you define once and reference anywhere with `{{ variables.KEY }}`. They're scoped to a single automation.
Use them for values you repeat across nodes or want to keep out of node fields — API keys, base URLs, account handles, a default hashtag.
In the Workflow editor, switch to the **Variables** tab in the right sidebar.
Give it a **key** (letters, numbers, and underscores — e.g. `API_KEY`) and a **value**.
Use `{{ variables.API_KEY }}` in any field, in any node.
Variable values are **stored encrypted**. They're safe for secrets like API keys.
## See it in action
The surest way to learn what's available is to run the flow:
Click **Run test** in the editor and open the result panel. Each node shows its exact **input** and **output**, so you can see precisely what every expression resolves to. See [Testing and monitoring](/knowledge-base/automations/monitoring).
# Brand profile
Source: https://docs.trypost.it/knowledge-base/brand-profile
The voice, look, and language settings that shape every AI-generated post.
## What it is
Each workspace has a **Brand profile** that the AI features (Generate, Review, Create) use to keep generated content on-brand without you having to spell it out every time. Set it up once per workspace; every AI call from then on is wrapped with these guardrails.
Find it under **Settings > Workspace > Brand**.
## Fields
| Field | Required | Notes |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Website** | No | A URL up to 255 characters. Used as context for AI generation. |
| **Description** | No | Up to 2 000 characters. What you do, who you serve. The AI's strongest signal for relevance. |
| **Tone** | No | One of: `professional`, `casual`, `friendly`, `bold`, `inspirational`, `humorous`, `educational` |
| **Voice notes** | No | Up to 2 000 characters. Free-form notes — phrases to use, things to avoid, signature jokes. |
| **Image style** | Required | Visual treatment for AI-generated images and slides. One of: `cinematic` (default), `illustration`, `isometric_3d`, `cartoon`, `typographic`, `infographic`, `minimalist`, `mockup`. |
| **Brand color** | No | Hex `#RRGGBB`. Used in image generation and any layouts that pull theme colors. |
| **Background color** | No | Hex `#RRGGBB`. Default canvas color when AI renders branded slides. |
| **Text color** | No | Hex `#RRGGBB`. Default text color on rendered slides. |
| **Brand font** | Required | One of the supported font families. Used for AI-rendered slides. |
| **Content language** | Required | `en`, `pt-BR`, or `es`. The language the AI generates posts in. |
## How the AI uses it
When you click **Generate**, **Review**, or run **Create with AI**, TryPost concatenates a short brand brief (description, tone, voice notes, language) with your prompt before sending it to the model. The model never sees your raw prompt in isolation — it always sees brand context.
That's why two workspaces with the same prompt can produce very different copy: one is wrapped with "Marketing agency for B2B SaaS, professional tone" and the other with "Indie creator, humorous tone, mention coffee".
## Recommendations
* **Keep the description specific.** "We sell software" → bad. "B2B inventory management for fast-fashion brands" → good.
* **Use voice notes for taste, not facts.** Things like "we never use exclamation marks", "we always close with a question", "avoid corporate buzzwords".
* **Lock in the content language.** It controls the whole AI surface — generated posts, review feedback, even the example button labels.
## Autofill (Cloud)
When you fill in **Website**, the dashboard offers to autofill the rest from your homepage's metadata. The AI scrapes the URL, extracts a brand description, suggests a tone, and proposes voice notes — you review and save. Saves a few minutes vs. typing it from scratch.
# Introduction
Source: https://docs.trypost.it/knowledge-base/introduction
Everything you need to know about using TryPost to manage your social media
## What is TryPost?
TryPost is a social media scheduling platform that lets you create, schedule, and publish posts across 12 social networks from a single dashboard. It's open-source and available as a managed cloud service or self-hosted on your own infrastructure.
## Core concepts
The content you create and publish. Each post fans out to one platform entry per connected account, with its own status, media, and per-platform metadata.
Your connected social media profiles and pages. Connect once, post everywhere.
Isolated environments for different brands, clients, or projects. Each workspace has its own accounts, posts, and team.
Invite collaborators with role-based permissions: Admin, Member, or Viewer. The workspace creator is the owner separately.
## Content organization
Color-coded tags to categorize and filter posts by campaign, topic, or priority.
Reusable text blocks — hashtag sets, newsletter CTAs, link callouts — appended to posts with one click.
Images and videos. Each platform has specific format requirements and limits.
Reusable workspace media plus on-demand search across Unsplash and Giphy.
Stay informed about publishing results, account issues, and team activity via in-app and email alerts.
## Working with AI
Generate captions, review drafts, and create end-to-end posts (text + images) from a single prompt.
The voice, tone, and visual settings the AI uses to keep every generated post on-brand.
Pre-built post structures you can apply to a draft in one click.
Per-account metrics from each platform, with date-range filtering.
## Account & billing
Profile, language, password, active sessions, and connected logins.
Cloud pricing, AI credits, the free trial, and managing your subscription.
Authenticate REST API requests with workspace-scoped Personal Access Tokens.
## How publishing works
Create a post, write content for each platform, and set a date and time. The scheduler runs every minute looking for posts that are due.
When the scheduled time arrives, TryPost dispatches a background job for each enabled platform. Each platform is published independently — if one fails, the others still go through.
After all platforms finish, you receive a notification (in-app and/or email) with the result: published, partially published, or failed.
## Integrations
Manage posts, signatures, labels, accounts, and API keys programmatically.
Connect AI assistants (Claude, Copilot, Cursor, etc.) to manage TryPost with natural language.
# Labels
Source: https://docs.trypost.it/knowledge-base/labels
Organize and categorize your posts with color-coded labels.
## What are labels?
Labels are color-coded tags that you can assign to posts to organize your content calendar. They make it easy to visually scan, filter, and manage your posts.
## Creating a label
Go to **Labels** in the sidebar or use the API:
| Field | Required | Description |
| --------- | -------- | --------------------------------------------------------------------- |
| **Name** | Yes | A descriptive name (e.g., "Product Launch", "Blog Content", "Urgent") |
| **Color** | Yes | A hex color code (e.g., `#4f46e5`). Must be in `#RRGGBB` format |
## Using labels
Assign labels to posts when creating or editing them. A post can have **multiple labels**, and the same label can be used across many posts.
Labels appear as colored badges in the post list and calendar, making it easy to visually identify different types of content at a glance.
### Filtering by label
In the calendar and post list views, you can filter by label to see only posts tagged with a specific label. This is useful when you want to review all posts for a particular campaign or category.
## Label ideas
| Label | Color | Use case |
| ------------------ | ---------------- | ----------------------------------------------- |
| **Product Launch** | Indigo `#4f46e5` | Posts related to a product launch campaign |
| **Blog** | Blue `#3b82f6` | Posts promoting blog articles |
| **Engagement** | Green `#22c55e` | Polls, questions, and community engagement |
| **Urgent** | Red `#ef4444` | Time-sensitive posts that need immediate review |
| **Evergreen** | Teal `#14b8a6` | Content that can be reused or rescheduled |
| **Client: Acme** | Orange `#f97316` | Agency use: posts for a specific client |
## Managing labels
* **Edit** — Update the name or color at any time. Changes are reflected immediately across all posts that use the label.
* **Delete** — Remove a label. It's removed from all posts, but the posts themselves are not affected or deleted.
## Via the API
Manage labels and assign them to posts programmatically:
```bash theme={null}
# Create a label
curl -X POST https://app.trypost.it/api/labels \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Product Launch", "color": "#4f46e5"}'
# Assign labels when updating a post
curl -X PUT https://app.trypost.it/api/posts/{post_id} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"label_ids": ["label-uuid-1", "label-uuid-2"]}'
```
See the full [Labels API reference](/api-reference/endpoint/list-labels).
# Media
Source: https://docs.trypost.it/knowledge-base/media
Attach images, videos, and PDFs to your social media posts.
## Supported media types
| Type | Common formats | MIME types accepted on upload |
| ------------ | ------------------- | ---------------------------------------------------- |
| **Image** | JPG, PNG, GIF, WebP | `image/jpeg`, `image/png`, `image/gif`, `image/webp` |
| **Video** | MP4, MOV | `video/mp4`, `video/quicktime` |
| **Document** | PDF | `application/pdf` |
Three upload paths share the same MIME set:
* [`POST /posts/{post}/media`](/api-reference/endpoint/upload-media) — direct upload with a Bearer token, attaches straight to a post.
* [`POST /posts/{post}/media/from-url`](/api-reference/endpoint/attach-media-from-url) — server-side fetch of a public URL.
* [`POST /uploads/{token}`](/api-reference/endpoint/request-media-upload) — one-shot **signed URL** (MCP [`request-media-upload-tool`](/ai/tools-reference#posts) or REST). Used by AI agents to upload local files without provisioning an API key.
WebM is intentionally excluded — every major social platform rejects it, so accepting it would just produce platform-specific publish failures.
## Upload limits
| Type | Default cap | Override (self-hosted) |
| -------------- | --------------- | ---------------------------- |
| Image | 10 MB per file | `MEDIA_IMAGE_MAX_SIZE_MB` |
| Video | 1 GB per file | `MEDIA_VIDEO_MAX_SIZE_MB` |
| Document (PDF) | 100 MB per file | `MEDIA_DOCUMENT_MAX_SIZE_MB` |
All upload paths (dashboard, REST, MCP signed URL) use the **same** per-type caps. There is no separate MCP-only size limit.
Files over 1 MB are sent in chunks (the dashboard streams them via `Content-Range` to the chunked upload endpoint) so big videos transfer reliably even on flaky connections. On self-hosted instances, raising the video cap also requires bumping PHP's `upload_max_filesize` / `post_max_size` and any reverse-proxy body-size cap to match.
Signed upload URLs are single-use and expire after **15 minutes** by default (`MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES`; legacy fallback `MCP_UPLOAD_URL_TTL_MINUTES`).
## Platform content types and media requirements
Each platform supports different content formats with specific media requirements. Use [`GET /content-types`](/api-reference/endpoint/list-content-types) for the live, machine-readable version — that catalog is the source of truth.
There is **no** `linkedin_carousel`, `linkedin_page_carousel`, or `instagram_carousel` content type on posts. Multi-image Instagram posts use `instagram_feed`. LinkedIn multi-image / PDF posts use `linkedin_post` / `linkedin_page_post` (format is inferred from the attached media; optional `meta.document_title` for PDFs).
### LinkedIn / LinkedIn Page
| Content type | Media | Max files | Notes |
| -------------------------------------- | -------- | ------------------------------------- | --------------------------------------------------------------------------------- |
| `linkedin_post` / `linkedin_page_post` | Optional | Up to 10 images, or 1 video, or 1 PDF | Images/videos/documents cannot be mixed. PDF posts can set `meta.document_title`. |
### X (Twitter)
| Content type | Media | Max files | Notes |
| ------------ | -------- | ------------------- | -------------------------------- |
| `x_post` | Optional | 4 images or 1 video | Images and video cannot be mixed |
### Facebook
| Content type | Media | Max files | Aspect | Notes |
| ---------------- | -------- | -------------------------- | ------ | -------------------------------- |
| `facebook_post` | Optional | Up to 10 images or 1 video | — | Standard page post |
| `facebook_reel` | Required | 1 video | 9:16 | Short-form vertical video |
| `facebook_story` | Required | 1 video | 9:16 | Vertical video story (up to 60s) |
### Instagram (and Instagram via Facebook)
| Content type | Media | Max files | Aspect | Notes |
| ----------------- | -------- | --------------------------- | ------ | ------------------------------------------------------------------ |
| `instagram_feed` | Required | Up to 10 images, or 1 video | 4:5 | Single- or multi-image feed post (carousel = multiple images here) |
| `instagram_reel` | Required | 1 video | 9:16 | Short-form vertical video |
| `instagram_story` | Required | 1 image or 1 video | 9:16 | Disappears after 24 hours |
### TikTok
| Content type | Media | Max files | Aspect | Notes |
| -------------- | -------- | --------------- | ------ | -------------------------------------- |
| `tiktok_video` | Required | 1 video | 9:16 | Short or long-form video |
| `tiktok_photo` | Required | Up to 35 images | 1:1 | Photo carousel — images only, no video |
### YouTube
| Content type | Media | Max files | Aspect | Notes |
| --------------- | -------- | --------- | ------ | ------------------------------- |
| `youtube_short` | Required | 1 video | 9:16 | Vertical short, up to 3 minutes |
### Threads
| Content type | Media | Max files | Notes |
| -------------- | -------- | -------------------------- | ----------------------------- |
| `threads_post` | Optional | Up to 10 images or 1 video | Text post with optional media |
### Pinterest
| Content type | Media | Max files | Aspect | Notes |
| --------------------- | -------- | ---------- | ------ | ------------------------- |
| `pinterest_pin` | Required | 1 image | 2:3 | Standard image pin |
| `pinterest_video_pin` | Required | 1 video | 9:16 | Video pin |
| `pinterest_carousel` | Required | 2–5 images | 2:3 | Multi-image swipeable pin |
### Bluesky
| Content type | Media | Max files | Notes |
| -------------- | -------- | ------------------------- | ----------------------------- |
| `bluesky_post` | Optional | Up to 4 images or 1 video | Text post with optional media |
### Mastodon
| Content type | Media | Max files | Notes |
| --------------- | -------- | ------------------------- | ----------------------------- |
| `mastodon_post` | Optional | Up to 4 images or 1 video | Text post with optional media |
### Telegram
| Content type | Media | Max files | Notes |
| --------------- | -------- | ---------------------- | ------------------------------------------ |
| `telegram_post` | Optional | Up to 10 images/videos | Text post; multiple media sent as an album |
### Discord
| Content type | Media | Max files | Notes |
| ----------------- | -------- | ---------------------- | -------------------------------------------------- |
| `discord_message` | Optional | Up to 10 images/videos | Channel message; supports mentions and rich embeds |
## Media ordering
Drag attachments in the post editor to reorder them. The order you set is the order they'll appear on the social platform — important for carousels and multi-image posts.
## File storage
Media files are stored on the disk configured by `FILESYSTEM_DISK`. Out of the box, TryPost ships with these drivers:
| Driver | Use case |
| -------- | ------------------------------------------------------------------------------------- |
| `public` | Default — serve files from `${APP_URL}/storage` (requires `php artisan storage:link`) |
| `s3` | AWS S3 |
| `r2` | Cloudflare R2 (S3-compatible) |
| `spaces` | DigitalOcean Spaces |
Any S3-compatible service (MinIO, DigitalOcean Spaces, Backblaze B2) works under the `s3` driver with the right endpoint.
On **TryPost Cloud**, media storage is handled automatically. Self-hosted users should configure cloud storage for production — see the [Configuration guide](/self-hosting/configuration).
## FAQ
Defaults: **10 MB** per image, **1 GB** per video, **100 MB** per PDF. Files over 1 MB are sent through the chunked upload endpoint via `Content-Range` headers. Self-hosters can raise the caps with `MEDIA_IMAGE_MAX_SIZE_MB` / `MEDIA_VIDEO_MAX_SIZE_MB` / `MEDIA_DOCUMENT_MAX_SIZE_MB`.
Each post stores its own media records. To reuse the same image, upload it again or copy from the asset library.
The media records are removed along with the post.
TryPost uploads media to social platforms as-is. Each platform applies its own compression and resizing rules afterwards.
# Notifications
Source: https://docs.trypost.it/knowledge-base/notifications
Stay informed about post publishing, account issues, and team activity.
## What are notifications?
TryPost sends notifications to keep you informed about important events — successful publishes, failures, account issues, and team activity. Notifications are delivered in real time so you can react quickly.
## Notification types
### Publishing
| Type | When it's sent | What it tells you |
| ------------------ | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| **Post published** | A post was published to all enabled platforms successfully | Which platforms were published with links to the live posts |
| **Post failed** | A post failed on one or more platforms (covers both full failures and partial publishes) | Which platforms failed and the error messages |
### AI
| Type | When it's sent | What it tells you |
| -------------- | ----------------------------------------------------------------------------------- | --------------------------------------------------------- |
| **Post ready** | The AI **Create** flow finished generating a post (text + images) in the background | A link to the new draft so you can review and schedule it |
### Accounts
| Type | When it's sent | What it tells you |
| ------------------------ | --------------------------------------- | -------------------------------------------- |
| **Account disconnected** | A social account lost its authorization | Which account and platform need reconnection |
### Team
| Type | When it's sent | What it tells you |
| ------------------------ | ---------------------------------------------- | ----------------------------------------------------- |
| **Invite received** | You were invited to join a workspace | Who invited you and which workspace |
| **Member joined** | Someone accepted an invite to your workspace | Who joined and their role |
| **Member removed** | A member was removed from your workspace | Who was removed |
| **Mentioned in comment** | A teammate `@`-mentioned you in a post comment | The post, the commenter, and a snippet of the comment |
## Notification channels
| Channel | Description |
| ---------- | -------------------------------------------------------------------------------------------- |
| **In-app** | Bell icon in the top nav. Delivered in real time via Reverb (WebSockets) — no refresh needed |
| **Email** | Sent to the address on your TryPost account, via the configured mailer (SendKit by default) |
In-app notifications are always created. Email is sent in addition to in-app for most types, with a couple of carve-outs:
* `mentioned_in_comment` — if you're online, only the in-app toast fires; if you're offline, you get both.
* `post_ready` — in-app only. The AI Create flow is short enough that a banner inside the app is all you need.
## Notification preferences
Go to **Settings > Notifications** to toggle which notifications you receive. Each toggle controls **email** for that notification type — in-app notifications are always created so you can catch up later from the bell.
Today the page exposes three toggles:
| Toggle | Controls email for |
| ------------------------ | --------------------------------------------------------------- |
| **Post published** | `post_published` |
| **Post failed** | `post_failed` (covers both full failures and partial publishes) |
| **Account disconnected** | `account_disconnected` |
`mentioned_in_comment`, `invite_received`, `member_joined`, `member_removed`, and `post_ready` use sensible defaults today and aren't user-configurable yet.
Keep **Post failed** and **Account disconnected** emails on. They need immediate action and you might not be looking at the dashboard when they fire.
## Managing notifications
The notification center (bell icon) shows all your in-app notifications:
* **Mark as read** — Click a notification to mark it as read
* **Mark all as read** — Clear all unread notifications at once
* **Archive all** — Archive all notifications to start fresh
## Email configuration
Email notifications require a working mail configuration. TryPost uses [SendKit](https://sendkit.dev?utm_source=trypost\&utm_medium=docs\&utm_campaign=notifications) as the default mailer with a free plan of 3,000 emails/month — more than enough for notification emails.
**Self-hosting?** See the [Configuration guide](/self-hosting/configuration) to set up email delivery.
## FAQ
The workspace owner receives all publishing notifications (success and failure), regardless of who created or scheduled the post.
Yes. Go to **Settings > Notifications** and disable each notification type. In-app notifications will still appear in the notification center, but emails will stop.
In-app notifications are delivered in real-time via WebSockets. You'll see them appear instantly without refreshing the page. Emails are sent asynchronously via the queue worker.
# Plans & billing
Source: https://docs.trypost.it/knowledge-base/plans-and-billing
How TryPost Cloud pricing, AI credits, and billing work.
Everything on this page applies to **TryPost Cloud**. Self-hosted instances skip subscription and credit checks entirely (set `SELF_HOSTED=true` in `.env`).
## The plan
TryPost Cloud has one plan, priced per workspace. Every feature is included on every workspace, with no tiers and no per-seat fees.
| | Per workspace |
| --------------- | -------------------------------------------------------------------------------------- |
| Price | **$12 / month**, or **$10 / month** billed yearly (**\$120 / year**) |
| Social accounts | One account on each network (your social set) |
| Team members | Unlimited, with roles and approvals |
| AI credits | 2,500 per month, pooled across your account |
| Included | Calendar, AI Copilot, automations, MCP server, REST API, analytics, signatures, labels |
Pricing is localized in some regions (for example, **R\$60 / month** in Brazil). See the [pricing page](https://trypost.it/pricing) for current rates.
### One workspace, one social set
Each workspace connects one account on every supported network (Instagram, Facebook, LinkedIn, X, TikTok, YouTube, Pinterest, Threads, Bluesky, Mastodon, Telegram, Discord) and runs them from a single calendar.
To manage another brand or client, or a second account on the same network, add another workspace. Each workspace is billed at the same per-workspace price, and AI credits from every workspace are pooled across your account.
## Trial
New accounts start with a **7-day free trial** with full access to the product. A card is required to start the trial. You are not charged during the 7 days, and you can cancel any time before the trial ends to avoid billing. The countdown is visible at **Settings > Account > Billing**.
When the trial ends, your subscription begins unless you cancel before then. If you let the trial lapse without subscribing, AI and credit-gated actions return `402 Payment Required` until you subscribe.
## Subscribing
1. Go to **Settings > Account > Billing**
2. Pick **Monthly** or **Yearly** at the top of the plan picker
3. Choose how many workspaces to bill
4. Complete payment in Stripe Checkout
Yearly billing costs less per month than monthly. The exact saving is shown in Checkout.
## Adding or removing workspaces
Use the same **Settings > Account > Billing** page to add or remove workspaces. New workspaces take effect immediately, and Stripe prorates the difference for the remainder of the billing period. Removing a workspace takes effect at the end of the current period, so you keep access until then.
## Updating payment method & invoices
Click **Manage subscription** on the Billing page to open the Stripe Customer Portal in a new tab. From there you can:
* Update card / payment method
* Download invoices and receipts
* Change billing email
* Cancel the subscription
## AI credits
Each workspace includes 2,500 AI credits per month. Credits are pooled across your account, so all of your workspaces draw from the same balance. The balance resets at the start of each billing cycle.
| Action | Cost |
| -------------------------------------------------- | --------------------------------------------------------------------------------- |
| Text generation (Generate, Review, Create caption) | 1 credit per \~150 tokens (input + output combined) |
| Image generation | 15 credits per image (current default model, OpenAI `gpt-image-2` at low quality) |
| Video generation | 500 credits per video |
Track usage at **Settings > Account > Usage**. The page shows how many credits are used, the monthly limit, and live counts for your connected accounts and members.
When credits hit zero, AI calls return `402 Payment Required` until the next reset.
## Limit behavior
When you reach a limit, the dashboard shows a dialog and the API/MCP layer returns `402 Payment Required` with a `message` explaining what was hit (for example, `Monthly AI credits exhausted`). The limits that apply are your pooled AI credits and one account per network in each workspace. Need more capacity on a network? Add another workspace.
## FAQ
One price per workspace: $12/month, or $10/month billed yearly (\$120/year). Every feature is included, with no tiers and no per-seat fees.
Yes. Add a workspace per brand or client. Each is billed at the same per-workspace price, and AI credits are pooled across your account.
No. The credit balance resets at the start of each billing cycle.
Stripe retries automatically. After the grace period set in your account, the subscription enters `past_due` and the dashboard shows a banner. New AI and credit-gated actions return `402` until billing recovers.
Yes. Cancel from the Stripe Customer Portal. The subscription stays active until the end of the current billing period, then expires.
No. Self-hosted instances bypass all subscription and credit checks. You manage your own infrastructure costs.
# Posts
Source: https://docs.trypost.it/knowledge-base/posts
Create, schedule, and manage social media posts across multiple platforms.
## What are posts?
Posts are the core content unit in TryPost. A post represents a piece of content that can be published across one or more social media platforms. Each post has **platform entries** — one for each connected social account targeted by the post — that hold the content type, publishing status, and per-platform metadata.
## Creating a post
1. Click **Create Post** or click on a date in the calendar
2. Write your content
3. Add media (images or videos) if desired
4. Pick which connected accounts to publish to (each becomes a platform entry)
5. Choose to post now or schedule for later
When you create a post, TryPost creates one platform entry per chosen social account. The post text is shared across all of them — there is no separate "synced/custom" toggle.
## The calendar
The calendar view is the primary way to see your content schedule:
* **Click** any post on the calendar to open it for editing
* **Switch views** between month, week, or day
* The status badge color signals where each post is in the publishing flow
## Scheduling
Pick a date and time and TryPost will publish the post automatically.
* All scheduling times are stored and displayed in **UTC**
* The scheduler checks for due posts **every minute**
* A post scheduled for `10:00 UTC` will typically publish between `10:00` and `10:01 UTC`
When a post becomes due:
1. Status flips to `publishing`
2. A background job is dispatched per enabled platform entry
3. Each platform publishes independently
4. When all platforms finish, the post status updates to the final outcome
Posts can only be scheduled in the future. The validator rejects past dates.
## Publishing flow
Each platform entry publishes independently. If LinkedIn succeeds but Instagram fails, the post becomes **partially published** — successful platforms aren't undone because of one failure.
Failed platform entries are not retried automatically — you can re-run the publish from the post editor (or, via API, send `PUT /posts/{post}` with `status=publishing`).
## Post statuses
| Status | Description |
| --------------------- | ----------------------------------------------- |
| `draft` | Being composed, not yet scheduled |
| `scheduled` | Waiting to be published at the set date/time |
| `publishing` | Currently being sent to platforms |
| `published` | Successfully published to all enabled platforms |
| `partially_published` | Published to some platforms, failed on others |
| `failed` | Failed on every enabled platform |
## Platform entry statuses
Each platform entry inside a post has its own status:
| Status | Description |
| ------------ | --------------------------------------------------------------- |
| `pending` | Waiting for the post to be published |
| `publishing` | Currently being sent to this platform |
| `published` | Successfully published — `platform_url` points to the live post |
| `failed` | Failed; `error_message` describes why |
When a platform entry fails, `error_message` carries the reason returned by the platform (e.g. `Token expired`, `Rate limit exceeded`, `Content policy violation`).
## Comments and @mentions
Posts have a **Comments** tab where workspace members can discuss drafts before they go out.
* Type `@` to mention another member — they receive a `mentioned_in_comment` notification (online → in-app only, offline → in-app + email)
* React to comments with emoji to lightweight-acknowledge without typing a reply
* Edit or delete your own comments any time
See [Notifications](/knowledge-base/notifications).
## Duplicating a post
From the post list (or any post's "..." menu) pick **Duplicate** to copy a post into a new draft. The duplicate gets a fresh ID and `draft` status; the original is untouched. The copy preserves content, media, labels, and platform entries — adjust the schedule and platforms before publishing.
The duplicate action is gated by the same workspace policies as "create post" — anyone with create-post permission can duplicate.
## Labels
Assign labels to posts to organize your content calendar. A post can have multiple labels. Use them for:
* **Campaigns** — Group posts by marketing campaign
* **Content types** — Separate educational, promotional, and engagement posts
* **Clients** — Tag posts by client (useful for agencies)
* **Priority** — Mark urgent or time-sensitive content
See [Labels](/knowledge-base/labels) for more.
## Per-platform fields
Each platform entry on a post exposes:
| Field | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `content_type` | The format used on this platform — `linkedin_post`, `instagram_reel`, `x_post`, etc. See [Media](/knowledge-base/media) for the full matrix. |
| `enabled` | Whether this platform is included when publishing |
| `status` | The publishing status for this specific platform |
| `platform_url` | Public URL of the live post (set after `status=published`) |
| `error_message` | Reason for failure (set when `status=failed`) |
## Deleting posts
You can delete any post at any time. Deleting removes the post and all its platform entries.
Already-published posts can't be "unpublished" from the social platforms — deletion in TryPost only removes our local record. Edit or delete on the platform itself if you need to take it down.
## FAQ
No. Once a post reaches `published`, the editor blocks further edits and the API returns `422` for `PUT /posts/{post}`. To change anything, edit the post on the social platform directly.
The other platforms are unaffected. The post becomes `partially_published` and the workspace owner receives a notification. The `error_message` on the failed platform entry tells you what went wrong.
Not directly. Create a new post with the same content, or re-trigger the publish from the editor on a post that has at least one failed/pending platform.
UTC. The dashboard and the API use UTC across the board — there's no per-workspace timezone setting today.
The scheduler runs every minute. A post scheduled for `10:00` will typically be picked up between `10:00` and `10:01`, then dispatched per platform.
# Signatures
Source: https://docs.trypost.it/knowledge-base/signatures
Reusable text blocks — hashtags, links, calls-to-action — that you can append to any post.
## What are signatures?
Signatures are reusable text blocks you can append to a post with one click — hashtag sets, a link to your newsletter, a call-to-action, anything you'd otherwise retype every time. Create a signature once and reuse it across posts.
## Creating a signature
Go to **Signatures** in the sidebar or use the API:
| Field | Required | Description |
| ----------- | -------- | --------------------------------------------------------------------------------------------------- |
| **Name** | Yes | A descriptive name (e.g., "SaaS hashtags", "Newsletter CTA", "Product launch") |
| **Content** | Yes | The text appended to the post (e.g., `#saas #marketing #growth` or `🔗 Subscribe: trypost.it/news`) |
Keep one signature per intent — one for hashtags, one for the newsletter CTA, one for the conference plug. You can apply more than one to the same post.
## Using signatures
When composing a post, click the signatures icon to see your list. Click any signature to append its content to the post.
You can apply multiple signatures to a single post — they're appended in the order you click them.
## Managing signatures
* **Edit** — Update the name or content at any time. Existing posts that already inserted the signature aren't affected — the text was copied at insertion time.
* **Delete** — Remove a signature you no longer need. Existing posts are not affected.
## Best practices
* **One intent per signature** — A hashtag pack and a newsletter CTA belong in separate signatures so you can mix and match.
* **Platform awareness** — LinkedIn performs well with 3–5 hashtags, X with 1–2, Instagram with up to 30. Consider creating platform-specific signatures.
* **Refresh regularly** — Trending tags and CTAs change. Review your signatures periodically.
## Via the API
Manage signatures programmatically:
```bash theme={null}
# Create a signature
curl -X POST https://app.trypost.it/api/signatures \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "SaaS hashtags", "content": "#saas #marketing #growth #startup #b2b"}'
# List all signatures
curl -X GET https://app.trypost.it/api/signatures \
-H "Authorization: Bearer YOUR_API_KEY"
```
See the full [Signatures API reference](/api-reference/endpoint/list-signatures).
# Social Accounts
Source: https://docs.trypost.it/knowledge-base/social-accounts
Connect and manage your social media accounts in TryPost.
## What are social accounts?
Social accounts are the connections between TryPost and your social media profiles or pages. When you connect an account, TryPost gets permission to create and publish posts on your behalf.
## Supported platforms
| Platform | Identifier | Account types | Auth method |
| --------------------------------- | -------------------- | ----------------------------------------------------- | -------------------- |
| **LinkedIn** | `linkedin` | Personal profiles | OAuth |
| **LinkedIn Page** | `linkedin-page` | Company pages | OAuth |
| **X (Twitter)** | `x` | Personal accounts | OAuth |
| **Facebook** | `facebook` | Pages | OAuth |
| **Instagram (Standalone)** | `instagram` | Personal IG via Instagram's own OAuth | OAuth |
| **Instagram (Facebook Business)** | `instagram-facebook` | Business / creator accounts linked to a Facebook page | OAuth (via Facebook) |
| **TikTok** | `tiktok` | Personal and business accounts | OAuth |
| **YouTube** | `youtube` | Channels | OAuth (via Google) |
| **Threads** | `threads` | Personal accounts | OAuth |
| **Pinterest** | `pinterest` | Personal and business accounts | OAuth |
| **Bluesky** | `bluesky` | Personal accounts | App Password |
| **Mastodon** | `mastodon` | Any instance | OAuth (per instance) |
| **Telegram** | `telegram` | Channels and groups | Bot (added as admin) |
| **Discord** | `discord` | Servers (channel chosen per post) | Bot + OAuth |
## Connecting an account
Go to **Accounts** in the dashboard and click the platform you want to connect. For most platforms, you'll be redirected to their authorization page. A few have extra steps:
* **LinkedIn Page** — after authorizing, pick which company page to connect
* **Facebook** — after authorizing, pick which page to manage
* **Instagram (Standalone)** — uses Instagram's own OAuth flow; suitable for personal IG accounts
* **Instagram (Facebook Business)** — authorize through Facebook, then pick the Instagram account linked to a Facebook page (required for first-party publishing of feed/reels/stories/carousels)
* **YouTube** — authorize with Google, then pick the channel
* **Bluesky** — enter your handle and an [App Password](https://bsky.app/settings/app-passwords) (not your main password)
* **Mastodon** — enter your instance URL first, then authorize on that instance
* **Telegram** — add the TryPost bot as an admin to your channel or group, then post the `/connect` command shown in the dialog
* **Discord** — authorize TryPost and choose which server to add the bot to (requires *Manage Server*); the whole server connects as one account and you pick the channel on each post
## Connection limits (Cloud only)
On TryPost Cloud, each workspace connects one account on every supported network (its social set). To connect a second account on the same network, add another workspace.
Self-hosted instances have no cap.
## Active vs. inactive
Each social account can be toggled between **active** and **inactive**:
* **Active** — The account is included when creating new posts. New posts will have a platform entry for this account.
* **Inactive** — The account is excluded from new posts. Existing scheduled posts are not affected.
Toggle an account's active state from the **Accounts** page or via the [API](/api-reference/endpoint/toggle-social-account).
Use inactive mode when you want to temporarily pause posting to a specific account (e.g., during a holiday) without disconnecting it.
## Connection status
| Status | Description | Action needed |
| ----------------- | ---------------------------------------------- | --------------------- |
| **Connected** | Working correctly, TryPost can publish posts | None |
| **Disconnected** | Authorization was revoked or token expired | Reconnect the account |
| **Token expired** | Access token expired and couldn't be refreshed | Reconnect the account |
TryPost runs three scheduled jobs to keep social connections healthy:
* **`social:refresh-expiring-tokens`** (hourly) — proactively refreshes OAuth tokens that are about to expire, before the platform forces them out
* **`social:check-connections`** (daily) — verifies every connected account still authenticates correctly
* **`social:recover-stuck-posts`** (every 30 minutes) — sweeps posts left in `publishing` and either retries or marks them failed
If any of these detects a disconnected or expired account, the workspace owner receives a notification (in-app and email).
### Why accounts get disconnected
* **Token expiration** — Some platforms issue short-lived tokens. TryPost automatically refreshes them, but if the refresh fails (e.g., the refresh token also expired), the account disconnects.
* **Password change** — Changing your password on the platform may revoke all OAuth tokens.
* **Permission revoked** — You manually revoked TryPost's access in the platform's settings.
* **Platform policy** — The platform may revoke tokens for inactivity or policy changes.
## Token security
* Access tokens and refresh tokens are **encrypted at rest** using Laravel's encryption
* Tokens are **never exposed** in API responses — the `access_token` and `refresh_token` fields are hidden
* Each account stores its token expiry date and TryPost proactively refreshes tokens before they expire
## Disconnecting an account
Go to **Accounts**, find the account, and click **Disconnect**. This:
* Removes the connection from TryPost
* Does not affect existing published posts
* Does not revoke TryPost's access on the platform (you can do that in the platform's settings)
## FAQ
No. Each social media account can only be connected once per workspace. If you try to connect an account that's already connected, TryPost will update the existing connection.
Posts scheduled for a disconnected account will fail for that platform when the scheduled time arrives. Other platforms in the same post are not affected. You'll receive a notification about the failure.
Usually no. TryPost automatically refreshes tokens. However, some platforms (like TikTok) have shorter token lifetimes and may occasionally require reconnection.
Yes. Each workspace manages its own connections independently. The same LinkedIn profile can be connected in multiple workspaces.
# Team & Roles
Source: https://docs.trypost.it/knowledge-base/team
Invite members to your workspace and manage permissions with roles.
## Team roles
TryPost has one **owner** per account (the user who signed up — set on `Account.owner_id`) plus three workspace-scoped roles that can be assigned to additional members.
| | Owner | Admin | Member | Viewer |
| ------------------------------- | :---: | :---: | :----: | :----: |
| View posts, calendar, analytics | Yes | Yes | Yes | Yes |
| Comment on posts | Yes | Yes | Yes | Yes |
| Create and edit posts | Yes | Yes | Yes | No |
| Create and manage labels | Yes | Yes | Yes | No |
| Create and manage signatures | Yes | Yes | Yes | No |
| Upload and manage media assets | Yes | Yes | Yes | No |
| Manage social accounts | Yes | Yes | No | No |
| Invite and remove members | Yes | Yes | No | No |
| Change member roles | Yes | Yes | No | No |
| Manage workspace settings | Yes | Yes | No | No |
| Manage API keys | Yes | Yes | No | No |
| Manage billing | Yes | No | No | No |
| Delete workspace | Yes | No | No | No |
**Owner** is not a role you assign — it's the account owner (the user who created the account). The owner has full access to every workspace under the account. The owner role cannot be transferred today.
**Viewer** is read-only — but viewers *can* leave comments on posts. They can open any post to review it (every field is read-only) and add comments for feedback. This makes the role ideal for clients or stakeholders who review drafts and give feedback without being able to change anything.
## Inviting members
1. Go to **Settings > Team**
2. Click **Invite Member**
3. Enter the email address
4. Select a role — **Admin**, **Member**, or **Viewer**
5. Click **Send Invite**
The invited person receives an email with a link to join. If they don't have a TryPost account, they'll be prompted to create one first.
Pending invitations can be cancelled at any time before they're accepted.
## Accepting an invite
When you receive an invitation:
1. Click the link in the email
2. Log in or create a TryPost account
3. Accept or decline the invitation
4. If accepted, you're immediately added to the workspace
## Changing roles
The account owner and workspace admins can change a member's role at any time from **Settings > Team**. Click on the member's current role to change it.
## Removing members
Owners and admins can remove members from **Settings > Team**. Removing a member:
* Revokes their access immediately
* Does **not** delete posts they created
* Does **not** affect their TryPost account or other workspace memberships
The removed member receives a notification about the removal.
## Multiple workspaces
A single TryPost account can belong to multiple workspaces with different roles in each. For example:
* **Owner** of your own account's workspaces
* **Admin** of a company workspace
* **Viewer** of a client workspace
Use the workspace switcher in the sidebar to navigate between them.
## FAQ
Not yet. The account owner is set at sign-up and stays fixed. For shared management, use the Admin role.
Viewers can see all posts, the calendar, and analytics, and they can open posts to review and leave comments for feedback. They cannot create, edit, publish, or delete anything, manage labels or signatures, connect social accounts, or manage the team.
All members (and viewers) can see all posts in the workspace, regardless of who created them.
Their scheduled posts remain and will still be published as planned. Only access to the workspace is revoked.
No. Team members are unlimited on every workspace, with roles and approvals included. Self-hosted instances are unlimited too.
# Post templates
Source: https://docs.trypost.it/knowledge-base/templates
Pre-built post structures you can apply to a draft in one click.
## What are templates?
Templates are pre-built post structures — caption skeleton, slide layouts for carousels, suggested image keywords — that you can apply to a draft and customize. They're a starting point, not a final post.
Each template is keyed to a platform and content type (e.g. `instagram_feed`, `linkedin_post`, `instagram_carousel` for multi-image Instagram templates). When applied, TryPost fills in the matching fields on your draft post. There is no `linkedin_carousel` template key — LinkedIn multi-image posts use `linkedin_post` / `linkedin_page_post`.
## Browsing templates
Open **Templates** from the sidebar. The library is filterable by:
* **Platform** — LinkedIn, Instagram, X, Facebook, etc.
* **Category** — Educational, Promotional, Engagement, Storytelling, Announcement, etc.
* **Search** — case-insensitive substring match on template name and description
Templates are content-managed — the catalog is locale-aware (English, Portuguese, Spanish), and TryPost falls back to English if a template isn't translated for your workspace's content language.
## Applying a template
1. Open or create a post
2. Click **Apply template**
3. Pick the template that matches your platform and intent
4. The post's content (and slide layouts, for carousels) is filled with the template's structure
5. Edit freely — the template is just the starting scaffold
Applied templates don't lock the post in any way. Once applied, the post is yours to edit, and changes to the template definition later don't affect existing posts.
## What a template carries
| Piece | Notes |
| ------------------------- | ------------------------------------------------------------------ |
| Caption skeleton | The body text with placeholders like topic / value prop / CTA |
| Slide layouts (carousels) | Title + body for each slide |
| Image keywords | Suggested terms for the AI image generator or a stock-photo search |
| Category & platform | Used by the filter; not editable on a post |
## Limitations
* The template library is curated — you can't create custom templates from the UI today.
* Templates only cover formats that work with text + images. Video formats (TikTok, Reels, Shorts) aren't in the catalog yet.
# Workspaces
Source: https://docs.trypost.it/knowledge-base/workspaces
Organize your social media accounts and content with workspaces.
## What are workspaces?
Workspaces are the top-level organizational unit in TryPost. Each workspace is a completely isolated environment with its own:
* Social media accounts
* Posts and calendar
* Signatures and labels
* Team members and roles
* API keys
Nothing is shared between workspaces — they're completely independent.
## Creating a workspace
1. Click on your workspace name in the sidebar
2. Click **Create Workspace**
3. Enter a name
4. Click **Create**
You'll be switched to the new workspace immediately. Connect social accounts and invite team members to get started.
## Switching workspaces
Click on the workspace name in the sidebar to see all your workspaces. Click any workspace to switch to it. The entire dashboard (posts, calendar, accounts, settings) updates to show that workspace's data.
## Workspace settings
Go to **Settings > Workspace** to manage:
| Setting | Description |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | The display name (e.g. "My Brand", "Client A") |
| **Logo** | Optional workspace logo, shown in the sidebar and avatar slots |
| **Brand profile** *(separate "Brand" tab)* | Website, description, tone, voice notes, primary/background/text colors, font, and content language. The AI generation features pull this profile so generated posts match your voice. |
All scheduling times are stored and displayed in UTC.
## Pricing (Cloud only)
On TryPost Cloud, each workspace is billed separately at one flat price, with every feature included: one account on each network, unlimited team members, and 2,500 AI credits per month (pooled across your account). To run more brands or clients, add more workspaces.
If you exhaust your pooled AI credits, the API and dashboard return `402 Payment Required` until the next reset. See [Plans & billing](/knowledge-base/plans-and-billing) for full pricing, trial, and credit details. Self-hosted instances skip these checks.
## Use cases
### Agencies
Create a workspace for each client to keep everything separate:
* **Client A** — Their social accounts, content calendar, and team
* **Client B** — Completely independent from Client A
* **Your Agency** — Your own brand's accounts
### Multiple brands
One workspace per brand, even within the same company:
* **Product A** — Product-focused accounts and content
* **Company Blog** — Corporate accounts and thought leadership
### Personal vs. business
Separate your personal and professional presence:
* **Personal** — Your personal LinkedIn, X, and Instagram
* **Business** — Company pages and business accounts
## API key scope
API keys belong to a specific workspace. When you authenticate with an API key:
* All API operations are scoped to that workspace
* You can only access that workspace's posts, accounts, signatures, etc.
* The same applies to MCP connections — the AI assistant only sees one workspace
To manage multiple workspaces via API, create a separate API key for each.
## Deleting a workspace
Go to **Settings > Workspace** and scroll to the danger zone.
Deleting a workspace permanently removes all posts, disconnects all social accounts, and removes all team members. This cannot be undone.
## FAQ
As many as you need. On TryPost Cloud each workspace is billed separately, so you add one per brand or client. Self-hosted instances have no limit.
Not directly. The workspace owner cannot be changed. You would need to create a new workspace under the other user and reconnect the accounts.
No. Posts belong to a workspace and cannot be moved. You would need to recreate them in the target workspace.
They lose access to the workspace immediately. Their TryPost accounts and other workspace memberships are not affected.
# Bluesky
Source: https://docs.trypost.it/platforms/bluesky
Connect your Bluesky account to TryPost
# Bluesky
TryPost supports posting to Bluesky.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Bluesky**
3. Enter your Bluesky handle and an **App Password**
Bluesky doesn't use OAuth. You need to create an [App Password](https://bsky.app/settings/app-passwords) in your Bluesky settings. Do not use your main password.
## Supported content types
| Type | Description |
| -------- | ------------------------------------------------ |
| **Post** | Text with up to 4 optional images **or** 1 video |
## Self-hosting setup
No API credentials or environment variables are required for Bluesky. It works out of the box on any self-hosted instance.
# Discord
Source: https://docs.trypost.it/platforms/discord
Connect a Discord server to TryPost and publish to its channels
# Discord
TryPost publishes to **Discord servers** through a shared TryPost bot that you add to your server. Each connected account is a **server (guild)** — you pick which **channel** to post in on each post.
## Connect a server
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect** on **Discord**
3. You'll be redirected to Discord — choose the **server** to add the TryPost bot to and authorize it
4. You're returned to TryPost with that server connected as an account
You need the **Manage Server** permission on a Discord server to add a bot to it. The authorization adds the bot with the permissions it needs to post: View Channel, Send Messages, Embed Links, Attach Files, Read Message History, and Mention Everyone.
Connect as many servers as you like — each one becomes its own account.
## Choosing a channel
A Discord account is the whole server, so every Discord post must target a **channel**. In the post editor's Discord settings, pick the channel from the searchable list.
Only channels the bot can actually post in are shown — **text** and **announcement** channels where the bot has *View Channel* + *Send Messages*. Forum, voice, stage, and category channels are excluded (they don't accept a direct message), as are channels the bot has no access to.
If a channel is missing from the list, give the TryPost bot access to it in Discord (channel or role permissions), or move it under a category the bot can see. The list is cached for a few minutes after a change.
## Supported content types
| Type | Description |
| ------------------------------- | ------------------------------------------------------------------- |
| **Message** (`discord_message`) | Text with up to 10 images/videos, optional mentions and rich embeds |
Discord allows up to **2000** characters of message text. Images are optimized to Discord's limits; videos and GIFs are sent as-is.
### Mentions
Add **mentions** (e.g. `@everyone`, a role, or a member) in the Discord settings. Mentions only ping on Discord — they're stored separately from the shared post text, so they never leak as literal `<@id>` markup to your other platforms. Only the mentions you explicitly add will ping; a literal "@everyone" typed into the shared caption does **not**.
### Embeds
Attach one or more rich **embeds** (title, description, URL, image, and accent color) to make announcements stand out. They render as Discord's native embed cards.
## Analytics
Discord exposes **no impressions or reach** for bot messages. TryPost surfaces what is available:
* **Members** — the server's member count (shown account-wide and on each post)
* **Reactions** — per-post reaction counts (🎉 👍 …)
* **Comments** — replies in the post's thread, when one exists
## Self-hosting setup
Discord uses a single shared bot application. Create one in the [Discord Developer Portal](https://discord.com/developers/applications) (**New Application → Bot**), copy its **Bot Token**, and grab the **Client ID** / **Client Secret** from the OAuth2 settings. Then configure:
```env theme={null}
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_BOT_TOKEN=
DISCORD_CLIENT_REDIRECT="${APP_URL}/accounts/discord/callback"
```
In the Developer Portal, add the redirect URL above to your application's **OAuth2 → Redirects**.
Use the OAuth2 **Client Secret** for `DISCORD_CLIENT_SECRET` — not the application's **Public Key**. They're different values, and the Public Key causes an `invalid_client` error on connect.
The bot is invited with a fixed permission bitfield (`248832` = View Channel + Send Messages + Embed Links + Attach Files + Read Message History + Mention Everyone) and the `bot identify guilds` scopes. Both are configurable if you need to change them:
```env theme={null}
DISCORD_PERMISSIONS=248832
DISCORD_SCOPES=bot,identify,guilds
```
See the [Self-Hosting configuration guide](/self-hosting/configuration) for the full platform credentials reference.
# Facebook
Source: https://docs.trypost.it/platforms/facebook
Connect your Facebook pages to TryPost
# Facebook
TryPost supports posting to Facebook pages.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Facebook**
3. Authorize TryPost on Facebook
4. Select the page you want to manage
## Supported content types
| Type | Aspect ratio | Description |
| --------- | ------------ | ------------------------------------ |
| **Post** | — | Text with up to 10 images or 1 video |
| **Reel** | 9:16 | Short-form vertical video |
| **Story** | 9:16 | Temporary content (24 hours) |
## OAuth scopes
`public_profile`, `pages_show_list`, `pages_read_engagement`, `pages_manage_posts`, `read_insights`
The `read_insights` scope powers per-post and per-page metrics in **Analytics**.
## Self-hosting setup
1. Go to [Meta for Developers](https://developers.facebook.com/) and create an app
2. Add the **Facebook Login** product
3. Set the redirect URL to `{APP_URL}/accounts/facebook/callback`
4. Request permissions: `public_profile`, `pages_show_list`, `pages_read_engagement`, `pages_manage_posts`, `read_insights`
5. Add to your `.env`:
```env theme={null}
FACEBOOK_CLIENT_ID=your_app_id
FACEBOOK_CLIENT_SECRET=your_app_secret
FACEBOOK_CLIENT_REDIRECT="${APP_URL}/accounts/facebook/callback"
```
# Instagram
Source: https://docs.trypost.it/platforms/instagram
Connect a personal Instagram or a Business / Creator account to TryPost
# Instagram
TryPost supports two Instagram connection flavors, each backed by a different OAuth flow:
| Connection | Identifier | Best for |
| --------------------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| **Instagram (Standalone)** | `instagram` | Personal Instagram accounts that aren't linked to a Facebook page. Uses Instagram's own OAuth. |
| **Instagram (Facebook Business)** | `instagram-facebook` | Business or Creator accounts linked to a Facebook page. Required for first-party publishing of feed, reels, and stories at full quality. |
Connect either (or both) from **Accounts** in the dashboard.
## Connect your account
### Standalone (personal)
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Instagram**
3. Authorize TryPost on instagram.com
4. The account shows up immediately
### Business (via Facebook)
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Instagram (Facebook Business)**
3. Authorize TryPost through Facebook
4. Select the Facebook page whose Instagram account you want to connect
## Supported content types
| Type | `content_type` | Aspect | Media | Description |
| --------- | ----------------- | ------ | -------------------------------------- | ------------------------------------------------------------------------------------- |
| **Feed** | `instagram_feed` | 4:5 | Up to 10 images, or 1 video (required) | Feed post — single image/video **or** multi-image carousel (all use `instagram_feed`) |
| **Reel** | `instagram_reel` | 9:16 | 1 video (required) | Short-form vertical video |
| **Story** | `instagram_story` | 9:16 | 1 image or video (required) | Temporary content (24 hours) |
There is **no** separate `instagram_carousel` content type on posts. The AI Create wizard may still accept `instagram_carousel` as a generation format; the resulting draft is stored as `instagram_feed`.
All three content types are available on both connection flavors.
## OAuth scopes
| Connection | Scopes requested |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Instagram (Standalone, `instagram`) | `instagram_business_basic`, `instagram_business_content_publish`, `instagram_business_manage_insights` |
| Instagram (Facebook Business, `instagram-facebook`) | `public_profile`, `pages_show_list`, `pages_read_engagement`, `business_management`, `instagram_basic`, `instagram_content_publish`, `instagram_manage_insights` |
The `*_manage_insights` scopes power the **Analytics** dashboard's Instagram view.
## Self-hosting setup
1. Go to [Meta for Developers](https://developers.facebook.com/) and create an app
2. Add the **Instagram** product (the standalone Instagram API, not Instagram Graph via Facebook)
3. Set the redirect URL to `{APP_URL}/accounts/instagram/callback`
4. Request permissions: `instagram_business_basic`, `instagram_business_content_publish`, `instagram_business_manage_insights`
5. Add to your `.env`:
```env theme={null}
INSTAGRAM_CLIENT_ID=your_app_id
INSTAGRAM_CLIENT_SECRET=your_app_secret
INSTAGRAM_CLIENT_REDIRECT="${APP_URL}/accounts/instagram/callback"
```
1. In the same Meta for Developers app, also add the **Facebook Login for Business** product
2. Set the redirect URL to `{APP_URL}/accounts/instagram-facebook/callback`
3. Request permissions: `public_profile`, `pages_show_list`, `pages_read_engagement`, `business_management`, `instagram_basic`, `instagram_content_publish`, `instagram_manage_insights`
4. Use the same `FACEBOOK_CLIENT_ID` / `FACEBOOK_CLIENT_SECRET` you've configured for Facebook (it's the same app)
# LinkedIn
Source: https://docs.trypost.it/platforms/linkedin
Connect your LinkedIn profile or company pages to TryPost
# LinkedIn
TryPost supports posting to LinkedIn personal profiles and company pages.
## Connect your account
### Personal profile
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect LinkedIn**
3. Authorize TryPost on LinkedIn — your profile is connected directly, no extra picker needed
### Company page
1. Go to **Accounts** and click **Connect LinkedIn Page**
2. Authorize TryPost on LinkedIn with organization admin scope
3. TryPost loads every Page you administer and redirects you to a dedicated **Select a LinkedIn Page** screen
4. Pick the Page you want to schedule for. Each Page connects as its own social account — repeat the flow to add more.
## Supported content types
| Type | `content_type` | Description |
| -------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Post** | `linkedin_post` / `linkedin_page_post` | Text with optional media — up to **10 images**, **1 video**, or **1 PDF**. Format is inferred from the attached files (no separate carousel / document content type). For PDF posts you can set `platforms[].meta.document_title`. |
Personal profiles use `linkedin_post`; company pages use `linkedin_page_post`.
## OAuth scopes
| Connection | Scopes requested |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
| Personal profile (`linkedin`) | `openid`, `profile`, `email`, `r_basicprofile`, `w_member_social` |
| Company page (`linkedin-page`) | `openid`, `profile`, `email`, `w_organization_social`, `r_organization_social`, `rw_organization_admin`, `w_member_social` |
## Self-hosting setup
1. Go to [LinkedIn Developers](https://developer.linkedin.com/) and click **Create App**
2. Fill in app details (name, company page, logo) and create the app
3. Go to the **Auth** tab and add redirect URLs:
* `{APP_URL}/accounts/linkedin/callback` (profiles)
* `{APP_URL}/accounts/linkedin-page/callback` (pages)
4. Go to the **Products** tab and request access to:
* **Share on LinkedIn**
* **Sign In with LinkedIn using OpenID Connect**
* **Marketing Developer Platform** (required for company-page scopes like `w_organization_social` and `rw_organization_admin`)
5. Add to your `.env`:
```env theme={null}
LINKEDIN_CLIENT_ID=your_client_id
LINKEDIN_CLIENT_SECRET=your_client_secret
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
```
# Mastodon
Source: https://docs.trypost.it/platforms/mastodon
Connect your Mastodon account to TryPost
# Mastodon
TryPost supports posting to any Mastodon instance.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Mastodon**
3. Enter your Mastodon instance URL (e.g., `mastodon.social`)
4. Authorize TryPost on your instance
## Supported content types
| Type | Description |
| -------- | -------------------------------------------- |
| **Post** | Text with up to 4 optional images or 1 video |
## OAuth scopes
`read:accounts`, `write:statuses`, `write:media`
## Self-hosting setup
No API credentials or environment variables are required for Mastodon. TryPost dynamically registers an OAuth app on each Mastodon instance the first time a user connects from there.
# Pinterest
Source: https://docs.trypost.it/platforms/pinterest
Connect your Pinterest account to TryPost
# Pinterest
TryPost supports creating pins on Pinterest.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Pinterest**
3. Authorize TryPost on Pinterest
## Board selection
Pinterest requires every pin to belong to a **board**. TryPost loads your boards over the API and lets you pick one in two places:
* **Per post** — when you target a Pinterest account in the post editor, the platform sidebar shows a **Board** dropdown. Pick the board for that specific pin.
* **Default board (optional)** — set a workspace default in **Accounts → your Pinterest account → Default board**. New Pinterest posts pre-fill the picker with this board; you can still override per post.
If neither a per-post `board_id` nor a default board is set, publishing fails with `Pinterest board_id is required`. The post editor blocks scheduling until you pick a board; the REST API and MCP only fail at publish time, so callers must provide `platforms[].meta.board_id` themselves.
## Supported content types
| Type | Aspect ratio | Description |
| ------------- | ------------ | ------------------------------------------------------ |
| **Pin** | 2:3 | Image pin (1 image, required) |
| **Video Pin** | 9:16 | Video pin, 4 seconds to 15 minutes (1 video, required) |
| **Carousel** | 2:3 | Multi-image pin (up to 5 images, required) |
## OAuth scopes
`boards:read`, `boards:write`, `pins:read`, `pins:write`, `user_accounts:read`
`pins:read` and `user_accounts:read` are required for the **Analytics** dashboard. `boards:read` powers the board picker.
## Self-hosting setup
1. Go to [Pinterest Developers](https://developers.pinterest.com/) and create an app
2. Add the scopes listed above in the app's permissions
3. Set the redirect URL to `{APP_URL}/accounts/pinterest/callback`
4. Add to your `.env`:
```env theme={null}
PINTEREST_CLIENT_ID=your_app_id
PINTEREST_CLIENT_SECRET=your_app_secret
PINTEREST_CLIENT_REDIRECT="${APP_URL}/accounts/pinterest/callback"
```
# Telegram
Source: https://docs.trypost.it/platforms/telegram
Connect a Telegram channel or group to TryPost
# Telegram
TryPost publishes to Telegram **channels and groups** through a shared TryPost bot that you add as an administrator.
## Connect a channel
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect** on **Telegram**
3. Add the bot (e.g. **@TryPostBot**) as an **administrator** of your channel or group
4. Copy the `/connect ` command shown in the dialog and post it in that channel
5. TryPost links the channel automatically — the dialog closes once it's connected
The bot must be an **administrator** of the channel. Telegram only delivers channel posts (including the `/connect` command and reactions) to bots that are admins, and the bot needs admin rights to publish.
You can connect as many channels and groups as you like — each one becomes its own account.
## Supported content types
| Type | Description |
| -------------------------- | ---------------------------------------------------------------------- |
| **Post** (`telegram_post`) | Text with up to 10 images/videos (multiple media are sent as an album) |
Telegram allows up to **4096** characters of text. Media captions are capped at **1024**, so a longer post is sent as the media followed by a separate text message. Formatting (bold, italic, links, `code`) and plain `@username` mentions are preserved — Telegram auto-links public usernames.
## Analytics
Telegram's Bot API exposes **no post view counts**. TryPost surfaces what is available:
* **Subscribers** — the channel's member count (shown account-wide and on each post)
* **Reactions** — per-post reaction counts (👍 ❤️ 🔥 …), pushed in real time by Telegram
## Self-hosting setup
Telegram uses a single shared bot. Create one with [@BotFather](https://t.me/BotFather), then configure:
```env theme={null}
TELEGRAM_BOT_TOKEN=123456:ABC...
TELEGRAM_BOT_USERNAME=YourBot
TELEGRAM_WEBHOOK_SECRET=a-long-random-string
```
Register the webhook so Telegram can deliver `/connect` commands and reactions:
```bash theme={null}
php artisan telegram:set-webhook
```
The webhook URL is `{WEBHOOK_URL or APP_URL}/telegram/webhook`. That base URL must be a **public HTTPS** address Telegram can reach. If `APP_URL` is private (or you use a tunnel in development), set `WEBHOOK_URL` instead — see [Configuration](/self-hosting/configuration#basic-configuration). Re-run `telegram:set-webhook` whenever it changes.
# Threads
Source: https://docs.trypost.it/platforms/threads
Connect your Threads account to TryPost
# Threads
TryPost supports posting to Threads.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect Threads**
3. Authorize TryPost on Threads
## Supported content types
| Type | Description |
| -------- | ------------------------------------------------- |
| **Post** | Text with up to 10 optional images **or** 1 video |
## OAuth scopes
`threads_basic`, `threads_content_publish`, `threads_manage_insights`
`threads_manage_insights` powers per-post and account-level metrics in **Analytics**.
## Self-hosting setup
1. Go to [Meta for Developers](https://developers.facebook.com/) and create an app
2. Add the **Threads** product
3. Request the scopes above in the app's permissions section
4. Set the redirect URL to `{APP_URL}/accounts/threads/callback`
5. Add to your `.env`:
```env theme={null}
THREADS_CLIENT_ID=your_app_id
THREADS_CLIENT_SECRET=your_app_secret
THREADS_CLIENT_REDIRECT="${APP_URL}/accounts/threads/callback"
```
# TikTok
Source: https://docs.trypost.it/platforms/tiktok
Connect your TikTok account to TryPost
# TikTok
TryPost supports posting videos and photo carousels to TikTok.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect TikTok**
3. Authorize TryPost on TikTok
## Supported content types
| Type | `content_type` | Aspect ratio | Description |
| ------------------ | -------------- | ------------ | ----------------------------------------------- |
| **Video** | `tiktok_video` | 9:16 | Short or long-form video (1 video, required) |
| **Photo carousel** | `tiktok_photo` | 1:1 | Multi-image post — up to 35 images per carousel |
## Per-post options
TikTok exposes extra publishing options that you can set per post in the editor sidebar (or via `platforms[].meta` on the API):
| Option | Values | Applies to |
| ----------------------------------------------- | --------------------------------------------------------------------------------- | ---------------------------------------- |
| `privacy_level` | `PUBLIC_TO_EVERYONE`, `MUTUAL_FOLLOW_FRIENDS`, `FOLLOWER_OF_CREATOR`, `SELF_ONLY` | Video + Photo |
| `disable_comment` | boolean | Video + Photo |
| `disable_duet` / `disable_stitch` | boolean | Video only — ignored for photo carousels |
| `brand_content_toggle` / `brand_organic_toggle` | boolean (required if your post contains branded content per TikTok policy) | Video only |
## OAuth scopes
`user.info.basic`, `user.info.profile`, `user.info.stats`, `video.publish`, `video.upload`, `video.list`
`user.info.stats` and `video.list` are required for the **Analytics** dashboard's TikTok view.
## Self-hosting setup
1. Go to [TikTok Developers](https://developers.tiktok.com/) and create an app
2. Enable both the **Content Posting API** (for `video.publish` / `video.upload`) and **Login Kit** (for the `user.info.*` scopes)
3. Add the scopes listed above to the app's authorized scopes
4. Set the redirect URL to `{APP_URL}/accounts/tiktok/callback`
5. Add to your `.env`:
```env theme={null}
TIKTOK_CLIENT_ID=your_client_key
TIKTOK_CLIENT_SECRET=your_client_secret
TIKTOK_CLIENT_REDIRECT="${APP_URL}/accounts/tiktok/callback"
```
# X (Twitter)
Source: https://docs.trypost.it/platforms/x-twitter
Connect your X (formerly Twitter) account to TryPost
# X (Twitter)
TryPost supports posting to X (formerly Twitter) accounts.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect X**
3. Authorize TryPost on X
## Supported content types
| Type | Description |
| -------- | ----------------------------------- |
| **Post** | Text with up to 4 images or 1 video |
## OAuth scopes
`tweet.read`, `tweet.write`, `users.read`, `media.write`, `offline.access`
`offline.access` is required so TryPost can refresh the access token in the background — without it, you'd have to reauthorize regularly.
## Self-hosting setup
1. Go to [X Developer Portal](https://developer.twitter.com/) and create a project/app
2. Enable **OAuth 2.0** in your app settings
3. On the User authentication settings page, enable the scopes listed above
4. Set the redirect URL to `{APP_URL}/accounts/x/callback`
5. Add to your `.env`:
```env theme={null}
X_CLIENT_ID=your_client_id
X_CLIENT_SECRET=your_client_secret
X_CLIENT_REDIRECT="${APP_URL}/accounts/x/callback"
```
# YouTube
Source: https://docs.trypost.it/platforms/youtube
Connect your YouTube channel to TryPost
# YouTube
TryPost supports uploading YouTube Shorts.
## Connect your account
1. Go to **Accounts** in the TryPost dashboard
2. Click **Connect YouTube**
3. Authorize TryPost with your Google account
4. Select the YouTube channel to connect
## Supported content types
| Type | Aspect ratio | Description |
| --------- | ------------ | ----------------------------------------- |
| **Short** | 9:16 | Vertical video up to 3 minutes (required) |
## OAuth scopes
`https://www.googleapis.com/auth/youtube.upload`, `https://www.googleapis.com/auth/youtube.readonly`, `https://www.googleapis.com/auth/youtube.force-ssl`, `https://www.googleapis.com/auth/yt-analytics.readonly`
`yt-analytics.readonly` powers the **Analytics** dashboard's YouTube view.
## Self-hosting setup
1. Go to [Google Cloud Console](https://console.cloud.google.com/) and create a project
2. Enable the **YouTube Data API v3** and **YouTube Analytics API**
3. Create OAuth 2.0 credentials
4. Add the scopes listed above to the OAuth consent screen
5. Set the redirect URL to `{APP_URL}/accounts/youtube/callback`
6. Add to your `.env`:
```env theme={null}
GOOGLE_CLIENT_ID=your_client_id
GOOGLE_CLIENT_SECRET=your_client_secret
GOOGLE_CLIENT_REDIRECT="${APP_URL}/accounts/youtube/callback"
```
The same Google credentials are used for YouTube and [Google Login](/self-hosting/configuration#google-login). Add both callback URLs to your OAuth client.
# AI Providers
Source: https://docs.trypost.it/self-hosting/ai
Configure AI text and image generation for self-hosted TryPost
# AI Providers
TryPost's AI features are built on [Laravel AI](https://laravel.com/docs/13.x/ai-sdk) ([GitHub](https://github.com/laravel/ai)), the official first-party AI SDK for Laravel. Every provider and model documented on this page is whatever that package supports — TryPost doesn't add its own provider integrations on top. If a provider/capability combination isn't listed below, it's because the package's provider class doesn't implement it, not a TryPost limitation.
TryPost only uses this SDK for **text** (the Generate / Review / Create AI flows in the post editor) and **image** generation (the Create wizard) — the package also supports audio, transcription, embeddings, and reranking, but no TryPost feature calls any of those today.
The Generate / Review / Create AI flows need a configured text-generation provider. Without one, the AI buttons stay disabled.
```env theme={null}
AI_TEXT_PROVIDER=openai
OPENAI_API_KEY=sk-...
# Optional — image generation for the AI Create wizard
AI_IMAGE_PROVIDER=openai
```
| Variable | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AI_TEXT_PROVIDER` | `openai` (default), `anthropic`, `gemini`, `azure`, `bedrock`, `groq`, `xai`, `deepseek`, `mistral`, `ollama`, `openrouter`, or `openai-compatible`. |
| `AI_IMAGE_PROVIDER` | Provider used for AI image generation in the Create wizard. Defaults to `openai`. Only `openai`, `gemini`, `xai`, `bedrock`, and `openrouter` support image generation — picking any other provider here leaves the Create wizard's image step non-functional. |
| Provider API keys | Each provider reads its own env: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`, `XAI_API_KEY`, `DEEPSEEK_API_KEY`, `GROQ_API_KEY`, `MISTRAL_API_KEY`, `OPENROUTER_API_KEY`. Ollama uses `OLLAMA_URL` (defaults to `http://localhost:11434`, no key needed). Bedrock uses AWS credentials (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_BEDROCK_REGION`) instead of a single API key. `openai-compatible` needs `OPENAI_COMPATIBLE_URL` plus a pinned model (see below) — it has no built-in default since it can point at any endpoint. |
Self-hosted instances skip the credit/quota check entirely (`SELF_HOSTED=true`), so AI calls go straight to the configured provider — you pay the provider directly.
## Pinning a specific model
Each provider ships with a sensible default text/image model, so setting just `AI_TEXT_PROVIDER` / `AI_IMAGE_PROVIDER` above is enough to get started. To pin a specific model instead of the provider's default, set the matching `_TEXT_MODEL` / `_IMAGE_MODEL` env var:
| Provider | Text | Image |
| ------------------- | --------------------------------------------- | ------------------------------- |
| `openai` | `OPENAI_TEXT_MODEL` | `OPENAI_IMAGE_MODEL` |
| `anthropic` | `ANTHROPIC_TEXT_MODEL` | — |
| `gemini` | `GEMINI_TEXT_MODEL` | `GEMINI_IMAGE_MODEL` |
| `azure` | `AZURE_OPENAI_DEPLOYMENT` | `AZURE_OPENAI_IMAGE_DEPLOYMENT` |
| `bedrock` | `AWS_BEDROCK_TEXT_MODEL` | `AWS_BEDROCK_IMAGE_MODEL` |
| `groq` | `GROQ_TEXT_MODEL` | — |
| `xai` | `XAI_TEXT_MODEL` | `XAI_IMAGE_MODEL` |
| `deepseek` | `DEEPSEEK_TEXT_MODEL` | — |
| `mistral` | `MISTRAL_TEXT_MODEL` | — |
| `ollama` | `OLLAMA_TEXT_MODEL` | — |
| `openrouter` | `OPENROUTER_TEXT_MODEL` | `OPENROUTER_IMAGE_MODEL` |
| `openai-compatible` | `OPENAI_COMPATIBLE_TEXT_MODEL` **(required)** | — |
```env theme={null}
OPENAI_TEXT_MODEL=gpt-5.4
OPENAI_IMAGE_MODEL=gpt-image-2
OPENROUTER_TEXT_MODEL=anthropic/claude-haiku-4.5
OPENROUTER_IMAGE_MODEL=google/gemini-3.1-flash-image-preview
ANTHROPIC_TEXT_MODEL=claude-sonnet-5
GEMINI_TEXT_MODEL=gemini-3.6-flash
```
All of these are optional — leave them unset to use the provider's built-in default. `azure` is the exception to the naming pattern: it resolves models via **deployment names** you create in the Azure portal (`AZURE_OPENAI_DEPLOYMENT`, `AZURE_OPENAI_IMAGE_DEPLOYMENT`), not raw model identifiers. `openai-compatible` is the other exception: `OPENAI_COMPATIBLE_TEXT_MODEL` is **required**, since an arbitrary self-hosted endpoint has no sensible default to fall back to.
**Using OpenRouter for images:** the app generates images through OpenRouter's chat-completions API, which only works with multimodal chat models — the Gemini image-preview family (the default) is confirmed working. Dedicated image-only models like `openai/gpt-image-2` or `openai/gpt-image-1` are **not** compatible this way — OpenRouter rejects them outright (404), since those require [OpenRouter's separate Image API](https://openrouter.ai/docs/guides/overview/multimodal/image-generation), which this app doesn't call. If image generation fails immediately after switching to OpenRouter, check that `OPENROUTER_IMAGE_MODEL` points at a Gemini-family model.
**OpenRouter credit pre-checks:** OpenRouter validates your balance against a request's *maximum possible* token usage before generating anything, not the tokens actually used. A low-balance key can fail with `402 insufficient credits` even on a cheap model and a short prompt, because the check is based on the model's max output ceiling (which can be tens of thousands of tokens), not your real usage. Funding the account with a few dollars usually clears this — see [OpenRouter's limits documentation](https://openrouter.ai/docs/api-reference/limits) for how credits and rate limits work.
For anything else OpenRouter-specific (available models, pricing, routing behavior), check [OpenRouter's documentation](https://openrouter.ai/docs) directly — TryPost just passes your prompt through.
# Configuration
Source: https://docs.trypost.it/self-hosting/configuration
Configure TryPost environment variables
# Configuration
TryPost is configured through environment variables in the `.env` file.
## Basic Configuration
```env theme={null}
APP_NAME="TryPost"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost
WEBHOOK_URL=
```
| Variable | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `APP_NAME` | Your application name |
| `APP_ENV` | Environment: `local`, `staging`, `production` |
| `APP_DEBUG` | Enable debug mode (set to `false` in production) |
| `APP_URL` | Your application URL |
| `WEBHOOK_URL` | Optional public base URL for inbound webhooks (e.g. Telegram) when `APP_URL` is not reachable from the internet. Defaults to `APP_URL`. |
## Self-Hosted Mode
```env theme={null}
SELF_HOSTED=true
```
`SELF_HOSTED=true` is the default. It bypasses everything billing-related so you never have to touch Stripe or Cashier, and it locks down public sign-ups:
* **Public registration is disabled** — `/register` is closed so random users can't create accounts on your instance. Bootstrap the first admin via the `UserSeeder` (see [Seed default data and create the admin user](/self-hosting/installation#6-seed-passport-client-and-admin-user)); after that, every account comes from a workspace invite (Settings → Members).
* No Stripe, Cashier, or `CASHIER_TRIAL_DAYS` configuration required
* The `LoadWorkspaceFromToken` middleware skips the `402 Payment Required` gate
* Subscription gating is not enforced, so workspaces, social accounts, members, and AI credits are unlimited per account
* AI calls go straight to your configured provider; you pay the provider directly (no credit accounting)
There's no reason to flip this to `false` on a self-hosted install. The Cloud SaaS deployment is the only place that runs with billing enabled.
## Database
TryPost supports PostgreSQL and MySQL.
### PostgreSQL (recommended)
```env theme={null}
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=trypost
DB_USERNAME=postgres
DB_PASSWORD=your_password
```
### MySQL
```env theme={null}
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=trypost
DB_USERNAME=root
DB_PASSWORD=your_password
```
## Redis
Redis is required for queues and caching.
```env theme={null}
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
```
## Runtime drivers
For a working production-like stack, point the Laravel drivers at Redis / Reverb / the database:
```env theme={null}
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=database
BROADCAST_CONNECTION=reverb
```
| Variable | Recommended | Why |
| ---------------------- | ----------- | ----------------------------------------------------------------------- |
| `QUEUE_CONNECTION` | `redis` | Horizon processes Redis queues (publishing, notifications, automations) |
| `CACHE_STORE` | `redis` | Cache + `onOneServer()` schedule locks across instances |
| `SESSION_DRIVER` | `database` | Durable sessions behind multiple app workers |
| `BROADCAST_CONNECTION` | `reverb` | Real-time dashboard updates |
Without Redis queues, Horizon has nothing useful to process. Without Redis cache on multi-instance deploys, scheduled jobs can double-fire.
## Passport (API & MCP tokens)
Passport signs and verifies both **REST API keys** (Personal Access Tokens) and **MCP OAuth** grants. You need:
```bash theme={null}
php artisan passport:keys # once — RSA files in storage/
php artisan db:seed # once — Personal Access Client in the DB (API keys)
```
Alternatively, set `PASSPORT_PRIVATE_KEY` and `PASSPORT_PUBLIC_KEY` in `.env` (PEM contents) instead of key files — **required for Docker production** (`compose.prod.yaml`) and useful for secret managers / multi-node deploys. Do not regenerate keys after users have issued tokens.
MCP clients authenticate with OAuth (`mcp:use`), not with an API key Bearer header. API keys are for the REST API only.
Full install order: [Installation](/self-hosting/installation).
## Media size limits
```env theme={null}
MEDIA_IMAGE_MAX_SIZE_MB=10
MEDIA_VIDEO_MAX_SIZE_MB=1024
MEDIA_DOCUMENT_MAX_SIZE_MB=100
MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES=15
```
| Variable | Default | Description |
| ------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `MEDIA_IMAGE_MAX_SIZE_MB` | `10` | Max image upload size, in MB. Applied to direct uploads and URL fetches. |
| `MEDIA_VIDEO_MAX_SIZE_MB` | `1024` | Max video upload size, in MB. Applied to direct uploads and URL fetches. |
| `MEDIA_DOCUMENT_MAX_SIZE_MB` | `100` | Max document (PDF) upload size, in MB. |
| `MEDIA_SIGNED_UPLOAD_URL_TTL_MINUTES` | `15` | Lifetime of the signed upload URL returned by MCP / API media upload flows. (`MCP_UPLOAD_URL_TTL_MINUTES` remains as a legacy fallback.) |
Signed uploads are rate-limited per workspace and per IP (`MEDIA_SIGNED_UPLOAD_PER_WORKSPACE_PER_MINUTE`, `MEDIA_SIGNED_UPLOAD_PER_IP_PER_MINUTE`). If you raise the video limit, also bump PHP's `upload_max_filesize` / `post_max_size` and any reverse-proxy body-size cap to match.
## File Storage
TryPost supports local public storage and S3-compatible cloud storage.
The application default is **`public`** (`FILESYSTEM_DISK=public`). Use object storage (S3 / R2 / Spaces) when you outgrow local disk or run multiple app instances.
### Public Disk
Stores files in `storage/app/public` and serves them directly from `${APP_URL}/storage`.
```env theme={null}
FILESYSTEM_DISK=public
```
Create the symlink from `public/storage` to `storage/app/public` (once per install):
```bash theme={null}
php artisan storage:link
```
Only needed for the `public` disk. Object storage (`s3`, `r2`, `spaces`) does not use this symlink.
See Laravel's [public disk documentation](https://laravel.com/docs/13.x/filesystem#the-public-disk) for more details.
### AWS S3
```env theme={null}
FILESYSTEM_DISK=s3
AWS_ACCESS_KEY_ID=your_key
AWS_SECRET_ACCESS_KEY=your_secret
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your_bucket
AWS_URL=https://your-bucket.s3.amazonaws.com
```
### Cloudflare R2
```env theme={null}
FILESYSTEM_DISK=r2
R2_ACCESS_KEY_ID=your_key
R2_SECRET_ACCESS_KEY=your_secret
R2_ENDPOINT=https://your-account.r2.cloudflarestorage.com
R2_REGION=auto
R2_BUCKET=your_bucket
R2_URL=https://your-custom-domain.com
```
### DigitalOcean Spaces
```env theme={null}
FILESYSTEM_DISK=spaces
SPACES_ACCESS_KEY_ID=your_key
SPACES_SECRET_ACCESS_KEY=your_secret
SPACES_ENDPOINT=https://nyc3.digitaloceanspaces.com
SPACES_REGION=nyc3
SPACES_BUCKET=your_bucket
```
### Other S3-Compatible Storage
Any other S3-compatible storage (MinIO, Backblaze B2, etc.) can be used with the `s3` disk configuration.
## Mail
TryPost sends transactional emails for post notifications, team invites, account alerts, and authentication (email verification, password reset). A working mail configuration is required.
### SendKit (recommended)
[SendKit](https://sendkit.dev?utm_source=trypost\&utm_medium=docs\&utm_campaign=self-hosted) is TryPost's official mailer — built by the same team and wired in as the default. Every new SendKit account is free and includes **3,000 transactional emails per month**, which is plenty for a self-hosted TryPost instance handling post notifications, team invites, and account alerts.
1. Create a free account at [sendkit.dev](https://sendkit.dev?utm_source=trypost\&utm_medium=docs\&utm_campaign=self-hosted)
2. Copy your API key from the SendKit dashboard
3. Drop it into your `.env`:
```env theme={null}
MAIL_MAILER=sendkit
SENDKIT_API_KEY=sk_your_sendkit_api_key
MAIL_FROM_ADDRESS="notifications@yourdomain.com"
MAIL_FROM_NAME="${APP_NAME}"
```
That's it — no DNS warm-up, no domain verification dance to get the first emails out, and the free tier doesn't expire.
### SMTP
If you'd rather use your own SMTP server:
```env theme={null}
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
MAIL_USERNAME=your_username
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="notifications@yourdomain.com"
MAIL_FROM_NAME="${APP_NAME}"
```
### Emails sent by TryPost
| Email | When it's sent |
| ------------------------ | -------------------------------------------------------------- |
| **Post published** | A scheduled post was successfully published |
| **Post failed** | A post failed to publish on one or more platforms |
| **Account disconnected** | A social account lost its authorization and needs reconnection |
| **Connections check** | Multiple accounts in a workspace were found disconnected |
| **Workspace invite** | A team member is invited to join a workspace |
| **Email verification** | A new user needs to verify their email address |
| **Password reset** | A user requests a password reset |
## WebSockets (Reverb)
TryPost uses [Laravel Reverb](https://laravel.com/docs/reverb) for real-time updates (live post status, notifications).
```env theme={null}
REVERB_APP_ID=1001
REVERB_APP_KEY=your-reverb-key
REVERB_APP_SECRET=your-reverb-secret
# Public hostname / port the browser will connect to
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http
# Bind address the Reverb server listens on (defaults to 0.0.0.0:8080)
REVERB_SERVER_HOST=0.0.0.0
REVERB_SERVER_PORT=8080
VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
```
| Variable | Description |
| -------------------- | ----------------------------------------------------------------------- |
| `REVERB_APP_ID` | Unique application ID |
| `REVERB_APP_KEY` | Application key for client connections |
| `REVERB_APP_SECRET` | Secret key for server-side authentication |
| `REVERB_HOST` | Public hostname the **browser** uses to connect (advertised to clients) |
| `REVERB_PORT` | Public port the browser connects to |
| `REVERB_SCHEME` | Protocol: `http` or `https` |
| `REVERB_SERVER_HOST` | Bind address the Reverb **server** listens on. Defaults to `0.0.0.0`. |
| `REVERB_SERVER_PORT` | Port the Reverb server listens on. Defaults to `8080`. |
In production, set `REVERB_SCHEME=https`, set `REVERB_HOST` to your real public domain (e.g. `app.example.com`), and use a reverse proxy to handle SSL termination for WebSocket connections. `REVERB_SERVER_HOST` stays `0.0.0.0` so the daemon listens on all interfaces inside the box.
## Social Platforms
Each social platform requires API credentials from its developer portal. See each [platform guide](/) for step-by-step setup.
**Bluesky** and **Mastodon** don't require API credentials — they work out of the box. **Telegram** uses a single shared bot you create with [@BotFather](https://t.me/BotFather), and **Discord** uses a single shared bot application from the [Discord Developer Portal](https://discord.com/developers/applications) — neither is a per-user OAuth app. See their credentials below.
### All platform credentials
```env theme={null}
# LinkedIn (https://developer.linkedin.com)
LINKEDIN_CLIENT_ID=
LINKEDIN_CLIENT_SECRET=
LINKEDIN_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin/callback"
LINKEDIN_PAGE_CLIENT_REDIRECT="${APP_URL}/accounts/linkedin-page/callback"
# X / Twitter (https://developer.twitter.com)
X_CLIENT_ID=
X_CLIENT_SECRET=
X_CLIENT_REDIRECT="${APP_URL}/accounts/x/callback"
# Facebook (https://developers.facebook.com)
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=
FACEBOOK_CLIENT_REDIRECT="${APP_URL}/accounts/facebook/callback"
# Instagram (https://developers.facebook.com)
INSTAGRAM_CLIENT_ID=
INSTAGRAM_CLIENT_SECRET=
INSTAGRAM_CLIENT_REDIRECT="${APP_URL}/accounts/instagram/callback"
# Threads (https://developers.facebook.com)
THREADS_CLIENT_ID=
THREADS_CLIENT_SECRET=
THREADS_CLIENT_REDIRECT="${APP_URL}/accounts/threads/callback"
# TikTok (https://developers.tiktok.com)
TIKTOK_CLIENT_ID=
TIKTOK_CLIENT_SECRET=
TIKTOK_CLIENT_REDIRECT="${APP_URL}/accounts/tiktok/callback"
# Google / YouTube (https://console.cloud.google.com)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CLIENT_REDIRECT="${APP_URL}/accounts/youtube/callback"
# Pinterest (https://developers.pinterest.com)
PINTEREST_CLIENT_ID=
PINTEREST_CLIENT_SECRET=
PINTEREST_CLIENT_REDIRECT="${APP_URL}/accounts/pinterest/callback"
# Telegram (single shared bot — create one with https://t.me/BotFather)
TELEGRAM_BOT_TOKEN=
TELEGRAM_BOT_USERNAME=
TELEGRAM_WEBHOOK_SECRET=
# Discord (single shared bot app — https://discord.com/developers/applications)
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
DISCORD_BOT_TOKEN=
DISCORD_CLIENT_REDIRECT="${APP_URL}/accounts/discord/callback"
```
Telegram delivers `/connect` commands and reactions over a webhook. After setting the variables above, register it with `php artisan telegram:set-webhook`. The webhook URL is `{APP_URL}/telegram/webhook`, so `APP_URL` must be a public HTTPS URL Telegram can reach — re-run the command whenever it changes. The bot must be added as an **administrator** of each channel or group it publishes to.
Discord uses a single shared bot application (create one in the [Discord Developer Portal](https://discord.com/developers/applications)). Use the OAuth2 **Client Secret** — not the application's **Public Key** — for `DISCORD_CLIENT_SECRET`, and add the redirect URL above under **OAuth2 → Redirects**. The bot invite permissions and scopes are configurable via `DISCORD_PERMISSIONS` (default `248832`) and `DISCORD_SCOPES` (default `bot,identify,guilds`). See the [Discord platform guide](/platforms/discord) for the full setup.
### Enabling/disabling platforms
You can selectively enable or disable platforms without removing their credentials. This is useful when API credentials are pending approval or temporarily revoked.
```env theme={null}
LINKEDIN_ENABLED=true
LINKEDIN_PAGE_ENABLED=true
X_ENABLED=true
TIKTOK_ENABLED=true
YOUTUBE_ENABLED=true
FACEBOOK_ENABLED=true
INSTAGRAM_ENABLED=true
INSTAGRAM_FACEBOOK_ENABLED=true
THREADS_ENABLED=true
PINTEREST_ENABLED=true
BLUESKY_ENABLED=true
MASTODON_ENABLED=true
TELEGRAM_ENABLED=true
DISCORD_ENABLED=true
```
Set any platform to `false` to hide it from the UI. All platforms are enabled by default.
## Social login
TryPost supports sign in with **Google** and **GitHub**. Both providers are off by default — set the corresponding `*_AUTH_ENABLED` flag and provide credentials to surface the buttons on the login/register pages.
### Google
Google login reuses the OAuth credentials from the YouTube integration, with a separate callback URL.
```env theme={null}
GOOGLE_AUTH_ENABLED=true
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_AUTH_CALLBACK="${APP_URL}/auth/google/callback"
```
In the [Google Cloud Console](https://console.cloud.google.com), add both callback URLs to your OAuth 2.0 client:
* `https://your-domain.com/accounts/youtube/callback` (YouTube connection)
* `https://your-domain.com/auth/google/callback` (Google login)
### GitHub
```env theme={null}
GITHUB_AUTH_ENABLED=true
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
GITHUB_AUTH_CALLBACK="${APP_URL}/auth/github/callback"
```
Register a new OAuth App at [github.com/settings/developers](https://github.com/settings/developers) and set the **Authorization callback URL** to `${APP_URL}/auth/github/callback`.
## AI features (optional)
The Generate / Review / Create AI flows in the post editor need a configured text-generation provider. Without one, the AI buttons stay disabled. See [AI Providers](/self-hosting/ai) for the full list of supported providers, per-provider model overrides, and OpenRouter-specific gotchas.
```env theme={null}
AI_TEXT_PROVIDER=openai
OPENAI_API_KEY=sk-...
```
## Asset library (Unsplash & Giphy)
The **Assets** page exposes optional Unsplash and Giphy tabs. Configure these to enable them:
```env theme={null}
UNSPLASH_ACCESS_KEY=
UNSPLASH_SECRET_KEY=
GIPHY_API_KEY=
```
Without these keys, the Library tab still works — only the stock-photo and GIF tabs are hidden.
## Analytics (optional)
### PostHog
```env theme={null}
POSTHOG_ENABLED=true
POSTHOG_API_KEY=phc_your_key
POSTHOG_HOST=https://us.i.posthog.com
VITE_POSTHOG_API_KEY="${POSTHOG_API_KEY}"
VITE_POSTHOG_HOST="${POSTHOG_HOST}"
```
`POSTHOG_ENABLED` gates the entire integration — without it set to `true`, the keys are ignored.
### Google Tag Manager
```env theme={null}
GTM_ID=GTM-XXXXXXX
```
Both are optional and only needed if you want to track usage analytics.
## Horizon (Queue Dashboard)
[Laravel Horizon](https://laravel.com/docs/horizon) provides a dashboard to monitor your queues. Access it at `/horizon`.
```env theme={null}
HORIZON_ALLOWED_EMAILS=admin@example.com,dev@example.com
```
| Variable | Description |
| ------------------------ | ---------------------------------------------------------------------- |
| `HORIZON_ALLOWED_EMAILS` | Comma-separated list of emails allowed to access Horizon in production |
If not set, Horizon dashboard will be inaccessible in non-local environments.
## Advanced
```env theme={null}
TRYPOST_ALLOW_PRIVATE_NETWORK=false
```
| Variable | Default | Description |
| ------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TRYPOST_ALLOW_PRIVATE_NETWORK` | `false` | When `true`, allows HTTP requests from TryPost (RSS, webhooks, URL media fetches, etc.) to private/internal network addresses. Keep `false` unless you intentionally need that for a private network install. |
## Next Steps
* [Connect your social accounts](/)
* [Create your first post](/getting-started/quickstart)
# Docker
Source: https://docs.trypost.it/self-hosting/docker
Run TryPost with Docker Compose
# Docker
TryPost ships two Compose files:
| File | Purpose |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `compose.prod.yaml` | **Self-hosting in production** — pulls `ghcr.io/trypostit/trypost:latest`, Postgres, Redis, Horizon, Reverb, scheduler, optional Caddy TLS |
| `compose.yaml` | Local development — builds from source, bind-mounts the repo, Mailpit, Vite |
## Requirements
* Docker Desktop (Mac, Windows) or Docker Engine (Linux)
* Docker Compose v2
***
## Production self-hosting
No local PHP/Node install required. The published image includes the built frontend.
### What you do vs what the container does
| You (once) | Entrypoint / Supervisor (automatic) |
| --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Set `APP_KEY`, `APP_URL`, DB passwords, `REVERB_APP_SECRET`, **`PASSPORT_PRIVATE_KEY` / `PASSPORT_PUBLIC_KEY`** | Wait for Postgres, run migrations |
| `docker compose … up -d` | Use Passport keys from env, `storage:link` if missing, ensure Personal Access Client |
| `db:seed` (plans) + `UserSeeder` | Cache config/routes/views/events |
| Optional: Caddy TLS profile | Start nginx, php-fpm, **Horizon**, **Reverb**, **schedule:work** |
### 1. Clone the repository
```bash theme={null}
git clone https://github.com/trypostit/trypost.git
cd trypost
```
You only need the repo for `compose.prod.yaml` (and optionally `Caddyfile`). The app image comes from GHCR.
### 2. Generate an app key and edit the compose file
```bash theme={null}
docker compose -f compose.prod.yaml run --rm app php artisan key:generate --show
```
Paste the value into `APP_KEY` in `compose.prod.yaml`. Also set:
* `APP_URL` — your public URL (e.g. `https://post.yourdomain.com`)
* `DB_PASSWORD` / `POSTGRES_PASSWORD` (must match)
* `REVERB_APP_SECRET`
* `PASSPORT_PRIVATE_KEY` / `PASSPORT_PUBLIC_KEY` — **required in production** (file keys under `storage/` are not on a persisted volume). Generate once:
```bash theme={null}
docker compose -f compose.prod.yaml run --rm app php artisan passport:keys --show
```
Paste the PEM contents into the compose env vars (use `\n` for newlines). Do not regenerate after users have issued API or MCP tokens.
Leave `REVERB_APP_KEY` as `trypost-reverb-key` unless you rebuild the image — it must match the key baked into the published frontend bundle.
Queues, cache, session, and broadcast are already set to Redis / Reverb / database in `compose.prod.yaml`. Configure **mail** (defaults to `log`) and optional social/AI credentials in the same file — see [Configuration](/self-hosting/configuration).
### 3. Start the stack
```bash theme={null}
docker compose -f compose.prod.yaml up -d
```
On first boot the entrypoint:
* waits for Postgres
* runs migrations
* creates `storage:link` if missing (unused if you later switch to S3 / R2 / Spaces)
* loads Passport keys from `PASSPORT_PRIVATE_KEY` / `PASSPORT_PUBLIC_KEY` (exits if unset in production)
* ensures the Passport Personal Access Client (`PassportSeeder`)
* caches config / routes / views / events
* starts nginx, php-fpm, Horizon, Reverb, and `schedule:work`
It does **not** create the admin user or plan rows — next step.
### 4. Seed plans and admin user (one-time)
```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan db:seed
docker compose -f compose.prod.yaml exec app php artisan db:seed --class=UserSeeder
```
| Seeder | Purpose |
| ---------------- | -------------------------------------------------------- |
| `DatabaseSeeder` | Plan rows (+ idempotent Passport Personal Access Client) |
| `UserSeeder` | Admin (`admin@trypost.it` / `password`) |
Change the admin password on first login. Extra accounts come from workspace invites (Settings → Members).
Production Passport **keys** come from `PASSPORT_*` env vars (set before `up`). The entrypoint also runs `PassportSeeder` so API-key creation works without a manual client step. MCP uses OAuth (`mcp:use`) with those same keys — not API-key Bearer headers. Still run `db:seed` / `UserSeeder` for plans and the admin user.
### 5. Optional HTTPS with Caddy
1. Point DNS at the host
2. Set `APP_URL` to `https://your-domain` and `APP_DOMAIN` on the `caddy` service
3. Start with the proxy profile:
```bash theme={null}
docker compose -f compose.prod.yaml --profile proxy up -d
```
Without the profile, the app listens on `http://localhost:8000`.
### 6. Verify
```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml logs -f app
```
1. Open `APP_URL` and sign in as admin
2. Create an API key (Settings → API Keys) — proves Passport keys + Personal Access Client exist
3. Connect an MCP client via OAuth (Settings → MCP), if you use assistants
4. Schedule a post — Horizon must show as running
### Access
| Service | URL |
| ------- | --------------------------------------------------------------------- |
| TryPost | `http://localhost:8000` (or your `APP_URL`) |
| Reverb | port `8080`, proxied under `/app/` and `/apps/` by in-container nginx |
### Reverb on a custom domain
The published image bakes `VITE_REVERB_*` at build time (`localhost:8080`). For HTTPS on your domain, rebuild:
```bash theme={null}
docker build \
--build-arg VITE_REVERB_HOST=post.yourdomain.com \
--build-arg VITE_REVERB_PORT=443 \
--build-arg VITE_REVERB_SCHEME=https \
--build-arg VITE_REVERB_APP_KEY=trypost-reverb-key \
-t trypost-app:custom \
-f docker/Dockerfile \
--target production \
.
```
Point `compose.prod.yaml` at that image. Changing only runtime `REVERB_HOST` / `PORT` / `SCHEME` does not update the browser client.
### Common production commands
```bash theme={null}
docker compose -f compose.prod.yaml logs -f app
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml exec app php artisan migrate --force
docker compose -f compose.prod.yaml down
```
***
## Local development
Use `compose.yaml` when you want a bind-mounted source tree.
### 1. Clone and start
```bash theme={null}
git clone https://github.com/trypostit/trypost.git
cd trypost
docker compose up -d
```
On first boot the entrypoint can seed `.env` from `docker/.env.docker.example`, install Composer/npm deps if missing, generate `APP_KEY`, migrate, `storage:link`, and `passport:keys`. Horizon, Reverb, the scheduler, and Vite start via Supervisor.
For local media URLs, set `FILESYSTEM_DISK=public` in `.env` (the Docker example ships with `local`) and ensure `storage:link` ran.
### 2. Seed admin + Passport client
```bash theme={null}
docker compose exec app php artisan db:seed
docker compose exec app php artisan db:seed --class=UserSeeder
```
Or wipe-and-rebuild on a brand-new database:
```bash theme={null}
docker compose exec app php artisan migrate:fresh --seed
docker compose exec app php artisan db:seed --class=UserSeeder
```
`migrate:fresh` **drops every table**.
| Field | Value |
| -------- | ------------------ |
| Email | `admin@trypost.it` |
| Password | `password` |
### Access (dev)
| Service | URL |
| -------- | --------------------------------------- |
| TryPost | `http://localhost:8000` (or `APP_PORT`) |
| Mailpit | `http://localhost:8025` |
| Vite HMR | `http://localhost:5173` |
| Postgres | `localhost:5432` |
| Redis | `localhost:6379` |
| Reverb | `localhost:8080` |
### Common development commands
```bash theme={null}
docker compose up -d
docker compose down
docker compose logs -f app
docker compose exec app php artisan
```
## Troubleshooting
### Port conflicts
```env theme={null}
APP_PORT=8080
FORWARD_DB_PORT=5433
FORWARD_MAILPIT_UI_PORT=8026
```
### Permission issues (Linux)
```bash theme={null}
sudo chown -R $USER: .
```
### Database connection refused
```bash theme={null}
docker compose ps
# or
docker compose -f compose.prod.yaml ps
```
### API key creation fails with "Personal access client not found"
Seed the Passport client:
```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan db:seed
```
### Horizon not processing jobs
```bash theme={null}
docker compose -f compose.prod.yaml exec app php artisan horizon:status
docker compose -f compose.prod.yaml logs app | grep -i horizon
```
## Next steps
* [Configuration](/self-hosting/configuration) — mail, storage, social OAuth, AI
* [Production setup](/self-hosting/production) — bare-metal Nginx / Supervisor if you are not using `compose.prod.yaml`
* [Installation](/self-hosting/installation) — clone-and-install without Docker
# Installation
Source: https://docs.trypost.it/self-hosting/installation
Install TryPost on your server
# Installation
Step-by-step install on a bare-metal server or VM. Prefer containers? Use [Docker production](/self-hosting/docker#production-recommended-for-self-hosting) instead.
## Requirements
* PHP 8.2+ (with the extensions listed in [Requirements](/self-hosting/requirements))
* Node.js 20+
* PostgreSQL 14+ or MySQL 8+
* Redis 6+
* Composer 2.x
## Install
### 1. Clone the repository
```bash theme={null}
git clone https://github.com/trypostit/trypost.git
cd trypost
```
### 2. Install PHP and Node dependencies
```bash theme={null}
composer install
cp .env.example .env
php artisan key:generate
npm install
```
`php artisan key:generate` writes `APP_KEY` into `.env`. Without it the app will not boot.
### 3. Configure the environment
Edit `.env` **before** migrating. Minimum for a working self-hosted instance:
```env theme={null}
APP_URL=https://your-domain.com
APP_ENV=production
APP_DEBUG=false
SELF_HOSTED=true
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=trypost
DB_USERNAME=postgres
DB_PASSWORD=your_password
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=database
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=public
```
| Block | Why |
| ----------------------------- | -------------------------------------------------------------------------- |
| `APP_KEY` | Already set by `key:generate` — encrypts sessions, cookies, encrypted data |
| Database | Where posts, users, and Passport clients live |
| Redis + queue/cache drivers | Horizon publishes posts and runs automations from Redis queues |
| `BROADCAST_CONNECTION=reverb` | Live dashboard updates |
| `FILESYSTEM_DISK=public` | Local media on disk (use `s3` / `r2` / `spaces` for object storage) |
| `SELF_HOSTED=true` | Disables Stripe billing and public `/register` |
Also configure **mail** (invites / password reset) and optional social OAuth / AI keys — see [Configuration](/self-hosting/configuration).
Prefer a one-liner for deps? `composer setup` runs install + `key:generate` + migrate + npm build, but it migrates **before** you edit `.env`. Safer path for a real server: follow the steps above (configure `.env`, then migrate yourself).
### 4. Run migrations
```bash theme={null}
php artisan migrate --force
```
### 5. Passport keys (and local storage symlink)
Passport needs an RSA key pair on disk to **sign and verify** API / MCP tokens:
```bash theme={null}
php artisan passport:keys
```
Run once. Do **not** regenerate later unless you intend to invalidate every existing API token.
If you keep `FILESYSTEM_DISK=public`, also create the public media symlink:
```bash theme={null}
php artisan storage:link
```
Skip `storage:link` when using object storage (`s3`, `r2`, `spaces`) — those disks serve from the bucket URL.
### 6. Seed Passport client and admin user
Self-hosted installs close `/register`. Bootstrap the database once:
```bash theme={null}
php artisan db:seed
php artisan db:seed --class=UserSeeder
```
| Seeder | What it creates |
| ---------------------------- | ------------------------------------------------------------------------------ |
| `DatabaseSeeder` (`db:seed`) | **Passport Personal Access Client** (required to issue API tokens) + plan rows |
| `UserSeeder` | Admin account |
Passport is two independent pieces:
1. **Keys** (`passport:keys`) — files on disk
2. **Personal Access Client** (`db:seed`) — row in `oauth_clients`
API keys in Settings need **both**. MCP OAuth uses the same Passport keys (not API-key Bearer headers).
Admin credentials — **change the password immediately on first login**:
| Field | Value |
| -------- | ------------------ |
| Email | `admin@trypost.it` |
| Password | `password` |
Further accounts come from workspace invites (Settings → Members).
Only on a brand-new empty database you may use `php artisan migrate:fresh --seed && php artisan db:seed --class=UserSeeder`. `migrate:fresh` **drops every table**.
### 7. Build the frontend
```bash theme={null}
npm run build
```
### 8. Run the app
**Local / smoke test** (one terminal):
```bash theme={null}
composer dev
```
This starts `php artisan serve`, a queue listener, log tail, and Vite. Open `http://localhost:8000` and sign in with the admin user.
**Production** — do **not** use `composer dev`. You need long-running processes:
| Process | Command | Purpose |
| --------------------- | -------------------------------------------------------------- | -------------------------------------------------- |
| Web (PHP-FPM + Nginx) | see [Production](/self-hosting/production#nginx-configuration) | Serve HTTP |
| **Horizon** | `php artisan horizon` | Queues — publish posts, notifications, automations |
| **Reverb** | `php artisan reverb:start` | WebSockets |
| **Scheduler** | cron → `php artisan schedule:run` | Due posts, token refresh, automations |
Full Supervisor configs, Nginx (including Reverb `/app/` + `/apps/`), SSL, and caches: [Production setup](/self-hosting/production).
## End-to-end checklist
| Step | Done when |
| ------------------ | ------------------------------------------ |
| `APP_KEY` set | `key:generate` ran |
| Database reachable | `.env` `DB_*` correct |
| Queues on Redis | `QUEUE_CONNECTION=redis` + Redis up |
| Migrations applied | `php artisan migrate` |
| Passport keys | `storage/oauth-private.key` exists |
| Passport client | `php artisan db:seed` ran |
| Admin user | `UserSeeder` ran; you can log in |
| Media (local) | `storage:link` if `FILESYSTEM_DISK=public` |
| Frontend built | `npm run build` |
| Horizon running | posts actually publish |
| Reverb + scheduler | live UI + cron jobs |
| Mail configured | invites / password reset work |
## Verify
1. Sign in at `APP_URL` with the admin credentials
2. Create an API key under workspace Settings → API Keys (fails with 500 if Passport keys or client are missing)
3. Connect an MCP client via OAuth (Settings → MCP), or call the REST API with the key above
4. Upload a media file (404s if you use `public` disk without `storage:link`)
5. Schedule a test post and confirm Horizon processes it
## Next steps
* [Configuration](/self-hosting/configuration) — mail, storage, social OAuth, AI
* [Production setup](/self-hosting/production) — Nginx, SSL, Horizon, Reverb, cron
* [Docker](/self-hosting/docker) — same stack via Compose
# Self-Hosting
Source: https://docs.trypost.it/self-hosting/overview
Deploy TryPost on your own infrastructure
TryPost is open-source and can be self-hosted on your own server. This section covers everything you need to install, configure, and run TryPost in production.
Don't want to manage infrastructure? Use [TryPost Cloud](https://app.trypost.it/register) — no setup required, free to get started.
## Pick a path
| Path | Best when | Guide |
| ---------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| **Docker production** | You want the published image with Horizon, Reverb, and the scheduler already wired | [Docker → Production](/self-hosting/docker#production-recommended-for-self-hosting) |
| **Bare metal** | You install PHP/Node yourself on a VPS | [Installation](/self-hosting/installation) then [Production](/self-hosting/production) |
| **Docker development** | You contribute to the codebase locally | [Docker → Local development](/self-hosting/docker#local-development) |
Every path needs the same building blocks:
1. **App key** (`APP_KEY`)
2. **Database** configured and migrated
3. **Redis** + `QUEUE_CONNECTION=redis` (Horizon)
4. **Passport keys** + **Personal Access Client** (REST API keys; MCP OAuth also uses the keys)
5. **Admin user** (`UserSeeder`)
6. Long-running **Horizon**, **Reverb**, and the **scheduler** (Docker prod starts these for you)
## Deployment options
Step-by-step guide to install TryPost on a server.
Deploy with Docker Compose — local development or production.
## Configuration & production
Environment variables, database, mail, storage, and more.
Nginx, SSL, Horizon, cron, and WebSockets (bare metal).
## Server requirements
See [Requirements](/self-hosting/requirements) for the full list of software and hardware requirements.
# Production Setup
Source: https://docs.trypost.it/self-hosting/production
Deploy TryPost to a production environment
# Production Setup
This guide is for **bare metal / a VM** after you have completed [Installation](/self-hosting/installation) (app key, `.env`, migrations, Passport keys, seed, frontend build).
Prefer containers? Use [`compose.prod.yaml`](/self-hosting/docker#production-recommended-for-self-hosting) — it already runs Horizon, Reverb, and the scheduler for you.
## Before you continue
Confirm the install checklist is done:
* [ ] `APP_KEY` set
* [ ] Database migrated
* [ ] `QUEUE_CONNECTION=redis` and Redis reachable
* [ ] Passport keys present (`PASSPORT_*` env for Docker prod, or `php artisan passport:keys` on bare metal)
* [ ] `php artisan db:seed` + `UserSeeder` ran
* [ ] `storage:link` ran **if** `FILESYSTEM_DISK=public`
* [ ] `npm run build` completed
## Environment (production values)
```env theme={null}
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com
SELF_HOSTED=true
QUEUE_CONNECTION=redis
CACHE_STORE=redis
SESSION_DRIVER=database
BROADCAST_CONNECTION=reverb
FILESYSTEM_DISK=public
```
See [Configuration](/self-hosting/configuration) for mail, object storage, and social OAuth.
## Optimizations
Run these commands after deployment:
```bash theme={null}
composer install --optimize-autoloader --no-dev
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
```
## Queue Worker
TryPost uses queues for critical background processing:
* **Publishing posts** to social platforms at scheduled times
* **Sending notifications** (email and in-app)
* **Verifying social account connections**
* **Processing analytics events**
* **Running automations**
Without a running queue worker, scheduled posts will not be published. Use Supervisor to keep Horizon running.
### Install Supervisor
```bash theme={null}
# Ubuntu/Debian
sudo apt install supervisor
# CentOS/RHEL
sudo yum install supervisor
```
### Configure Supervisor
Create `/etc/supervisor/conf.d/trypost-worker.conf`:
```ini theme={null}
[program:trypost-horizon]
process_name=%(program_name)s
command=php /var/www/trypost/artisan horizon
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/trypost/storage/logs/horizon.log
stopwaitsecs=3600
```
Then start it:
```bash theme={null}
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start trypost-horizon
```
## Nginx Configuration
Example Nginx configuration:
```nginx theme={null}
server {
listen 80;
listen [::]:80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your-domain.com;
root /var/www/trypost/public;
index index.php;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
client_max_body_size 1G;
# Reverb WebSocket (Pusher protocol) + HTTP API
location /app/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
location /apps/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
# Replace with the FPM socket for the PHP version you installed (e.g. php8.2-fpm.sock, php8.3-fpm.sock, php8.4-fpm.sock).
fastcgi_pass unix:/var/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
```
Both `/app/` (browser WebSocket) and `/apps/` (Pusher HTTP API) must be proxied to Reverb. Omitting `/apps/` causes flaky real-time updates.
## SSL Certificate
Use Let's Encrypt for free SSL:
```bash theme={null}
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
```
## Scheduled Tasks (Cron)
TryPost uses Laravel's task scheduler for several recurring jobs. The scheduler **must** run every minute or scheduled posts won't publish, automations won't fire, expiring tokens won't refresh, and stuck posts won't recover.
| Command | Schedule | Purpose |
| -------------------------------- | ---------------- | --------------------------------------------------------------- |
| `posts:process-scheduled` | every minute | Dispatches `PublishPost` jobs for posts that are due |
| `automation:fire-schedule` | every minute | Fires time-based automation triggers |
| `automation:process-delays` | every minute | Advances automation runs waiting on delay nodes |
| `automation:recover-stuck-runs` | every 5 minutes | Recovers automation runs stuck mid-flight |
| `automation:prune-dry-runs` | every 10 minutes | Prunes dry-run automation history |
| `social:refresh-expiring-tokens` | every 15 minutes | Refreshes OAuth tokens that are about to expire |
| `social:recover-stuck-posts` | every 30 minutes | Detects posts stuck in `publishing` and retries or fails them |
| `social:check-connections` | daily | Verifies every connected social account still has a valid token |
Add the Laravel scheduler to cron:
```bash theme={null}
crontab -e
```
Add this line:
```
* * * * * cd /var/www/trypost && php artisan schedule:run >> /dev/null 2>&1
```
This is required. Without the cron job, scheduled posts will not be published automatically, automations will stall, and social tokens will expire silently.
## WebSocket Server (Reverb)
TryPost uses [Laravel Reverb](https://laravel.com/docs/reverb) for real-time updates in the dashboard (live post status changes, notifications). Add a Supervisor config to keep it running:
Create `/etc/supervisor/conf.d/trypost-reverb.conf`:
```ini theme={null}
[program:trypost-reverb]
process_name=%(program_name)s
command=php /var/www/trypost/artisan reverb:start --host=0.0.0.0 --port=8080
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/www/trypost/storage/logs/reverb.log
```
Then reload Supervisor:
```bash theme={null}
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start trypost-reverb
```
Proxy `/app/` and `/apps/` through Nginx as shown above.
## File Permissions
Set correct permissions:
```bash theme={null}
sudo chown -R www-data:www-data /var/www/trypost
sudo chmod -R 755 /var/www/trypost
sudo chmod -R 775 /var/www/trypost/storage
sudo chmod -R 775 /var/www/trypost/bootstrap/cache
```
Keep `storage/oauth-private.key` readable only by the app user (typically mode `600`).
## Checklist
Make sure all of these are in place in production:
| Item | How | Purpose |
| ------------------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------- |
| **App key** | `php artisan key:generate` | Encrypts sessions / cookies |
| **Passport keys** | `PASSPORT_PRIVATE_KEY` / `PASSPORT_PUBLIC_KEY` (or `php artisan passport:keys` once on bare metal) | Sign/verify API & MCP tokens |
| **Passport client** | `php artisan db:seed` (once; Docker prod also runs `PassportSeeder` on boot) | Issue personal access tokens |
| **Admin user** | `php artisan db:seed --class=UserSeeder` (once) | First login |
| **Storage link** | `php artisan storage:link` (once, if `FILESYSTEM_DISK=public`) | Local media URLs — skip for S3 / R2 / Spaces |
| **Redis** | `QUEUE_CONNECTION=redis` + Redis up | Queues, cache, schedule locks |
| **Mail** | SMTP or SendKit | Invites, password reset, alerts |
| **Horizon** | `php artisan horizon` (Supervisor) | Publishes posts, notifications, automations |
| **Reverb** | `php artisan reverb:start` (Supervisor) | Real-time WebSocket updates |
| **Scheduler** | cron → `schedule:run` every minute | Due posts, token refresh, automations |
If Horizon, Reverb, or the scheduler are not running, TryPost will not function correctly. Scheduled posts won't publish, notifications won't send, and the dashboard won't update in real time.
## Monitoring
Consider setting up monitoring for:
* Server health (CPU, memory, disk)
* Application errors (Laravel logs in `storage/logs/`)
* Queue status (Horizon dashboard at `/horizon`)
* WebSocket connections (Reverb logs in `storage/logs/reverb.log`)
## Backups
Regularly backup:
* Database
* `.env` file
* Passport keys (`storage/oauth-*.key` or `PASSPORT_*` env) — losing them invalidates API keys and MCP OAuth grants
* Uploaded media (if using local storage)
# Requirements
Source: https://docs.trypost.it/self-hosting/requirements
Server requirements for self-hosting TryPost
# Server Requirements
This guide covers the requirements for self-hosting TryPost.
## Minimum Requirements
| Component | Minimum | Recommended |
| --------- | ------- | --------------------------------- |
| CPU | 1 core | 2+ cores |
| RAM | 1 GB | 2+ GB |
| Storage | 10 GB | 20+ GB (depends on media storage) |
## Software Requirements
### Required
* **PHP 8.2+** with extensions:
* BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, XML
* **pcntl** (required by Horizon)
* **intl**, **zip**, **exif**, **sockets**
* GD or Imagick (for image processing)
* Redis extension (or use Predis via Composer — the Redis **service** is still required)
* **Composer** 2.x
* **Node.js** 20+ and npm (Vite 7 + Vue 3 require Node 20 or newer) — not needed if you only run the published Docker image
* **PostgreSQL** 14+ or **MySQL** 8+
* **Redis** 6+
### Optional
* **Nginx** or **Apache** (for production bare-metal)
* **Supervisor** (for Horizon, Reverb, and related workers)
* **SSL certificate** (Let's Encrypt recommended)
## PHP Configuration
Recommended `php.ini` settings:
```ini theme={null}
upload_max_filesize = 1G
post_max_size = 1G
memory_limit = 512M
max_execution_time = 600
```
Match these to your reverse-proxy body-size limit (`client_max_body_size` in Nginx, etc.). The published Docker image uses a 1G cap.
## Supported Operating Systems
TryPost can run on any OS that supports the required software:
* Ubuntu 22.04+ (recommended)
* Debian 11+
* CentOS/RHEL 8+
* macOS (for development)
* Windows with WSL2 (for development)
## Cloud Providers
TryPost works well on any cloud provider. We recommend [Hetzner Cloud](https://hetzner.cloud/?ref=V4djx1Vt7Mm7) for the best price-to-performance ratio.
Get **20 EUR free credits** on [Hetzner Cloud](https://hetzner.cloud/?ref=V4djx1Vt7Mm7) — enough to run TryPost for several months on a CX22 instance (2 vCPUs, 4 GB RAM). *This is a referral link — it helps support TryPost development at no extra cost to you.*
Other supported providers:
* DigitalOcean Droplets
* AWS EC2
* Google Cloud Compute
* Linode
* Vultr
## Storage Considerations
Media files (images, videos, documents) can be stored:
* **Locally** — `FILESYSTEM_DISK=public` + `php artisan storage:link`
* **S3** — Amazon S3
* **R2** — Cloudflare R2
* **Spaces** — DigitalOcean Spaces
* **Any S3-compatible** — MinIO, etc.
For production with heavy media usage, object storage is recommended. See [Configuration → File Storage](/self-hosting/configuration#file-storage).
## Next Steps
* [Installation](/self-hosting/installation)
* [Docker Deployment](/self-hosting/docker)
* [Production Setup](/self-hosting/production)
# Support
Source: https://docs.trypost.it/support
Get help with TryPost Cloud and self-hosted deployments
Sometimes things go wrong, or you need a hand.
The **self-hosted** version of TryPost is supported by the community in their free time, on a best-effort basis. Post your question and be patient.
Flexible chat with screenshots and screen sharing — usually the fastest way to get help.
Backup option if you can't use Discord, or prefer an async thread.
## TryPost Cloud — billing
For questions specific to a paid TryPost Cloud subscription (refunds, invoice changes, plan changes, account closure):
* Use the **in-app chat** inside [TryPost Cloud](https://app.trypost.it) — open the chat widget while you're signed in. That's the right place for billing and account issues.
* **GitHub Issues** are for bugs in the open-source app, not for billing.
* Don't share API keys, OAuth secrets, or payment details in Discord or other public channels.
Before opening a billing conversation, check [Plans & billing](/knowledge-base/plans-and-billing) — many billing questions are answered there.
## How to ask for support effectively
Follow this order when you ask for help: **Goal → Environment → Changes → Results**.
### Goal
Explain what you were trying to do:
* *I want to schedule a LinkedIn carousel for Friday at 6pm*
* *I want to run TryPost in Docker on a VPS*
* *I want to connect Instagram via Facebook Business*
### Environment
Share the relevant parts of your environment.
Web app issues — browser and OS:
* *I'm using Chrome on macOS*
* *I'm using Firefox on Windows 11*
Self-hosted / install issues — machine and how you installed:
* *I'm on Ubuntu 24.04 with PHP 8.3, PostgreSQL, and Redis*
* *This is a fresh Docker install on a Hetzner CX22*
* *I'm on TryPost Cloud*
### Changes
What changed before it broke?
* *I updated my browser and now…*
* *I pulled the latest release and now…*
* *This used to work, but now…*
### Results
What happened, and what did you expect?
* *I see a blank screen*
* *I get a `402 Active subscription required` response*
* *The post stays stuck in `publishing`*
* *I expected the Instagram account picker, but got an error*
Include screenshots, logs, or the exact error message when you can. That usually cuts the back-and-forth in half.