URL: /code/integrations/tiktok-ads · Id: `page:/code/integrations/tiktok-ads`

# TikTok Ads

> Your agent promotes the TikTok clip that beat your median views, signed in as you, and reads what each ad spent and earned.

Your agent promotes the TikTok clip that beat your median views, signed in as you, and reads what each ad spent and earned.

```text title="Run it · Ask"
Turn my late-rent clip into a Spark Ad at $25 a day for 7 days, and show me what TikTok would accept.
```

```ts title="Run it · SDK"
import { $ } from 'startup-builder'

const result = await $.try.Campaign.create({ venue: 'tiktok', objective: 'traffic', budget: { amount: 25, currency: 'USD', per: 'day' }, days: 7, ads: ['posts/tiktok-late-rent'] })
```

```json title="Run it · Response"
{
  "$type": "Campaign",
  "mode": "try",
  "stored": false,
  "venue": "tiktok",
  "format": "Spark Ad",
  "adGroupBudget": {
    "amount": 25,
    "currency": "USD",
    "per": "day"
  },
  "campaignBudget": "unset",
  "total": {
    "amount": 175,
    "currency": "USD"
  },
  "checks": {
    "adGroupFloor": "over the $20 floor"
  },
  "runsBy": "browser",
  "steps": 16
}
```

