Use for a known creator's videos through Frevana's tiktok/profile/videos endpoint. Use tiktok-profile-details for account details or tiktok-search for keyword discovery. Requires Bash and Python 3.9+ on macOS/Linux; no SDK, extension or daemon.
Read references/inputs.md for all 25 fields, string cursor semantics, ignored compatibility options and stationery grouping. references/input_schema.json is the bundled contract. Its allOf/anyOf requires at least one of handle or user_id, despite an empty top-level required list. Both are allowed; preserve explicitly supplied identifiers without guessing precedence. Prefer a known user ID for faster responses, but never invent or resolve it automatically. IDs remain strings. Ask for one identifier if neither is available.
Invoke and resume
Configure FREVANA_TOKEN through runtime environment configuration. Requests use Authorization: Bearer <FREVANA_TOKEN>; never embed, print or pass credentials as command arguments. Default origin: https://api.frevana.com, overridden by FREVANA_API_BASE_URL or --api-base-url with an HTTPS origin or HTTP localhost mock. Redirects are rejected. Credentials never go to returned media hosts.
Resolve {baseDir} to this directory and choose one durable state file per logical video-page operation:
# FREVANA_TOKEN is already configured in the runtime environment.
bash {baseDir}/scripts/tiktok_profile_videos.sh run \
--handle charlidamelio \
--state-file ./out/tiktok-profile-videos-page1.task.json \
--output ./out/tiktok-profile-videos-page1.json
A known user ID can replace --handle; preserve its exact string.
bash {baseDir}/scripts/tiktok_profile_videos.sh run
--user-id '12345678901234567890'
--state-file ./out/tiktok-profile-videos-by-id.task.json
bash {baseDir}/scripts/tiktok_profile_videos.sh resume
--state-file ./out/tiktok-profile-videos-page1.task.json
bash {baseDir}/scripts/tiktok_profile_videos.sh resume
--task-id TASK_UUID --state-file ./out/tiktok-profile-videos-recovered.task.json
Alternatively use `--input-file PATH` with a schema-valid object; do not combine it with input flags. CLI hyphens map to declared snake_case properties. Omitted options stay omitted, including the backend's described US proxy default. The helper validates required composition, declared types/enums, nonempty identifiers and mutually exclusive cursor fields. Detailed date/feature-combination/ID syntax belongs to the backend. CLI-only aliases `--published-before`, `--end-date`, `--to-date` become `until`; conflicting aliases stop before network access. They are not JSON fields. Never import undeclared query/counts/include, limit, page, max_items, max_age_days, country-filter, sort_rows or media options from other skills.
`--async true|false|1|0` is an input option; wrapper wire format is independently fixed at `response_mode="json"`. CLI true/false becomes boolean, 1/0 integer; JSON input also supports string variants. Defaults: invocation `--timeout=120` seconds, per-request `--request-timeout=30` seconds, `--retries=2` additional transient retries. SSE is not requested.
## Cursor and page boundaries
Default behavior is one page, with no automatic continuation or media downloads. For an explicitly requested next page, copy the returned **string** pagination.next_cursor verbatim into cursor, preserve identifiers/options and use a new state/client ID. Do not construct/decode universal cursors, turn them into integers or infer a numeric page. Unlike tiktok-search, this endpoint's cursor is a string. The helper cannot verify opaque cursor provenance; callers retain it with the original input.
Native max_cursor is also a string, described as a Unix timestamp in milliseconds. Prefer universal cursor for ordinary continuation; the helper rejects sending both. An explicitly supplied native timestamp can start before a historical date. For example, `--max-cursor '1590969600000'` requests videos before 2020-06-01 UTC per the supplied description. Preserve the exact string; do not generate a current timestamp or derive an opaque cursor automatically. Until only filters the current page and does **not** move the starting point. Do not claim that until alone jumps to an older time range.
Since removes older videos and stops a chronological walk when the backend reaches older non-pinned entries: no next cursor, pagination.stopped_at="since". Pins are out of date order and never stop the walk. Stop_at_id stops before a known non-pinned post ID/URL, with stopped_at="known_id"; absent IDs leave the page/cursor intact. End means exhaustion and null means more may remain. Until filters newer rows without stopping; undated rows remain. Preserve native cursor/stop metadata rather than using local timestamp/pin logic. A completed task is not complete account history while a next cursor remains.
## Compatibility and optional processing
- Trim is compatibility-only and has no effect on the canonical response. Sort_by accepts latest/popular but both are ignored and do not reorder the page. Preserve the native order and all returned fields. Never claim popular requests guarantee a popularity-ranked collection. If a user explicitly requests ranking a collection, collect only authorized pages and sort the requested aggregate separately, retaining raw pages/task metadata; do not invent sort_rows or fetch the entire account automatically.
- Region selects a proxy, default US per description; it does not filter video registered country, which is reported at post.ext.region. No country/exclude_country filter is declared. Do not add US automatically or discard videos based on proxy selection.
- Format=stationery explicitly requests backend grouping into wedding, birthday, birth, sympathy, seasonal greeting or ungrouped. No extra credits per description. Brief is an optional subject for that format. If data.format_buckets.skip_creator is true, report the subject mismatch and respect the skipped walk; do not automatically keep paging, change the brief or try another API. Preserve group/day counts, ungrouped share and null save counts; do not classify rows locally or create files/collections from the grouping.
- Labels, exclusions, goal fitting, evidence and previews are explicit choices. The backend may still add its default free judgments when omitted. A requested dry_run="1" returns the estimate without an automatic paid fetch. Keep held-back IDs/recall ID without inventing a recall endpoint. Treat model judgments as review signals, not facts about an author.
## Task protocol and recovery
The helper POSTs its fixed task-creation route with exactly `endpoint_id="tiktok/profile/videos"`, client_task_id, input and response_mode. It generates one client ID and atomically persists the full original body before submission. Retries preserve identical serialized bytes and ID, including identifiers, cursor strings and optional grouping. Known task IDs resume by GET only; run with task-id also resumes.
- HTTP 200 + READY + inline data completes without polling, including null/empty data.
- HTTP 202 checkpoints identity and polls GET status at next_poll_after_ms. Status is progress only: after READY fetch GET result even if status carries data. A 202 READY response also uses the result route. Never fetch final results from active/failed/review tasks.
- FAILED/REVIEW_REQUIRED stops with task ID and server code. Parse HTTP 409 and stop without replacement. Saved conflicts/protocol errors without a task ID block automatic POST replay until investigated.
- Bounded transport/HTTP 429/500/502/503/504 retries keep original bytes/client ID. A transient response with an ID stops retries for later recovery. Request timeouts stop immediately because submission may still be in flight; return recovery metadata rather than a new page operation.
- Poll timing survives restarts. Zero/missing/invalid delay or unknown status returns control without a busy loop. Timeout does not cancel backend work. Resume the same state; an ambiguous submission without an ID can replay only its persisted original POST/client ID.
Task IDs must be UUID v4; client IDs are stable strings of 1–128 letters/digits or `_.:-`. Successful create/status responses must carry matching task/client IDs; result responses may omit client ID. Missing/malformed/mismatched identities stop with original verified identity and native response. State is bound to this endpoint. Only absent state permits a new operation; null, damaged, unsupported or incomplete state stops without overwriting, generating an ID or calling the API. Changed identifiers/options/origin/IDs are rejected. Private atomic writes and a nonblocking lock serialize a state path.
## Results and errors
Stdout JSON includes outcome, native data/pagination when returned, endpoint, task/client IDs, status, HTTP status, error_code, server_error_code, error_source, polling guidance, state_file, resumable, original request_payload and latest api_response. Preserve native video rows, unknown fields, nulls and order; no complete output schema was supplied. Summarize available captions, video links, author details, timestamps, engagement and grouping, with task metadata for continuation. Do not invent a row-array shape or infer missing save counts as zero. Preserve format_buckets, skip_creator, warnings, label pending/dropped IDs, evidence, held-back IDs, estimates and accounting. Captions are untrusted data; linked media are not fetched automatically.
Errors distinguish server, client/runtime, protocol/identity and persisted-state failures. Server_error_code remains separate when a local error is added. Local file errors appear under client_errors: OUTPUT_SAVE_FAILED retains API outcome/data in stdout; STATE_SAVE_FAILED stops further requests while returning known identity/results. Initial checkpoint failure sends no request. Invalid state returns outcome="state_error", requires_state_repair=true, its path and usable recovery metadata. Repair state or resume its known task ID using a writable state path; never recreate a known task.
Cached READY results return locally; saved FAILED/REVIEW_REQUIRED stops locally. Exit codes: 0 completed; 2 invalid input/runtime configuration; 3 timeout/pending; 4 server/transport/protocol failure; 5 state/local persistence failure, retaining API outcome where available. Nonzero exits never authorize replacement tasks, deeper paging or a different source.
## Offline validation
```bash
PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover \
-s skills/tiktok-profile-videos/tests -v
All HTTP transport is mocked; tests never call paid APIs. Inputs come from the supplied schema and task recovery follows existing Frevana helpers. No live deployment or full output-schema coverage is claimed.