PM3K REST API — TikTok Posting Service

1. What this service does

PM3K gives you two main ways to work with TikTok:

  • Use the dashboard to upload a video and either post it instantly, schedule it for later, or just get the final TikTok link to reuse in your own low-code workflows.
  • Use the REST API to post a video from a URL (PULL_FROM_URL, not file upload) and then check publish status by publish_id. For public low-code automation, the canonical endpoints are direct_post and status.

Base URLs (production):

  • POST https://pm3k.org/api/tiktok/direct_post
  • POST https://pm3k.org/api/tiktok/status

All responses are JSON with Content-Type: application/json; charset=UTF-8.

Dashboard scheduling is a separate PM3K session flow: the dashboard creates, reads, and deletes scheduled jobs, while actual scheduled execution happens internally in PM3K.

2. Authentication & plans

2.1. Sign in and plan

  1. Go to pm3k.org.
  2. Click Connect / Sign in with TikTok.
  3. Approve requested scopes in TikTok (official OAuth 2.0 + PKCE flow).
  4. Choose a plan: Free or Monthly (Annual is not active).

PM3K stores your TikTok open_id and plan in internal storage (KV).

Free plan basics:

  • API key lifetime: about 7 days.
  • PM3K storage TTL for uploaded files: up to 3 days.
  • TikTok posting: max 10 posts/day, at least 1 hour between posts.

Paid plans increase key lifetime, storage TTL and upload limits, but TikTok anti-spam limits stay the same.

2.2. API key

  1. In the dashboard, open the API key section.
  2. Click Issue / Regenerate key.
  3. Copy the key and store it in your tool (n8n, Make, etc.) as a secret.

Important:

  • The key is shown only once; if you lose it, issue a new one.
  • PM3K stores only a hash of your key and cannot recover the original.
  • When the plan expires, the key effectively stops working.

Use this header for all API calls:

Authorization: Bearer YOUR_API_KEY_HERE

3. Video source (source_url)

You always pass a URL to the video file, not the file itself. Two common options:

3.1. PM3K storage (recommended for testing)

  1. In the dashboard, use the Upload section.
  2. PM3K stores your file in cloud sorage with a TTL based on your plan.
  3. You get an HTTPS URL like:
    https://files.pm3k.org/uploads/<open_id>/<date>/<id>.mp4
  4. You can use this URL as source_url in direct_post.

3.2. Your own hosting (CDN / object storage)

Available on active plans (Free / Monthly) after verification.

Requirements for your own domain:

  • Public HTTPS URL (no auth, no paywall).
  • Reasonable file size for your plan (Free up to ~100 MB, paid up to ~200 MB).
  • Your source_url must resolve within a reasonable number of HTTP redirects. If PM3K cannot fetch the file (for example, redirect loop or network timeout over ~30 seconds), the request may fail with fetch_error or redirect_not_allowed.

File size limit: Free plan is typically around 100 MB, paid plans around 200 MB. Larger files typically fail with too_large or too_large_need_proxy.

