> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.algorhythm.app/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.algorhythm.app/_mcp/server.

# Post to a farm profile

POST https://api.algorhythm.app/v1/posts
Content-Type: application/json

Queues a video or slideshow to be posted to the farm profile's TikTok account. The response returns immediately with a `post_id`; the upload + publish runs on the cloud phone in the background and may take several minutes.

**Preconditions:**
- Profile must be `ready` (fully warmed up). Posting before then risks TikTok flagging the account.
- `media_url` must be HTTPS and publicly retrievable from our infrastructure.

**Posts go out at the account's default privacy.** Explicit privacy controls are on the roadmap.

**Cadence:** TikTok bans accounts that post too frequently. We recommend ≤ 1–2 posts per day per profile. We don't enforce a per-profile limit; pacing is your responsibility.

**Idempotency:** not yet supported. Retrying a request that returned 502 may produce a duplicate post. If you retry, watch for the same `media_url` going up twice. Idempotency-Key support is on the roadmap.

**Rate limit:** 30 requests per minute per team.

Reference: https://docs.algorhythm.app/api-reference/posts/create-post-v-1-posts-post

## Authentication

- `X-API-Key` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects a CreatePostRequest.

- `profile_id` (string, required) — The farm profile to post from. Must belong to your team. Posting is only permitted when `status` is `ready`.
- `media_type` (enum, required) — `video` for a single MP4; `slideshow` for a `.zip` of JPGs.
  - Allowed values: `video`, `slideshow`
- `media_url` (string, required) — Publicly retrievable HTTPS URL of the media. For `video`, an MP4 (H.264, ≤ 60s, 9:16 recommended). For `slideshow`, a `.zip` whose entries are named `slide-1.jpg`, `slide-2.jpg`, … (JPG only; PNG/AVIF not supported). Signed URLs are fine as long as they're reachable from the public internet.
- `sound_id` (string, required) — TikTok sound ID to attach to the post. We do not validate that the ID exists.
- `webhook_url` (string, required) — HTTPS URL we'll POST `{event: 'post.published', phone_id, post_url}` to once the post goes live on TikTok, where `post_url` is the live TikTok URL. Fire-and-forget; no retries.
- `title` (string, optional, nullable) — Title shown on the post. Applies to `slideshow` posts only; ignored for `video` posts.
- `description` (string, optional, default: ) — Caption text. **Note:** `@` and `#` characters are stripped server-side — TikTok's automation flow can't reliably enter them. Native hashtag/mention support is on the roadmap.
- `execute_at` (integer, optional, nullable) — Optional scheduled post time as a Unix epoch in **milliseconds**. Must be in the future. Omit to publish immediately.
- `is_live` (boolean, optional, default: false) — `true` publishes the post live to TikTok immediately. `false` (default) saves it as a draft on the device for review. Not a livestream toggle.
- `use_official_sound` (boolean, optional, default: true) — `true` (default) plays the linked `sound_id` as the post's audio at full volume. `false` uses the video's own original audio while still tagging the linked `sound_id` as the official sound, so the two coexist. Applies to `video` posts only; ignored for slideshows.
- `hashtags` (list of string, optional) — Hashtags to append to the post. Enter the words only, without `#` (e.g. `["viral", "trending"]`); we add the `#` and strip any non-alphanumeric characters server-side. Omit or leave empty for none.

## Response

### 200

Successful Response

- `post_id` (string, required) — Opaque ID for the post, stable for its lifetime. Poll `GET /v1/posts/{post_id}` with it to track status and, once live, retrieve the TikTok post ID and URL.

## Errors

### 400 Bad Request Error

Malformed request — bad URL scheme, past `execute_at`, etc.

- `any`

### 404 Not Found Error

Profile not found or not owned by your team.

- `any`

### 409 Conflict Error

Profile is not `ready` yet (or is in a terminal state).

- `any`

### 422 Unprocessable Entity Error

Validation Error

- `detail` (list of ValidationError, optional)

### 429 Too Many Requests Error

Rate limit exceeded.

- `any`

### 502 Bad Gateway Error

Upstream content-server failure.

- `any`

## Types

### ValidationError

