fix(tiktok): stop posts silently landing in the TikTok inbox instead of publishing#1710
Merged
Conversation
The agent (chat / MCP) had to pick content_posting_method with no guidance: the setting is a bare required enum with no description or default, and TikTok's agent rules said nothing about it. For a video post "UPLOAD" reads like the obvious choice - but UPLOAD does not publish, it only drops the video into the user's TikTok app inbox, where it must be completed manually within ~24h or it is discarded. Users hit this as "post marked successful but never published". TikTok's @rules text now tells the agent to use DIRECT_POST unless the user explicitly asks to review/edit in the app first, and explains that UPLOAD does not publish. No settings default is needed to back this up: content_posting_method is required in TikTokDto (@isin with no @IsOptional, validated with skipMissingProperties: false), so an omitted key cannot fall through to UPLOAD - validatePosts rejects it and the tool hands the error back to the agent to retry. The failure mode is the agent *choosing* UPLOAD, and only prose the agent reads can prevent that. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`content_posting_method` was exposed to schema consumers as a bare
`enum: ["DIRECT_POST", "UPLOAD"]` with no description, so an LLM asked to
"upload this video to TikTok" would reasonably pick the value literally
named UPLOAD - which never publishes, it only drops the media into the
user's TikTok app inbox where it expires after 24 hours.
Annotate the field with `@JSONSchema({ description })`. This is metadata
only: it is read by class-validator-jsonschema when generating the schema
and registers no validator, so `@IsIn([...])` still rejects a missing or
invalid value and the field stays required. Verified against the
generated schema: `description` is present, `enum` is unchanged, and
`content_posting_method` remains in `required`.
The annotation reaches every `getValidationSchemas()` consumer:
- `integrationSchema` MCP tool (internal web agent + external MCP agents)
- `GET /public/v1/integration-settings/:id` (public API, and the CLI's
`postiz integrations:settings`)
The dashboard is unaffected - its React form hardcodes DIRECT_POST.
For agents this layers on top of the @rules text in the previous commit:
both are prose the model reads before it picks a value, which is the only
thing that helps here - the field is required, so the model always sends
*something*, and no server-side default can rescue an explicit UPLOAD.
For public API callers that read the schema but not `rules`, this
description is the only in-band signal.
Precedent for the decorator: whop.dto.ts, discord.dto.ts, listmonk.dto.ts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three places in the provider read `content_posting_method`, and they did not agree on what a missing value means: - `postingMethod()` already defaulted it: its `switch` has `case 'DIRECT_POST': default:`, so an absent value picks `/video/init/` (publish) rather than `/inbox/video/init/`. - `buildTikokPostInfoBody()` compared `=== 'DIRECT_POST'`, so an absent value fell into the inbox-shaped body with no `privacy_level`. - `buildTikokSourceInfoBody()` compared the same way, so an absent value gave a photo `post_mode: 'MEDIA_UPLOAD'` - the app inbox, which never publishes. Net effect of the disagreement: a photo with no method silently went to the inbox, and a video with no method hit the *publish* endpoint with a body missing `privacy_level`, which TikTok rejects. Normalize once in `contentPostingMethod()` - only an explicit 'UPLOAD' selects the inbox flow, everything else (including a missing value) publishes - and use it at all three sites, so the video endpoint's existing default is now the behaviour of the whole publish path. Settings validation keeps a missing value out of the publish path for normal posts (the field is required in TikTokDto), so this is the belt-and-braces layer for anything that bypasses it - drafts, older rows written before the field existed, direct repository writes. Verified by driving the three builders with photo/video posts and the method present/absent: absent now matches DIRECT_POST exactly (endpoint, post_info privacy_level, post_mode), and explicit UPLOAD is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Contribution-checker quality warning Heuristics that flagged:
If this is a genuine contribution, please add detail to your PR description and tighten the diff scope before reviewers look at it. |
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
giladresisi
added a commit
that referenced
this pull request
Jul 15, 2026
Combine the UPLOAD guidance from #1710 with this branch's settings-visibility text: keep the 24-hour inbox expiry detail in both the @rules string and the content_posting_method @jsonschema description, plus the settings-ignored / video-only constraints from this branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Scheduled TikTok posts were being marked successful in Postiz and Temporal but never published. The
postSocialactivity returnedpostId: "missing"with atiktok.com/messagesrelease URL.Root cause: the post had
content_posting_method: "UPLOAD"instead ofDIRECT_POST.UPLOADdoes not publish — TikTok's API delivers the video to the account's app inbox, where the user must manually complete it within ~24h or it's discarded. This is TikTok'sSEND_TO_USER_INBOXterminal status, which we (correctly) treat as a successful upload.Why the agent chose
UPLOAD: the chat/MCP agent had to supplycontent_posting_methodwith no guidance — it's a bare required enum ('DIRECT_POST' | 'UPLOAD') with no description, and TikTok's agent@Rulessaid nothing about it. For a video post, "UPLOAD" reads like the obvious choice. The user never asked for it; the model guessed.Fix
1.
@Rulesguidance (tiktok.provider.ts) — the agent is now told to useDIRECT_POSTunless the user explicitly asks to review/edit inside the TikTok app first, and thatUPLOADdoes not publish.2. Schema description (
tiktok.dto.ts) —@JSONSchema({ description })explains, on the field itself, thatUPLOADnever publishes. It's metadata only: it registers no validator, so@IsIn([...])still rejects a missing or invalid value and the field remains required.Both are prose the model reads before it picks a value, which is the only thing that helps here — the field is required, so the model always sends something, and no server-side default can rescue an explicit
UPLOAD. They reach different populations:@Rules(viaintegrationSchema) and the field descriptionrulesfromGET /public/v1/integration-settings/:id@Rules@JSONSchemadescriptionThe dashboard is unaffected — its React form hardcodes
DIRECT_POST.3. Consistent handling of a missing value (
tiktok.provider.ts) — while fixing the above it turned out the three places that readcontent_posting_methoddisagreed about what an absent value means:postingMethod()DIRECT_POST— itsswitchalready hascase 'DIRECT_POST': default:buildTikokPostInfoBody()privacy_levelbuildTikokSourceInfoBody()post_mode: 'MEDIA_UPLOAD'— the app inboxNet effect: a photo with no method silently went to the inbox, and a video with no method hit the publish endpoint with a body missing
privacy_level, which TikTok rejects. Normalized in onecontentPostingMethod()helper — only an explicitUPLOADselects the inbox flow, everything else publishes — used at all three sites, so the video endpoint's existing default is now the behavior of the whole publish path.Settings validation already keeps a missing value out of the publish path for normal posts (the field is required in
TikTokDto, validated withskipMissingProperties: false), so this is the belt-and-braces layer for anything that bypasses it: drafts, older rows, direct repository writes.Testing
DIRECT_POSTexactly (endpoint,post_info.privacy_level,post_mode), and explicitUPLOADis unchanged. Before the change, absent gaveMEDIA_UPLOADfor photos and aprivacy_level-less direct body for videos.TikTokDto:descriptionpresent,enumunchanged,content_posting_methodstill listed inrequired.nestjs-librariesare pre-existing and unrelated (mastodon, media repository, track service).Not covered here
Two other copies of this enum are documented outside this repo and are unreachable from a backend change. Both are being fixed separately:
gitroomhq/postiz-docs—public-api/providers/tiktok.mdxdescribesUPLOADas "Upload for manual posting";public-api/openapi.jsonhas a bare enum with no description. (Hand-maintained, not generated.)gitroomhq/postiz-agent— the skill bundles its ownPROVIDER_SETTINGS.md, which lists the enum with no explanation. Skill runs read that file rather than callingintegrations:settings, so no backend fix reaches them.Notes
Supersedes #1687, which could not take the rewritten history (force-push is blocked on that branch). The earlier version of this fix also added a
PROVIDER_SETTINGS_DEFAULTSmap tointegration.schedule.post.tsthat filled an omittedcontent_posting_method; it has been dropped. It put a TikTok-specific value in a generic tools file, it only applied on the MCP path (so the same payload behaved differently through MCP than through the public API), and it was redundant — the field is required, so an omitted key is rejected by validation rather than falling through toUPLOAD. The provider-level normalization above covers the same ground where it actually matters, at publish time.First of two related changes. A follow-up feature branch (agent list/update of scheduled posts) stacks on this one and will be opened as a separate PR.
🤖 Generated with Claude Code