Domain verification flow:

  1. In the dashboard, open the section for storage / domains.
  2. Click Add domain and enter your root domain (for example media.example.com).
  3. PM3K will generate a small .txt verification file that you need to upload to the root of your domain (for example: https://your-domain.com/pm3k-verify.txt).
  4. Just add the file on your side, then click Verify in the dashboard.
  5. Once the domain is marked as verified, you can safely use URLs from that domain as source_url in the API.

Even for a verified domain, PM3K will still pull your video through its own storage first (temporary copy). PM3K may use trusted media metadata from that copy (for example size + ETag when available) for duplicate / anti-spam checks. TikTok does not fetch directly from your domain via this API; PM3K sits in the middle for better safety and de-duplication.

4. Endpoint: POST /api/tiktok/direct_post

4.1. Purpose

Create a TikTok post using PULL_FROM_URL.

4.2. Request

URL

POST https://pm3k.org/api/tiktok/direct_post

Headers

Authorization: Bearer YOUR_API_KEY_HERE
Content-Type: application/json

4.3. Request body

Body (basic)

{
  "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
  "title": "My caption without links",
  "privacy": "PUBLIC_TO_EVERYONE",
  "disable_comment": false,
  "disable_duet": false,
  "disable_stitch": false
}

4.3.1. n8n: filling the HTTP Request node field by field

You can send a PM3K direct post request in n8n without pasting a full raw JSON payload. Filling the body field by field is supported.

The important part is not how you enter the values, but what the final HTTP request looks like. PM3K expects a JSON request body, so the node must still send a real JSON object even if you entered the values one row at a time in the UI.

Recommended HTTP Request setup

  • Method: POST
  • URL: https://pm3k.org/api/tiktok/direct_post
  • Header: Authorization: Bearer YOUR_API_KEY
  • Header: Content-Type: application/json

Do not send the request as:

  • form-data
  • x-www-form-urlencoded
  • plain text body

Important: field-by-field is still JSON

When you add body values one by one in n8n, the final request must still be a JSON object.

Good result

{
  "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
  "privacy": "PUBLIC_TO_EVERYONE",
  "title": "Caption"
}

Bad result

source_url=https://files.pm3k.org/uploads/.../video.mp4&privacy=PUBLIC_TO_EVERYONE

The second example is not a JSON body and may lead to 400 invalid_request.

Video post: field-by-field example

For a normal video post, the safest minimum setup is:

  • source_url → direct public video URL
  • privacy → for example PUBLIC_TO_EVERYONE
  • title (optional)
  • disable_comment (optional)
  • disable_duet (optional)
  • disable_stitch (optional)

Example final JSON body

{
  "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
  "title": "My caption",
  "privacy": "PUBLIC_TO_EVERYONE",
  "disable_comment": false,
  "disable_duet": false,
  "disable_stitch": false
}

privacy is required. title is optional. disable_comment, disable_duet, and disable_stitch are optional interaction fields.

Photo post: field-by-field example

For photo mode, use:

  • media_typePHOTO
  • photo_images → array of direct public image URLs
  • photo_cover_index0 or another valid index
  • privacy
  • title (optional)

Example final JSON body

{
  "media_type": "PHOTO",
  "photo_images": [
    "https://files.pm3k.org/uploads/.../a.jpg",
    "https://files.pm3k.org/uploads/.../b.png"
  ],
  "photo_cover_index": 0,
  "title": "Caption for a photo post",
  "privacy": "PUBLIC_TO_EVERYONE"
}

Important: photo_images must be an array, not a single string.

Correct

{
  "photo_images": ["https://files.pm3k.org/uploads/.../a.jpg"]
}

Incorrect

{
  "photo_images": "https://files.pm3k.org/uploads/.../a.jpg"
}

Branded content fields

If you enable ad / disclosure mode, the current recommended fields are brand_organic_toggle and brand_content_toggle. When used, send them as real booleans.

{
  "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
  "privacy": "PUBLIC_TO_EVERYONE",
  "is_ad": true,
  "brand_organic_toggle": true,
  "brand_content_toggle": false
}

Do not send branded content booleans as strings like:

{
  "brand_organic_toggle": "true"
}

Use:

{
  "brand_organic_toggle": true
}

Common mistakes when filling the node row by row

  1. Missing privacy.
    {
      "source_url": "https://files.pm3k.org/uploads/.../video.mp4"
    }
    {
      "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
      "privacy": "PUBLIC_TO_EVERYONE"
    }
  2. Sending photo_images as text instead of an array.
    {
      "media_type": "PHOTO",
      "photo_images": "https://files.pm3k.org/uploads/.../a.jpg",
      "photo_cover_index": 0,
      "privacy": "PUBLIC_TO_EVERYONE"
    }
    {
      "media_type": "PHOTO",
      "photo_images": ["https://files.pm3k.org/uploads/.../a.jpg"],
      "photo_cover_index": 0,
      "privacy": "PUBLIC_TO_EVERYONE"
    }
  3. Using the wrong body type. Even if the fields look correct in the n8n UI, the request can still fail if the node sends them as form fields or plain text instead of JSON.
  4. Sending branded-content booleans as strings.
    {
      "is_ad": true,
      "brand_organic_toggle": "false",
      "brand_content_toggle": "true"
    }
    {
      "is_ad": true,
      "brand_organic_toggle": false,
      "brand_content_toggle": true
    }

Quick checklist before you run the node

  • Request method is POST.
  • URL is https://pm3k.org/api/tiktok/direct_post.
  • Authorization: Bearer ... header is present.
  • Content-Type: application/json header is present.
  • Body is sent as JSON.
  • privacy is present.
  • photo_images is an array for photo posts.
  • Branded-content toggles are real booleans.

4.4. Direct post details

Use this endpoint from n8n, Make, or your own scripts to send a prepared vertical video directly to TikTok.

4.4.1. Full payload example

{
  "source_url": "https://files.pm3k.org/abc123.mp4",
  "title": "Your TikTok caption here",
  "privacy": "PUBLIC_TO_EVERYONE",

  "disable_comment": false,
  "disable_duet": false,
  "disable_stitch": false,

  "is_ad": false,
  "brand_organic_toggle": false,
  "brand_content_toggle": false,
  "brand": ""
}

4.4.2. Core fields

  • source_url (string, required for video mode) — HTTPS URL of your vertical MP4/MOV. The URL must be either from PM3K storage or from a verified custom domain.
  • title (string, optional) — TikTok caption, up to ~2200 characters.
  • privacy (string, required) — TikTok privacy level. Recommended values:
    • PUBLIC_TO_EVERYONE
    • MUTUAL_FOLLOW_FRIENDS
    • FOLLOWER_OF_CREATOR
    • SELF_ONLY
    For backwards compatibility you may also send: "public", "friends", "followers", "private" — PM3K will normalize them to the correct TikTok enum. If privacy is missing, PM3K rejects the request with 400 invalid_request. If privacy is present but the value is unknown, PM3K falls back to SELF_ONLY.

For photo mode, send photo_images and photo_cover_index. title stays optional there too, while privacy is still required.

If you omit title, PM3K does not invent a caption for the request. This also applies to new scheduled jobs created through the dashboard.

Empty captions are allowed. Empty title values are not checked against caption duplicate or caption-spread rules. Non-empty captions still are.

Important: The video must be vertical (9:16). PM3K does not re-encode, rotate or compress your video — the exact file you provide is uploaded to TikTok as-is. Files that are not vertical may be rejected with a bad_request error.

Duration limits: TikTok may reject videos that exceed the maximum duration allowed for your account. PM3K does not override this limit — TikTok enforces it on upload.

PM3K does not compress, modify or re-encode your video. The file hosted at source_url is the file TikTok receives.

4.4.3. Interaction settings

  • disable_comment (boolean, optional) — if true, comments are turned off for this video.
  • disable_duet (boolean, optional) — if true, duets are disabled.
  • disable_stitch (boolean, optional) — if true, stitches are disabled.

4.4.4. Ad / branded content fields

These fields are optional but recommended if you use PM3K for commercial posts. They are designed to reflect TikTok’s ad / branded content policies.

  • is_ad (boolean, optional) — turn on ad / disclosure mode for this post.
  • brand_organic_toggle (boolean, optional) — you promote your own brand, product, or service.
  • brand_content_toggle (boolean, optional) — you promote a third-party brand (branded content collaboration).
  • brand (string, optional) — brand or advertiser name. When you use branded content, provide a clear brand name here.

If is_ad is true, you must also choose a disclosure type: send brand_organic_toggle for your own brand or brand_content_toggle for third-party branded content. If is_ad is true but neither disclosure type is selected, PM3K rejects the request with 400 invalid_request.

PM3K also accepts the legacy aliases ad_your_brand and ad_branded_content, but the current dashboard export uses brand_organic_toggle and brand_content_toggle.

You don’t need to send any technical metadata (file size, ETag, etc.). PM3K reads everything it needs from its own storage and uses internal checks to block duplicate uploads and spammy captions.

4.5. Photo Mode (slides)

Photo Mode lets you publish a TikTok photo carousel (slides) using the same /api/tiktok/direct_post endpoint. PM3K always sends PULL_FROM_URL to TikTok and uses PM3K storage URLs for final delivery.

4.5.1. Photo payload (direct post)

{
  "media_type": "PHOTO",
  "photo_images": [
    "https://files.pm3k.org/uploads/<open_id>/albums/<album_id>/a.jpg",
    "https://files.pm3k.org/uploads/<open_id>/albums/<album_id>/b.webp"
  ],
  "photo_cover_index": 0,
  "auto_add_music": true,
  "title": "Caption for a photo post",
  "privacy": "PUBLIC_TO_EVERYONE",
  "disable_comment": false,
  "disable_duet": false,
  "disable_stitch": false,
  "is_ad": false,
  "brand_organic_toggle": false,
  "brand_content_toggle": false,
  "brand": ""
}

4.5.2. Upload-only (get link) + retention

In the dashboard, Photo mode shows a live JSON payload as uploads complete. If you click Upload only (get link), PM3K upgrades retention for the album.

POST https://pm3k.org/api/album/retain
{ "album_id": "..." }

Use the resulting JSON in n8n/Make with /api/tiktok/direct_post.

Use a JSON body there as well. Do not switch to form-data or x-www-form-urlencoded, and keep privacy in the final payload.

4.5.3. Photo limits & rules

  • 1..35 images per post.
  • JPEG / WebP / PNG accepted (PNG is converted before posting).
  • Max 20 MB per image.
  • Max 1080px on either side.
  • photo_cover_index is required and 0-based.
  • Host-only domain verification for external URLs (no prefix checks). PM3K buffers external images to PM3K storage; TikTok receives PM3K URLs only.
  • External URLs must send Content-Length (otherwise content_length_required).
  • Hetzner proxy is video-only; photos never use proxy.

Posting limits are unchanged: max 1 post/hour and 10 posts/day total across photo/video posts for API usage. Dashboard upload limits remain plan-based (photo albums count as 1 upload).

4.5.4. Photo errors (common)

ErrorMeaning
too_few_imagesMissing images or empty array.
too_many_imagesMore than 35 images.
cover_out_of_rangeInvalid cover index.
unsupported_image_typeImage type is not accepted for this flow. External images may be JPEG/WebP/PNG; PM3K-hosted photo URLs must be JPEG/WebP.
image_too_largeImage exceeds 20 MB.
image_too_large_pxImage exceeds 1080px on one side.
content_length_requiredMissing Content-Length for external URLs.
domain_not_verifiedExternal domain not verified.
domain_mismatchExternal URL host does not match the verified domain on this PM3K account.
image_convert_failedPM3K could not prepare the external image into a supported upload format.

4.6. Successful response

{
  "ok": true,
  "publish_id": "7153xxxxxxxxxxxxxxxxxx"
}

You will use publish_id with /api/tiktok/status to check the publish state.

5. Endpoint: POST /api/tiktok/status

5.1. Purpose

Check current TikTok publish status for a post created by direct_post.

5.2. Request

URL

POST https://pm3k.org/api/tiktok/status

Headers

Authorization: Bearer YOUR_API_KEY_HERE
Content-Type: application/json

Body

{
  "publish_id": "7153xxxxxxxxxxxxxxxxxx"
}

5.3. Successful response

{
  "ok": true,
  "status": "PUBLISHING",
  "state": "pending",
  "fail_reason": null,
  "raw": {
    "...": "TikTok original response"
  }
}

Fields:

  • status – raw TikTok status (e.g. PUBLISHING, PUBLISH_COMPLETE, PUBLISH_FAILED).
  • state – simplified state:
    • pending – still processing.
    • done – published successfully.
    • failed – TikTok failed to publish.
  • fail_reason – TikTok fail reason, if any.
  • raw – full TikTok JSON for debugging.

5.4. Status rate limits

For /status, PM3K applies a per-minute limit of 20 calls per minute per TikTok account.

PM3K also enforces a hard limit of 40 calls per hour per open_id. The first time you exceed this limit in a 1-hour window, status polling enters a 1-hour cooldown. A new successful post clears this cooldown earlier.

If exceeded, you receive:

{
  "ok": false,
  "error": "rate_limit_status",
  "message": "Too many /status calls for this account, please slow down.",
  "retry_after": "2025-01-01T12:00:00.000Z"
}

HTTP status: 429.

Hourly cooldown response:

{
  "ok": false,
  "error": "status_cooldown",
  "message": "Status polling is temporarily paused for this account after too many /status checks.",
  "cooldown": "temporary_backoff",
  "retry_after_seconds": 3600,
  "retry_after": "2025-01-01T12:30:00.000Z",
  "cooldownUntil": "2025-01-01T12:30:00.000Z"
}

For status_cooldown, cooldownUntil is the canonical absolute deadline, retry_after_seconds is the canonical duration field, and plain retry_after is a deprecated legacy alias kept for backward compatibility.

If the cooldown was cleared early by a successful post, but the same hour window is still capped:

{
  "ok": false,
  "error": "hourly_limit_status",
  "retry_after": "2025-01-01T13:00:00.000Z",
  "resetAt": "2025-01-01T13:00:00.000Z"
}

Recommended polling pattern:

  • Wait 10–15 seconds between checks.
  • Stop after 20–30 attempts and treat as unknown if still pending.

6. Scheduling and instant posting in the dashboard

You can manage TikTok posts directly from the PM3K dashboard. No extra public API endpoints are required for this dashboard flow.

  1. Upload your video to PM3K storage — dashboard uploads always go through PM3K’s own storage. Videos from your own verified domain are used only from API calls, not from the dashboard upload flow.
  2. Once uploaded, the video will appear in the dashboard as an available source.
  3. Open the TikTok scheduling block (the card with “Now / Schedule” options).
  4. Choose your video, set the caption, and optional flags (disable comments / duets / stitches).
  5. To post immediately, select the Now mode and confirm. PM3K will send the request to TikTok as soon as you click publish.
  6. To schedule for later, select the Schedule mode, pick date and time, and save. PM3K creates a scheduled job in the dashboard flow, and PM3K executes that job internally later. No public low-code trigger call is required for dashboard scheduling.
  7. If you leave the caption empty, PM3K keeps it empty for new scheduled jobs. The system does not auto-fill a default caption.
  8. Also, after the video is uploaded, the dashboard shows a direct PM3K file URL (your video in PM3K storage). You can copy this URL and use it as source_url in your own low-code workflows (for example, call /api/tiktok/direct_post from n8n or Make, and then check /api/tiktok/status). n8n/Make delays are controlled by your workflow. PM3K internal scheduling is only available through the dashboard.

7. Anti-spam rules & Acceptable Use

7.1. Links in captions

Captions with links are blocked. If title contains http://, https:// or www., PM3K rejects the request:

{
  "ok": false,
  "error": "links_forbidden",
  "message": "Links are not allowed in TikTok captions for this service."
}

HTTP status: 400.

7.2. Forbidden words

PM3K maintains a forbidden-words list (STOP_WORDS) covering, for example:

  • Gambling, betting, casinos, slot machines.
  • Hard drugs and illegal drug trade.
  • Weapons, explosives, terrorism, extremism, hate content.
  • Pornography, paid sexual services, explicit sex content.
  • Hacking, malware, stolen data, illegal markets.
  • Financial scams, Ponzi schemes, “get rich quick”, fake investments.
  • Pirated software/media, cracked accounts and similar.

If your caption contains one of these words or phrases, PM3K returns:

{
  "ok": false,
  "error": "forbidden_word",
  "message": "Caption contains forbidden content: \"...\"."
}

HTTP status: 400. Such captions are not sent to TikTok at all.

7.3. Duplicate video protection

PM3K avoids posting the same video repeatedly for the same account with two layers of protection.

  • A very short recent window applies to the same exact source_url.
  • An additional duplicate check may apply for up to 30 days when PM3K has trusted media fingerprint metadata for the file.

If one of these video duplicate checks blocks the request, PM3K returns:

{
  "ok": false,
  "error": "video_duplicate",
  "message": "This video URL was already used for this TikTok account recently."
}

HTTP status: 400. For backward compatibility, the public error code and message text stay the same. Client-supplied media metadata alone does not trigger the longer fingerprint-based block. The same duplicate checks also apply when a scheduled video job is executed.

7.4. Duplicate captions

Two layers apply to non-empty captions. Empty captions are not checked against these two caption-reuse rules.

  1. Per account – if the same caption is used on the same account within 7 days:
    {
      "ok": false,
      "error": "caption_duplicate_account",
      "message": "This caption was already used on this TikTok account recently."
    }
  2. Global account-spread rule – if the same caption is used on more than 5 different accounts in 24 hours, PM3K rejects the request with:
    {
      "ok": false,
      "error": "caption_global_spam",
      "message": "This caption is being reused across too many accounts in a short time."
    }

Both responses use HTTP status 400.

7.5. Posting limits

Per TikTok account (per creator_id):

  • Max 10 posts per day via PM3K.
  • Minimum 1 hour between posts.

If you exceed these limits:

{
  "ok": false,
  "error": "daily_limit",
  "message": "Daily TikTok posting limit (10/day) reached."
}

or

{
  "ok": false,
  "error": "interval_limit",
  "message": "You must wait about N more minutes before next TikTok post."
}

HTTP status: 429.

7.6. TikTok 429 & cooldown

If TikTok itself returns HTTP 429 (rate limit) for your account, PM3K sets a cooldown for this creator_id (around 3 hours).

During cooldown you will see:

{
  "ok": false,
  "error": "tiktok_rate_limit",
  "message": "TikTok returned HTTP 429 (rate limit) for this account; posting is temporarily paused.",
  "retry_after_seconds": 10800
}

or subsequent calls will return:

{
  "ok": false,
  "error": "tiktok_cooldown",
  "message": "TikTok recently returned HTTP 429 for this account; posting is temporarily paused."
}

Both use HTTP status 429. Do not auto-retry during cooldown.

8. Error reference (quick lookup)

Canonical public error values for /api/tiktok/direct_post and /api/tiktok/status:

  • unauthorized (401) – missing or malformed authentication. For /status, a Bearer API key is required. For direct_post, PM3K accepts either a Bearer API key or a dashboard session cookie.
  • invalid_api_key (401) – key not found or revoked.
  • api_key_expired (401) – Plan window expired for the public /api/tiktok/* automation surface such as /api/tiktok/direct_post and /api/tiktok/status. Message: "Your plan has expired. Please upgrade to a paid plan."
  • plan_required (401) – no active PM3K plan is attached to this account.
  • tiktok_not_connected (401) – TikTok account is not connected for this PM3K user; reconnect in dashboard.
  • tiktok_reauth_required (401) – TikTok refresh token expired; reconnect TikTok in dashboard.
  • tiktok_refresh_failed (401) – PM3K could not refresh the TikTok access token; reconnect TikTok in dashboard.
  • bad_request (400) – invalid JSON or missing required request fields (for example source_url, publish_id, invalid photo item, or invalid URL format).
  • invalid_request (400) – validation error in the JSON body (for example missing privacy, invalid branded-content toggle types, or is_ad: true without a disclosure type).
  • domain_not_verified (403) – external media host is not verified for this PM3K account.
  • domain_mismatch (403) – external media URL host does not match the verified domain on this account.
  • fetch_error (400) – PM3K could not fetch or prepare the external media URL.
  • redirect_not_allowed (400) – external media URL responded with redirects that PM3K does not accept for this flow.
  • content_length_required (400) – external media URL must provide a valid Content-Length header.
  • too_large (413) – media file is too large for the current plan or fetch path.
  • too_large_need_proxy (413) – media is larger than the direct fetch limit for this plan; use a smaller file or PM3K-hosted media.
  • too_few_images (400) – photo post did not include any valid images.
  • too_many_images (400) – photo post included too many images.
  • cover_out_of_range (400) – photo_cover_index is outside the uploaded image range.
  • unsupported_image_type (400) – image type is not accepted for this photo flow.
  • image_too_large (400) – image exceeds the supported size limit.
  • image_too_large_px (400) – image exceeds the supported pixel dimensions.
  • image_convert_failed (400) – PM3K could not convert the external image into a supported upload format.
  • links_forbidden (400) – caption contains a link.
  • forbidden_word (400) – caption hits forbidden content; not sent to TikTok.
  • video_duplicate (400) – duplicate video protection triggered for this account (same source URL in a very short recent window, or an additional trusted media fingerprint match within 30 days when available).
  • caption_duplicate_account (400) – same non-empty caption reused on this account within 7 days.
  • caption_global_spam (400) – request rejected because the same non-empty caption was reused on too many accounts in 24 hours.
  • daily_limit (429) – 10 posts/day limit reached.
  • interval_limit (429) – 1-hour interval not respected.
  • tiktok_cooldown (429) – PM3K is temporarily pausing new posts because TikTok recently returned HTTP 429 for this account.
  • tiktok_rate_limit (429) – TikTok returned HTTP 429 during post init; wait for cooldown before retrying.
  • rate_limit_status (429) – too many /status calls; see retry_after.
  • status_cooldown (429) – temporary 1-hour pause after too many /status calls; use cooldownUntil as the canonical deadline and retry_after_seconds as the canonical duration. Plain retry_after remains as a deprecated legacy alias for backward compatibility. A successful new post clears the cooldown earlier.
  • hourly_limit_status (429) – hourly cap reached; wait until resetAt.
  • tiktok_error (400) – fallback bucket when TikTok returned an upstream error and PM3K does not expose a more specific top-level code. Inspect raw.
  • Raw TikTok error.code passthrough (400) – in some upstream failures, PM3K returns TikTok's own error.code directly as the top-level error value.
  • proxy_error (502) – PM3K Pages could not reach the TikTok backend at all. This is only the Pages -> backend unreachable case, not a general media-fetch bucket.

Rare internal 5xx responses or upstream failures without a stable JSON error body may still happen, but they are not part of the supported public error contract for these endpoints.

9. Simple n8n pattern

9.1. Node 1 — HTTP Request: create TikTok post

  • Method: POST
  • URL: https://pm3k.org/api/tiktok/direct_post
  • Headers: Authorization: Bearer <your key>, Content-Type: application/json

Body (JSON):

{
  "source_url": "https://files.pm3k.org/uploads/.../video.mp4",
  "title": "My caption without links",
  "privacy": "PUBLIC_TO_EVERYONE",
  "disable_comment": false,
  "disable_duet": false,
  "disable_stitch": false
}

Send this node as a real JSON body. Do not build the request as form-data or x-www-form-urlencoded. privacy is required, photo posts must use photo_images, and branded-content toggles should be sent as real booleans.

Store ok and publish_id from the response. If ok is false, log error and message and stop the flow.

In n8n, always add an IF node right after this step: if ok == true → continue to the status loop; if ok == false → stop the flow and log error/message. Do not try to access publish_id when ok is false.

9.2. Node 2 — Loop with Wait + Status

  1. Wait 10–15 seconds.
  2. Call POST https://pm3k.org/api/tiktok/status with:
    {
      "publish_id": "{{$json["publish_id"]}}"
    }
  3. Check:
    • If state == "done" → success.
    • If state == "failed" → log fail_reason and stop.
    • Else (still pending) → wait and repeat, up to a max attempts limit.