- `loc` (list of ValidationErrorLocItems, required)
- `msg` (string, required)
- `type` (string, required)
- `input` (any, optional)
- `ctx` (ValidationErrorCtx, optional)

### ValidationErrorLocItems

- string
- integer

### ValidationErrorCtx

## Examples

### Video post

**Request**

```json
{
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "video",
  "media_url": "https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "description": "starting the day right",
  "is_live": true,
  "use_official_sound": false,
  "hashtags": [
    "farmlife",
    "morning"
  ]
}
```

**Response**

```json
{
  "post_id": "273a704c-0114-4e9e-a574-e8ae3a4d427e"
}
```

**SDK Code**

```python Video post
import requests

url = "https://api.algorhythm.app/v1/posts"

payload = {
    "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
    "media_type": "video",
    "media_url": "https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4",
    "sound_id": "7255433468434549509",
    "webhook_url": "https://example.com/hooks/algorhythm",
    "description": "starting the day right",
    "is_live": True,
    "use_official_sound": False,
    "hashtags": ["farmlife", "morning"]
}
headers = {
    "X-API-Key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Video post
const url = 'https://api.algorhythm.app/v1/posts';
const options = {
  method: 'POST',
  headers: {'X-API-Key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"profile_id":"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d","media_type":"video","media_url":"https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4","sound_id":"7255433468434549509","webhook_url":"https://example.com/hooks/algorhythm","description":"starting the day right","is_live":true,"use_official_sound":false,"hashtags":["farmlife","morning"]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Video post
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.algorhythm.app/v1/posts"

	payload := strings.NewReader("{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"video\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"description\": \"starting the day right\",\n  \"is_live\": true,\n  \"use_official_sound\": false,\n  \"hashtags\": [\n    \"farmlife\",\n    \"morning\"\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-Key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Video post
require 'uri'
require 'net/http'

url = URI("https://api.algorhythm.app/v1/posts")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"video\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"description\": \"starting the day right\",\n  \"is_live\": true,\n  \"use_official_sound\": false,\n  \"hashtags\": [\n    \"farmlife\",\n    \"morning\"\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Video post
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.algorhythm.app/v1/posts")
  .header("X-API-Key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"video\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"description\": \"starting the day right\",\n  \"is_live\": true,\n  \"use_official_sound\": false,\n  \"hashtags\": [\n    \"farmlife\",\n    \"morning\"\n  ]\n}")
  .asString();
```

```php Video post
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.algorhythm.app/v1/posts', [
  'body' => '{
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "video",
  "media_url": "https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "description": "starting the day right",
  "is_live": true,
  "use_official_sound": false,
  "hashtags": [
    "farmlife",
    "morning"
  ]
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-Key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp Video post
using RestSharp;

var client = new RestClient("https://api.algorhythm.app/v1/posts");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-Key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"video\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"description\": \"starting the day right\",\n  \"is_live\": true,\n  \"use_official_sound\": false,\n  \"hashtags\": [\n    \"farmlife\",\n    \"morning\"\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Video post
import Foundation

let headers = [
  "X-API-Key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "video",
  "media_url": "https://storage.googleapis.com/campaign-posts/processed/f0590215-34b8-4bbe-b7a4-b66c804d3cd6/593b166d-5fd3-4170-aac3-12ac91ab2ccc.mp4",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "description": "starting the day right",
  "is_live": true,
  "use_official_sound": false,
  "hashtags": ["farmlife", "morning"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.algorhythm.app/v1/posts")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Slideshow post (scheduled, save as draft)

**Request**

```json
{
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "slideshow",
  "media_url": "https://storage.googleapis.com/campaign-posts/abc123.zip",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "title": "Throwback Thursday",
  "description": "which one is your favorite",
  "execute_at": 1748102400000,
  "is_live": false,
  "hashtags": [
    "throwback",
    "nostalgia"
  ]
}
```

**Response**

```json
{
  "post_id": "4154a282-2759-4d3a-beb9-4f7c3fcfb1a6"
}
```

**SDK Code**

```python Slideshow post (scheduled, save as draft)
import requests

url = "https://api.algorhythm.app/v1/posts"

