Skip to main content
Glama
README.md
<h1 align="center">adkit</h1>

<p align="center">
  <b>Plug-and-play Meta (Facebook + Instagram) ads automation.</b><br>
  Drive your entire ad chain from the terminal or from Claude Code, with AI-generated creative. Bring your own keys.
</p>

<p align="center">
  <code>verify</code> · <code>targeting</code> · <code>generate</code> · <code>campaign</code> · <code>adset</code> · <code>creative</code> · <code>ad</code> · <code>leadform</code> · <code>automate</code>
</p>

<p align="center">
  <a href="https://github.com/jatinjain25/adkit/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/jatinjain25/adkit/actions/workflows/ci.yml/badge.svg"></a>
  <img alt="Python" src="https://img.shields.io/badge/python-3.10%2B-blue">
  <img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-green">
</p>

---

Launching Meta ads through the Marketing API is fiddly: tokens and scopes, the Page to Instagram link, targeting IDs, creative uploads, the campaign to ad-set to creative to ad chain, and the ever-present risk of accidentally spending money. adkit turns that into a handful of clean commands, plus one command that builds a whole campaign from a single brief file. It is safe by default: every object is created **PAUSED**, and the end-to-end automation is **dry-run** until you explicitly say go.

It also plugs straight into **Claude Code**, so you can run your ads by talking to an agent, with cost and safety guardrails built in.

## The adkit flow

adkit is the whole journey of automating an ad, one command per step:

**Research** competitors → write a **Brief** → generate **Creative** → **Build** the campaign → **Launch** it → **Optimize** what runs.

```
research  →  init / brief  →  generate  →  automate  →  ad activate  →  optimize
```

## Try it in 30 seconds (no account needed)

```bash
pipx install meta-adkit     # or: pip install meta-adkit
adkit demo
```

`adkit demo` prints the exact plan adkit would build from a brief. It writes nothing, calls nothing, and needs no Meta account or API keys, so you can see the whole flow before you set anything up:

```
=== adkit demo [DRY RUN] ===
objective=OUTCOME_TRAFFIC
campaign: Example | TOF | Traffic
  adset: Agent builders  budget=2500 (minor units)
    ad: Expensive chatbot  [image]  cta=LEARN_MORE
    ad: Buy vs build  [image]  cta=SIGN_UP
```

When you're ready to run it on a real account, `adkit init my-brief.yaml` writes a starter brief and `adkit verify` walks you through connecting Meta.

> The command is `adkit`; the PyPI package is `meta-adkit` (the name `adkit` was already taken).

## Why adkit

- **One brief, one command.** Describe a campaign in YAML and `adkit automate launch` builds the campaign, ad sets, creatives, and ads for you.
- **AI creative, cost-aware.** Generate feed images and Reels videos with `adkit generate`, with a running spend log so you always know what a run cost.
- **Safe by construction.** Objects are created PAUSED. The one command that spends (`ad activate`) and the ones that generate media are the only ones that cost anything, and they say so.
- **Bring your own keys, keep them yours.** Every secret is read from a local `.env` that is gitignored. Nothing sensitive is ever committed. See [SECURITY.md](SECURITY.md).
- **Claude Code native.** Ships a skill and slash commands so an agent can drive it end to end.

## Install

Pick whichever fits how you want to use it.

**As a command-line tool** (isolated, recommended):

```bash
pipx install "meta-adkit[yaml]"     # or: uvx --from "meta-adkit[yaml]" adkit --help
```

**As a library or to hack on it:**

```bash
git clone https://github.com/jatinjain25/adkit
cd adkit
pip install -e ".[dev]"             # dev extra = yaml + mcp + pytest + ruff
```

**As a Claude Code plugin** (adds the slash commands, skill, and MCP server in one step):

```bash
# in Claude Code
/plugin marketplace add jatinjain25/adkit
/plugin install adkit
```

## Connect your Meta account (when you're ready to go live)

Everything above works with no account. To build on a real ad account, configure your credentials once:

```bash
cp .env.example .env                # fill in your own values, never commit .env
```

adkit looks for `.env` in your current directory (and its parents), then in
`~/.config/adkit/.env`, so it works the same whether you installed from a clone,
via `pipx`, or as a Claude Code plugin. Put a `.env` in the project you run adkit
from, or a user-wide one at `~/.config/adkit/.env`. `ADKIT_ENV=/path/to/.env`
overrides everything. `adkit verify` prints which file it loaded.

You need a Meta app with a token that has ads and pages scopes, an ad account, and a Page linked to an Instagram account. Walkthrough: [docs/setup-token.md](docs/setup-token.md).

