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.
| Action | Endpoint | Cost |
|---|---|---|
| Provision device | POST /v1/devices/provision | 10 cr |
| Bootstrap device | POST /v1/devices/{id}/bootstrap | 10 cr |
| Warm device feed | POST /v1/devices/{id}/warm | 10 cr |
| Feeds | GET /v1/feed | 5 cr |
| Persona For You feed | GET /v1/feed/persona-fyp | 10 cr |
| Submit batch job | POST /v1/jobs/submit | per task |
| Get job | GET /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).