Guides / End-to-end tutorials / Geo & persona feeds A–Z

Geo & persona feeds A–Z

Provision a dedicated city-pinned device context, run bootstrap and niche warmup, compare For You distributions across Miami, London and Tokyo, and batch-enrich discovered videos.

1. Dedicated devices vs. persona snapshots

Combine persistent Growth+ device contexts (devices.provision → bootstrap → warm) for longitudinal tracking with stateless /v1/feed/persona-fyp pulls across miami, london and tokyo, then enrich unique aweme_ids asynchronously via /v1/jobs/submit.

ActionEndpointCost
Provision devicePOST /v1/devices/provision10 cr
Bootstrap devicePOST /v1/devices/{id}/bootstrap10 cr
Warm device feedPOST /v1/devices/{id}/warm10 cr
FeedsGET /v1/feed5 cr
Persona For You feedGET /v1/feed/persona-fyp10 cr
Submit batch jobPOST /v1/jobs/submitper task
Get jobGET /v1/jobs/{id}Free

2. Multi-city feed comparison & async batch enrichment

TypeScript SDKPython SDKcURL / MCPgeo_persona
1import { TikUltra } from 'tikultra'
2
3const client = new TikUltra({ apiKey: process.env.TIKULTRA_KEY })
4const dev    = await client.devices.provision({ os: 'android', app_flavour: 'tiktok', region: 'US', city: 'miami' })
5const devId  = dev.data.device_id
6await client.devices.bootstrap(devId, { warmup: true })
7await client.devices.warm(devId, { niche: 'specialty coffee', duration_sec: 45 })
8
9const [warmedFyp, miami, london, tokyo] = await Promise.all([
10  client.withMatrix({ deviceId: devId }).feed.fyp({ count: 15 }),
11  client.feed.personaFyp({ niche: 'specialty coffee', region: 'US', city: 'miami', count: 15 }),
12  client.feed.personaFyp({ niche: 'specialty coffee', region: 'GB', city: 'london', count: 15 }),
13  client.feed.personaFyp({ niche: 'specialty coffee', region: 'JP', city: 'tokyo', count: 15 }),
14])
15const uniqueIds = [...new Set([warmedFyp, miami, london, tokyo].flatMap((r) => (r.data.items ?? []).map((x: any) => x.aweme_id)))].slice(0, 40)
16const job       = await client.jobs.submit({
17  tasks: uniqueIds.map((aweme_id) => ({ path: '/v1/video/fyp-velocity', query: { aweme_id } })),
18  webhook_url: 'https://hooks.acme.dev/tikultra-geo',
19})
20const finished = await client.jobs.wait(job.data.job_id, { pollIntervalMs: 2000, timeoutMs: 60_000 })
21console.log({ devId, uniqueVideos: uniqueIds.length, jobStatus: finished.data.status })
65 credits + per-task enrichment. Device provision (10) + bootstrap (10) + warm (10) + device FYP (5) + 3 persona feeds (30) = 65 credits; GET /v1/jobs/{id} polling is free. On Free/Starter plans, catch PermissionDeniedError (403) and use feed.personaFyp directly (public on all plans).