For AI creative generation you also need a `GEMINI_API_KEY` and the [gemskills](https://github.com/b-open-io/gemskills) toolkit. That part is optional; the ad automation works without it. To run adkit as an MCP server, install the `mcp` extra: `pipx install "meta-adkit[mcp]"`, then point your agent at the `adkit-mcp` command.

## 60-second tour

```bash
# 1. Confirm your credentials, scopes, IG link, and ad account are healthy
adkit verify

# 2. Find targeting IDs
adkit targeting search "LangChain"
adkit targeting search "CTO" --type adworkposition

# 3. Generate a creative (prints the ~cost first; logs spend)
adkit generate image "Dark developer-brand ad, bold headline 'Give your AI a memory'" \
  --out creatives/hook.png --aspect 1:1

# 4. Launch a whole campaign from a brief. Dry run first (writes nothing):
adkit automate launch --brief examples/briefs/example.yaml
# Then build it for real. Everything is created PAUSED:
adkit automate launch --brief examples/briefs/example.yaml --go

# 5. When you are ready to spend, go live. This flips the ad AND its parent
#    ad set and campaign to ACTIVE (all three must be active to deliver):
adkit ad activate --ad-id <id>
```

## Research competitors first

Before you build anything, see what already works in your market:

```bash
adkit research --keyword "edtech" --country IN
```

adkit pulls matching ads from Meta's **official Ad Library API** (not scraping, which violates Meta's Terms), then ranks advertisers by **how long and how widely they run** their ads. Meta does not expose impressions or spend for commercial ads, so longevity and variant count are the honest proxy: advertisers kill losers fast, so a long-running, heavily-varied ad is almost certainly a winner. You get the winning advertisers, their longest-running ads (with snapshot links to view), and the common hooks, offers, CTAs, and placements, so you can design your ad from what's proven.

Turn the findings straight into a starter brief, so research feeds your creative:

```bash
adkit research --keyword "edtech" --country IN --seed-brief edtech.yaml
# then edit the copy and: adkit automate launch --brief edtech.yaml
```

Research is meant to come **before you make creatives**. `adkit generate` and `automate` print a gentle one-line reminder if you haven't researched recently (silence it with `ADKIT_NO_TIPS=1`), and the Claude Code skill runs research as step one.

First use requires a one-time identity confirmation and terms acceptance at [facebook.com/ads/library/api](https://www.facebook.com/ads/library/api); adkit tells you if it's not set up yet. API coverage of non-EU commercial ads can be sparse; the public Ad Library website is the manual fallback.

## The brief

A brief is a declarative description of a campaign. adkit reads it top to bottom and creates each object, optionally generating the creative first.

```yaml
campaign:
  name: "Example | TOF | Traffic"
  objective: OUTCOME_TRAFFIC
  daily_budget: 5000            # minor units: 5000 = $50.00
adsets:
  - name: "Agent builders"
    daily_budget: 2500
    countries: [US, GB, CA]
    interest_ids: []            # from `adkit targeting search`
    ads:
      - name: "Expensive chatbot"
        message: "Your agent forgets every user. Add memory in one API call."
        headline: "Give your product a memory"
        link: "https://example.com"
        cta: LEARN_MORE
        image: "creatives/hook.png"
        # or generate it on the fly:
        # generate: { type: image, prompt: "...", aspect: "1:1" }
```

Full example with comments: [examples/briefs/example.yaml](examples/briefs/example.yaml).

## Optimize live ads (kill losers, scale winners)

Once ads are delivering, `adkit optimize report` pulls each ad's performance and tells you, in plain language, what to do — without touching anything:

```bash
# Lead-gen: judge on cost per QUALIFIED lead (pulls the form's leads and scores quality)
adkit optimize report --target-cpl 3.00 --lead-form-id <form_id>

# Marketing: judge on ROAS from revenue you supply
adkit optimize report --revenue <ad_id>=480 --target-roas 2.0
```

Each ad comes back as **KILL** (over target, draining budget), **SCALE** (a winner worth more budget), **KEEP**, or **WAIT** — and it will never judge an ad still in Meta's learning phase (the 2-3 day / budget window is a policy default). The report prints the exact command to enact each call. Nothing changes until you run it:

```bash
adkit optimize apply --ad-id <id> --action pause --yes    # stop a loser
adkit optimize apply --ad-id <id> --action scale --yes    # raise the winner's ad set budget
```

**Build your own logic.** The decision engine is a pure, importable function with an overridable policy — swap the thresholds or the whole function; the CLI and the agent use whatever you set:

```python
from adkit.optimize import EvaluationPolicy, evaluate_account

policy = EvaluationPolicy(target_cpl=3.00, min_days_before_judging=3, scale_budget_pct=30)
report = evaluate_account(policy=policy, lead_form_id="<form_id>")
```

Lead quality is scored locally (implausible phone, disposable/invalid email, duplicates) and **lead PII is never written to disk, never printed unredacted, and never returned over MCP** — only aggregate counts. Pulling leads needs the `leads_retrieval` scope; `adkit verify` flags it if missing.

## Drive it from Claude Code

adkit ships a `.claude/` folder with a skill and two slash commands, so an agent can operate your account with the same guardrails:

- `/launch-campaign <plain-language brief>` drafts a brief, dry-runs it, and waits for your go.
- `/make-creative <description>` writes a prompt, tells you the cost, and generates the asset.

The skill teaches Claude the golden rules: read-only commands are free, creation is PAUSED and safe, and only `ad activate` and `generate` cost money or go live. More in [docs/claude-code.md](docs/claude-code.md).

## Use it as a library

Every operation is a plain function in `adkit.core`, so you can build adkit into your own code:

```python
from adkit import core

core.verify_credentials()                       # health check
ids = core.search_targeting("LangChain")         # find interest IDs
camp = core.create_campaign("My campaign", objective="OUTCOME_TRAFFIC")
core.launch_from_brief(brief_dict, go=True)      # or build a whole campaign
```

The CLI and the MCP server are both thin layers over these functions, so there is one implementation of each operation.

## Use it as an MCP server

Any MCP-capable agent (Claude Code, Cursor, Claude Desktop, or your own app) can drive adkit. Install the `mcp` extra so the `adkit-mcp` command exists:

```bash
pipx install "meta-adkit[mcp]"
```

**Claude Code** — one line:

```bash
claude mcp add adkit -- adkit-mcp
```

**Any MCP client** — drop this into your client's config (e.g. `~/.cursor/mcp.json`, or Claude Desktop's config):