payload = {
    "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
    "media_type": "slideshow",
    "media_url": "https://storage.googleapis.com/campaign-posts/abc123.zip",
    "sound_id": "7255433468434549509",
    "webhook_url": "https://example.com/hooks/algorhythm",
    "title": "Throwback Thursday",
    "description": "which one is your favorite",
    "execute_at": 1748102400000,
    "is_live": False,
    "hashtags": ["throwback", "nostalgia"]
}
headers = {
    "X-API-Key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Slideshow post (scheduled, save as draft)
const url = 'https://api.algorhythm.app/v1/posts';
const options = {
  method: 'POST',
  headers: {'X-API-Key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"profile_id":"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d","media_type":"slideshow","media_url":"https://storage.googleapis.com/campaign-posts/abc123.zip","sound_id":"7255433468434549509","webhook_url":"https://example.com/hooks/algorhythm","title":"Throwback Thursday","description":"which one is your favorite","execute_at":1748102400000,"is_live":false,"hashtags":["throwback","nostalgia"]}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Slideshow post (scheduled, save as draft)
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.algorhythm.app/v1/posts"

	payload := strings.NewReader("{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"slideshow\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/abc123.zip\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"title\": \"Throwback Thursday\",\n  \"description\": \"which one is your favorite\",\n  \"execute_at\": 1748102400000,\n  \"is_live\": false,\n  \"hashtags\": [\n    \"throwback\",\n    \"nostalgia\"\n  ]\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-Key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Slideshow post (scheduled, save as draft)
require 'uri'
require 'net/http'

url = URI("https://api.algorhythm.app/v1/posts")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"slideshow\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/abc123.zip\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"title\": \"Throwback Thursday\",\n  \"description\": \"which one is your favorite\",\n  \"execute_at\": 1748102400000,\n  \"is_live\": false,\n  \"hashtags\": [\n    \"throwback\",\n    \"nostalgia\"\n  ]\n}"

response = http.request(request)
puts response.read_body
```

```java Slideshow post (scheduled, save as draft)
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.algorhythm.app/v1/posts")
  .header("X-API-Key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"slideshow\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/abc123.zip\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"title\": \"Throwback Thursday\",\n  \"description\": \"which one is your favorite\",\n  \"execute_at\": 1748102400000,\n  \"is_live\": false,\n  \"hashtags\": [\n    \"throwback\",\n    \"nostalgia\"\n  ]\n}")
  .asString();
```

```php Slideshow post (scheduled, save as draft)
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.algorhythm.app/v1/posts', [
  'body' => '{
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "slideshow",
  "media_url": "https://storage.googleapis.com/campaign-posts/abc123.zip",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "title": "Throwback Thursday",
  "description": "which one is your favorite",
  "execute_at": 1748102400000,
  "is_live": false,
  "hashtags": [
    "throwback",
    "nostalgia"
  ]
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'X-API-Key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp Slideshow post (scheduled, save as draft)
using RestSharp;

var client = new RestClient("https://api.algorhythm.app/v1/posts");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-Key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"profile_id\": \"f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d\",\n  \"media_type\": \"slideshow\",\n  \"media_url\": \"https://storage.googleapis.com/campaign-posts/abc123.zip\",\n  \"sound_id\": \"7255433468434549509\",\n  \"webhook_url\": \"https://example.com/hooks/algorhythm\",\n  \"title\": \"Throwback Thursday\",\n  \"description\": \"which one is your favorite\",\n  \"execute_at\": 1748102400000,\n  \"is_live\": false,\n  \"hashtags\": [\n    \"throwback\",\n    \"nostalgia\"\n  ]\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Slideshow post (scheduled, save as draft)
import Foundation

let headers = [
  "X-API-Key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "profile_id": "f8a1c2d4-3b7e-4f9a-9c2d-1e5b7a8f9c3d",
  "media_type": "slideshow",
  "media_url": "https://storage.googleapis.com/campaign-posts/abc123.zip",
  "sound_id": "7255433468434549509",
  "webhook_url": "https://example.com/hooks/algorhythm",
  "title": "Throwback Thursday",
  "description": "which one is your favorite",
  "execute_at": 1748102400000,
  "is_live": false,
  "hashtags": ["throwback", "nostalgia"]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.algorhythm.app/v1/posts")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```