- **Connect:** Sign in once, 3 grants
- **Runs through:** [Your account or a browser agent](/code/integrations#three-ways)
- **First run:** Read the ads running on your category’s words, about 0.2¢
- **Sites:** [TikTok](/venues#tiktok)

## Connect once
1. Call `Integration.connect` with `system: 'ads'` and `venue: 'tiktok'`, then open the `connectUrl` it returns and sign in as yourself with TikTok for Business.
2. Authorize the ad account, then sign in once more in the browser window the platform opens; that saved session builds the Spark Ads.
3. Set the spend on your standing approval with `Mandate.authorize`; TikTok bills your ad account, never the platform.

| Grant | What it unlocks | Scope |
| --- | --- | --- |
| Read your ad account | Campaigns, ad groups with their bids and targeting, and ads with their creative and landing page | `Ad Account Management` |
| Read results | Spend, impressions, clicks and conversions by ad | `Reporting` |
| Your saved session | Spark Ads built from your own clips, and budget changes, in TikTok Ads Manager | `browser session: ads.tiktok.com` |

| To | Do this |
| --- | --- |
| Pause | `Integration.pause` stops every call through TikTok at once and keeps the token; `Integration.resume` picks up from the same cursor. |
| Reconnect | `Integration.reconnect` returns a fresh `connectUrl` that lasts one hour, and keeps the Integration's id and cursor, so nothing imports twice. |
| Disconnect | `Integration.revoke` revokes the token with TikTok; every record it brought in stays in your records. |

## What your agents do
| Ask | Verb | Runs through | Price |
| --- | --- | --- | --- |
| Turn my late-rent clip into a Spark Ad at $25 a day for 7 days, signed in as me. | [`Campaign.create`](/code/apps/ads/create-campaign) | A browser agent | About 1.6¢ a step |
| Add my second-best clip to the same ad group, signed in as me. | [`Ad.create`](/code/apps/ads/create-ad) | A browser agent | About 1.6¢ a step |
| Pause the clip with the higher cost per landing-page view, signed in as me. | [`Campaign.update`](/code/apps/ads/update-campaign) | A browser agent | About 1.6¢ a step |
| Show me spend, views and cost per landing-page view by ad this week. | [`Metric.query`](/code/apps/analytics-and-experiments/query) | Your account | Free |

Calls on your own account are free; TikTok bills your spend directly.

Reading this site’s public data needs no account: [`Ad.find`](/code/data/ads/find) is a Data call (the [three ways a call runs](/code/integrations#three-ways)).

## What it holds
| Noun | The account’s object | When | Which side leads |
| --- | --- | --- | --- |
| [Campaign](https://startupbuilder.dev/business/nouns/campaign) | `campaign and ad group` | Your agent builds it | Your records |
| [Ad](https://startupbuilder.dev/business/nouns/ad) | `Spark Ad` | Your agent builds it; TikTok’s review sets its status | Your records |
| [Entry](https://startupbuilder.dev/business/nouns/entry) | `report: spend` | Each day’s spend settles | The account |

## Events and webhooks
| Direction | Event |
| --- | --- |
| Emits | `Campaign.created`, `Campaign.updated`, `Ad.created`, `Entry.posted` |

Any Event can wake a Workflow, and a Watch delivers it to your endpoint, signed ([Events and webhooks](/code/events)).

## Limits
| What | Limit | Call |
| --- | --- | --- |
| Budget minimums | An ad group’s daily budget must exceed $20, and a campaign’s daily budget, when you set one, must exceed $50 | [`Campaign.create`](/code/apps/ads/create-campaign) |
| Spark Ads | Promote a post already on your TikTok account, with its likes and comments kept | [`Ad.create`](/code/apps/ads/create-ad) |
| Ad review | TikTok reviews every ad; a rejection comes back as `venue-rejected` with the reason | [`Ad.create`](/code/apps/ads/create-ad) |
| Spend | Your standing approval holds the cap; a change past it waits with `over-mandate` | [`Campaign.update`](/code/apps/ads/update-campaign) |

## Start here
| Move | The rule |
| --- | --- |
| Structure | One campaign with no campaign budget, one ad group, and Spark Ads from the 2 clips that beat your median views by 3 times. |
| Budget | The ad group floor sets the test: its daily budget must exceed $20, so $25 a day for 7 days is $175. Set no campaign budget, or more than $50 a day if you set one. |
| Creative | Spark Ads from your own posts, so your handle, likes and comments carry over; vertical, with the problem said in the first 2 seconds. |
| Kill rule | At day 7, keep the clip with the lower cost per landing-page view; with no signup at $175, stop both and go back to posting. |

| Benchmark | Source |
| --- | --- |
| Ad group daily budget over $20; campaign daily budget over $50 when one is set | TikTok Ads Manager help |
| 7 days at $25 a day is $175 | Our calculation |
| Promote a clip only at 3 times your median views | Our planning number |

## Field map
| Our field | TikTok Ads’s field | Notes | Calls |
| --- | --- | --- | --- |
| `Campaign.objective` | `campaign objective_type` | `traffic` is TRAFFIC; `leads` is LEAD_GENERATION with TikTok’s instant form; `conversions` is WEB_CONVERSIONS on your pixel | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.budget` | `adgroup budget with budget_mode BUDGET_MODE_DAY` | Over $20; the campaign budget stays unset unless you ask, and then it is over $50 | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign), [`Ad.create`](/code/apps/ads/create-ad) |
| `Campaign.startsAt` | `adgroup schedule_start_time` | None. | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.endsAt` | `adgroup schedule_end_time` | Set with schedule_type SCHEDULE_START_END | [`Campaign.create`](/code/apps/ads/create-campaign), [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.state` | `adgroup operation_status` | `live` is ENABLE, `paused` DISABLE | [`Campaign.update`](/code/apps/ads/update-campaign) |
| `Campaign.targeting` | `adgroup location_ids, age_groups and interest_category_ids` | TikTok targets people, not job titles: an interest stands in for a role | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Campaign.bid` | `adgroup bid_type` | `lowest-cost` is BID_TYPE_NO_BID; a `costPer` cap is BID_TYPE_CUSTOM with bid_price | [`Campaign.create`](/code/apps/ads/create-campaign) |
| `Ad.media` | `ad tiktok_item_id` | A Spark Ad: your own post, authorized for the ad, keeping its likes and comments | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.callToAction` | `ad call_to_action` | Such as LEARN_MORE or SIGN_UP | [`Ad.create`](/code/apps/ads/create-ad) |
| `Ad.landingUrl` | `ad landing_page_url` | None. | [`Ad.create`](/code/apps/ads/create-ad) |

## What try does here
Returns the Spark Ad and its ad group as Ads Manager will hold them, checked against TikTok’s budget floors, with the steps the browser agent would take; nothing is created.

## How long a connection lasts
A connection lasts 365 days from your first sign-in, the life of TikTok’s refresh token; the platform renews the 24-hour access token on its own.

The platform asks you to reconnect 7 days before it runs out; until you do, a call that needs it refuses with [`needs-reauth`](/code/refusals#needs-reauth).

## Formats
| Site | Format | Size | Limits |
| --- | --- | --- | --- |
| [TikTok](/venues#tiktok) | Video | None. | Caption 2,200 characters, hashtags included; length up to what your account reports |
| [TikTok](/venues#tiktok) | Spark Ad | None. | A post already on your account, promoted with its likes and comments kept |

## Plays and strategies
| Kind | Where |
| --- | --- |
| Plays | [Paid social](/plays/paid-social), [Creator ads](/plays/creator-ads), [Promote winners](/plays/promote-winners) |
| Strategies | [Paid](/strategy/paid) |

## Used by
No Workflow, agent task or Service acts through it yet: your agents call it directly.

## Reference

| Call | Path | Price |
| --- | --- | --- |
| [`Campaign.create`](/code/apps/ads/create-campaign) | `POST /campaigns/create` | About 1.6¢ a step |
| [`Ad.create`](/code/apps/ads/create-ad) | `POST /ads/create` | About 1.6¢ a step |
| [`Campaign.update`](/code/apps/ads/update-campaign) | `POST /campaigns/update` | About 1.6¢ a step |
| [`Metric.query`](/code/apps/analytics-and-experiments/query) | `POST /metrics/query` | Free |