```json
{
  "mcpServers": {
    "adkit": {
      "command": "adkit-mcp"
    }
  }
}
```

It exposes tools like `verify`, `search_targeting`, `create_campaign`, `launch_brief`, and `generate_image`. The tool descriptions carry the same safety notes: creation is PAUSED, and the tools that cost money or go live say so. Actions that spend money or start delivery (`activate_ad`, `generate_image`, `generate_video`, `launch_brief --go`) are refused unless the operator sets `ADKIT_ALLOW_SPEND=1` in the server's environment, so an auto-approving client or a prompt injection can't spend on your behalf.

## Commands

| Command | What it does |
| --- | --- |
| `adkit demo` | Dry-run the whole flow with no account or keys. Start here. |
| `adkit init [brief.yaml]` | Write a starter campaign brief you can edit. |
| `adkit research` | Research competitor ads in the Ad Library; rank winners by longevity. |
| `adkit verify` | Check token validity, scopes, Page to Instagram link, ad account. |
| `adkit targeting search` | Look up interest and job-title IDs from Meta's taxonomy. |
| `adkit generate image \| video \| spend` | Generate AI creative and see spend. |
| `adkit campaign create \| activate \| pause \| list` | Create, toggle, and list campaigns. |
| `adkit adset create \| activate \| pause \| list` | Create ad sets with targeting; toggle them. |
| `adkit creative create \| list` | Build image or video creatives. |
| `adkit ad create \| activate \| pause \| list` | Build ads; `activate` takes the whole ad→set→campaign chain live. |
| `adkit leadform create \| list` | Create Instant Forms for lead-gen. |
| `adkit automate launch` | Build a whole campaign from a brief (dry run unless `--go`). |
| `adkit optimize report` | Analyze live ads; recommend KILL/SCALE/KEEP. Read-only. |
| `adkit optimize apply` | Enact one recommendation (pause a loser / scale a winner). |

## Safety model

- **Nothing spends by accident.** Campaigns, ad sets, and ads are all created PAUSED. A Meta ad only delivers when the ad, its ad set, and its campaign are *all* ACTIVE, so `adkit ad activate` flips the whole chain live in one step (other ads in the set stay PAUSED). Use `--ad-only` to flip just the ad.
- **Dry run by default.** `automate launch` prints the plan and writes nothing until `--go`.
- **Costs are surfaced.** `generate` prints an estimate before it runs and logs every call to `creatives/.spend.log`.
- **Secrets stay local.** All credentials come from `.env` (gitignored). See [SECURITY.md](SECURITY.md).

## Contributing

Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md). adkit is MIT licensed.

## Acknowledgements

AI creative generation is powered by [gemskills](https://github.com/b-open-io/gemskills) (Gemini images and Veo video).

---

Built by [Jatin Jain](https://github.com/jatinjain25), who got tired of clicking through Ads Manager. If adkit saved you some clicks, a star helps others find it.