Skip to content

bilalbayram/metacli

Repository files navigation

Meta Marketing CLI

Meta Marketing CLI is a developer-first, fail-closed command-line interface for running Meta marketing operations end-to-end without Ads Manager. It combines automated auth setup, direct Graph API access, campaign/ad lifecycle workflows, Instagram publishing, reliability checks, and enterprise controls behind a stable machine-readable output contract.

Features

  • Automated auth setup with browser callback (meta auth setup)
  • Keychain-only secret storage (no env/plaintext fallback)
  • Fail-closed profile preflight before operational commands
  • Direct Graph API access (api get/post/delete/batch)
  • Insights account discovery, raw action-type discovery, publisher-platform filtering, and metric packs (insights accounts list, insights action-types, insights run --metric-pack quality|local_intent)
  • Campaign, ad set, ad, creative (image + video), audience, and catalog workflows
  • Budget mutation guardrails for spend-changing writes
  • Instagram media upload, status, publish, schedule lifecycle, and raw account/media insights
  • Operations intelligence checks (schema drift, rate limits, policy preflight)
  • Stable output envelope (contract_version: 1.0) for automation

Installation

Prerequisites

  • Go 1.23+
  • macOS Keychain or Linux Secret Service (required)
  • jq (recommended for scripting and smoke checks)

Install with go install

go install github.com/bilalbayram/metacli/cmd/meta@latest
meta --help

Build locally

go build -o meta ./cmd/meta
./meta --help

Quick Start

1) Get APP_ID, APP_SECRET, REDIRECT_URI

Create a Meta app in the developer dashboard and collect your app credentials.

Use this callback URI pattern for CLI auth automation:

REDIRECT_URI=https://<REDIRECT_URI>/oauth/callback

You must allow this redirect URI in your app OAuth settings, Meta doesn't allow http, you can use cloudflared or similar service for it:

cloudflared tunnel --url http://127.0.0.1:53682

2) Scopes

Scope packs are used by auth setup to request a practical scope set for your workflow (solo_smb, ads_only, ig_publish).

Official docs:

3) Setup auth and validate profile

./meta auth setup \
  --profile prod \
  --app-id <APP_ID> \
  --app-secret <APP_SECRET> \
  --mode both \
  --scope-pack solo_smb \
  --listen 127.0.0.1:53682 \
  --timeout 180s \
  --open-browser

./meta auth validate \
  --profile prod \
  --min-ttl 72h

Usage

Graph API Directly

./meta --profile prod api get act_<AD_ACCOUNT_ID>/campaigns \
  --fields id,name,status \
  --follow-next \
  --limit 100

./meta --profile prod api post act_<AD_ACCOUNT_ID>/campaigns \
  --params "name=Launch Campaign,objective=OUTCOME_SALES,status=PAUSED"

./meta --profile prod api delete <OBJECT_ID>

Ad Creation

Schema-aware commands look in ~/.meta/schema-packs by default. Run ./meta schema sync first if you have not populated that directory yet.

./meta --profile prod campaign create \
  --account-id <AD_ACCOUNT_ID> \
  --params "name=Launch Campaign,objective=OUTCOME_SALES,status=PAUSED"

./meta --profile prod adset create \
  --account-id <AD_ACCOUNT_ID> \
  --params "name=Prospecting,campaign_id=<CAMPAIGN_ID>,status=PAUSED,billing_event=IMPRESSIONS,optimization_goal=OFFSITE_CONVERSIONS"

./meta --profile prod ad create \
  --account-id <AD_ACCOUNT_ID> \
  --params "name=Launch Ad,adset_id=<ADSET_ID>,status=PAUSED" \
  --json '{"creative":{"creative_id":"<CREATIVE_ID>"}}'

Insights Reporting

# Discover active ad accounts first
./meta --profile prod insights accounts list \
  --active-only \
  --output table

# Run campaign insights with expanded quality metrics
./meta --profile prod insights run \
  --account-id <AD_ACCOUNT_ID> \
  --date-preset last_7d \
  --level campaign \
  --metric-pack quality \
  --format jsonl

# Discover raw action types returned by actions and cost_per_action_type
./meta --profile prod insights action-types \
  --account-id <AD_ACCOUNT_ID> \
  --date-preset last_30d \
  --level ad \
  --format json

# Keep paid insights on Instagram placements only
./meta --profile prod insights run \
  --account-id <AD_ACCOUNT_ID> \
  --date-preset last_7d \
  --level account \
  --metric-pack local_intent \
  --publisher-platform instagram \
  --format json

# Run local-intent insights and keep raw action arrays plus flat aliases
./meta --profile prod insights run \
  --account-id <AD_ACCOUNT_ID> \
  --date-preset last_30d \
  --level campaign \
  --metric-pack local_intent \
  --format json

Notes:

  • insights run still fails closed when --account-id is missing.
  • --metric-pack basic keeps the previous default behavior.
  • --metric-pack quality requests expanded fields (CTR, CPC, CPM, reach/frequency, actions, and related cost metrics).
  • --metric-pack local_intent preserves raw actions and cost_per_action_type, then adds flat alias fields like address_taps, calls, directions, and profile_visits when those raw action types are present.
  • --publisher-platform instagram keeps the paid Ads Insights surface but forces a publisher_platform breakdown and filters the returned rows down to Instagram placements.
  • calls is populated from the raw Graph action type click_to_call_native_call_placed; other call-connect and call-confirm metrics remain raw-only so machine consumers can keep those semantics separate.
  • Place-navigation aliases are intentionally sparse. address_taps, directions, and profile_visits only appear when Graph emits the matching raw action types for your campaigns.
  • insights action-types is the quickest way to discover which raw action_type values your account is returning before you automate against them.

IG Publication

./meta --profile prod ig caption validate \
  --caption "Launch post #meta #ads" \
  --strict

./meta --profile prod ig media upload \
  --ig-user-id <IG_USER_ID> \
  --media-url https://cdn.example.com/image.jpg \
  --caption "Launch post #meta"

./meta --profile prod ig publish feed \
  --media-url https://cdn.example.com/image.jpg \
  --caption "Launch post #meta" \
  --idempotency-key publish-feed-001

# Schedule a story for next Tuesday at 4 PM UTC
./meta --profile prod ig publish story \
  --media-url https://cdn.example.com/story.mp4 \
  --caption "Coming soon" \
  --media-type STORIES \
  --publish-at 2026-03-17T16:00:00Z

# Execute all due scheduled publishes (designed for cron)
./meta --profile prod ig publish schedule run

# Preview what would be published without executing
./meta --profile prod ig publish schedule run --dry-run

# List scheduled jobs
./meta --profile prod ig publish schedule list --status scheduled

IG Insights

# Fetch raw Instagram account insights
./meta --profile prod ig insights account run \
  --metric profile_views \
  --period day \
  --metric-type total_value \
  --since 2026-03-14 \
  --until 2026-03-21 \
  --format json

# Discover recent media ids plus identity fields for follow-up insights calls
./meta --profile prod ig insights media list \
  --limit 10 \
  --format json

# Fetch raw organic media insights for one or more posts
./meta --profile prod ig insights media run \
  --media-id <MEDIA_ID> \
  --metric profile_visits \
  --period lifetime \
  --format json

# Fetch Instagram account local-intent summary plus raw metric payloads
./meta --profile prod ig insights account local-intent \
  --since 2026-03-14 \
  --until 2026-03-21 \
  --format json

# Combine paid Instagram Ads Insights with Instagram account local-intent metrics
./meta --profile prod ig insights local-intent \
  --account-id <AD_ACCOUNT_ID> \
  --date-preset last_7d \
  --format json

Notes:

  • ig insights account run stays close to the raw Instagram account-insights metric objects returned by Graph.
  • ig insights media run uses the Instagram media insights surface, which Meta documents as organic-only.
  • ig insights account local-intent preserves raw metric objects and adds normalized summary fields like calls, directions, email_contacts, text_contacts, book_now, and profile_views when the corresponding contact button breakdowns are present.
  • ig insights local-intent returns a layered object with paid_instagram, instagram_account, and a merged summary so the paid and Instagram-account surfaces stay distinguishable.

Cron setup (run every 30 minutes):

*/30 * * * * /usr/local/bin/meta --profile prod ig publish schedule run

Marketing Workflows

Primary command families:

  • campaign: list, create, update, pause, resume, clone
  • adset: list, create, update, pause, resume
  • ad: list, create, update, pause, resume, clone
  • creative: upload, upload-video, create
  • audience: create, update, delete, list, get
  • catalog: upload-items, batch-items

Creative video upload example:

./meta --profile prod creative upload-video \
  --account-id <AD_ACCOUNT_ID> \
  --file ./assets/launch.mp4 \
  --wait-ready \
  --timeout 10m \
  --poll-interval 5s

Campaign/ad set/ad list examples:

# List campaigns with shared read filters
./meta --profile prod campaign list \
  --account-id <AD_ACCOUNT_ID> \
  --fields id,name,status,effective_status,objective \
  --name launch \
  --status ACTIVE,PAUSED \
  --effective-status ACTIVE \
  --active-only \
  --page-size 50 \
  --follow-next \
  --limit 100

# List ad sets scoped to one campaign
./meta --profile prod adset list \
  --account-id <AD_ACCOUNT_ID> \
  --campaign-id <CAMPAIGN_ID> \
  --fields id,name,status,effective_status,campaign_id

# List ads with campaign+adset intersection filtering
./meta --profile prod ad list \
  --account-id <AD_ACCOUNT_ID> \
  --campaign-id <CAMPAIGN_ID> \
  --adset-id <ADSET_ID> \
  --fields id,name,status,effective_status,campaign_id,adset_id

Budget mutation guardrail example:

./meta --profile prod campaign update \
  --campaign-id <CAMPAIGN_ID> \
  --params "daily_budget=5000" \
  --confirm-budget-change

Audience read examples:

# List audiences (default kind=all includes custom + saved)
./meta --profile prod audience list \
  --account-id <AD_ACCOUNT_ID>

# List only custom audiences
./meta --profile prod audience list \
  --account-id <AD_ACCOUNT_ID> \
  --kind custom

# List only saved audiences with selected fields and pagination controls
./meta --profile prod audience list \
  --account-id <AD_ACCOUNT_ID> \
  --kind saved \
  --fields id,name,subtype,time_updated \
  --limit 100 \
  --follow-next

# Get one audience by ID
./meta --profile prod audience get \
  --audience-id <AUDIENCE_ID> \
  --fields id,name,subtype,time_updated,retention_days

Instagram Publishing + Plugin Runtime

  • ig media upload|status
  • ig publish feed|reel|story
  • ig publish schedule list|cancel|retry
  • Plugin namespace stubs: wa, msgr, threads, capi

Operations Intelligence + Reliability

./meta --output json ops init --state-path "$HOME/.meta/ops/baseline-state.json"

./meta --output json ops run \
  --state-path "$HOME/.meta/ops/baseline-state.json" \
  --preflight-config-path "$HOME/.meta/config.yaml"

Enterprise Hardening

./meta enterprise mode cutover \
  --legacy-config ~/.meta/config.yaml \
  --config ~/.meta/enterprise.yaml \
  --org agency \
  --org-id org_1 \
  --workspace prod \
  --workspace-id ws_1 \
  --principal ops.admin

./meta enterprise execute \
  --config ~/.meta/enterprise.yaml \
  --principal ops.admin \
  --command "auth rotate" \
  --workspace agency/prod \
  --approval-token <GRANT_TOKEN> \
  --correlation-id corr-20260224-001 \
  --require-secret auth_rotation_key:rotate

Zero Ads Manager Workflow

# 1) Automated auth
./meta auth setup --profile prod --app-id <APP_ID> --app-secret <APP_SECRET> --mode both --scope-pack solo_smb

# 1.5) Sync schema packs into ~/.meta/schema-packs
./meta schema sync

# 2) Create campaign + ad set + creative + ad
./meta --profile prod campaign create --account-id <AD_ACCOUNT_ID> --params "name=CLI Campaign,objective=OUTCOME_SALES,status=PAUSED"
./meta --profile prod adset create --account-id <AD_ACCOUNT_ID> --params "name=CLI AdSet,campaign_id=<CAMPAIGN_ID>,status=PAUSED,billing_event=IMPRESSIONS,optimization_goal=OFFSITE_CONVERSIONS"
./meta --profile prod creative create --account-id <AD_ACCOUNT_ID> --params "name=CLI Creative,object_story_id=123_456"
./meta --profile prod ad create --account-id <AD_ACCOUNT_ID> --params "name=CLI Ad,adset_id=<ADSET_ID>,status=PAUSED" --json '{"creative":{"creative_id":"<CREATIVE_ID>"}}'

# 3) Publish IG content
./meta --profile prod ig publish feed --media-url https://cdn.example.com/launch.jpg --caption "Shipped from CLI" --idempotency-key launch-feed-001

# 4) Run ops check
./meta --profile prod --output json ops run --state-path "$HOME/.meta/ops/baseline-state.json"

Token Lifecycle Model

token_type Primary Use Required Profile Keys (v2) Lifecycle Path
system_user Business automation and system flows app_id, business_id, token_ref, app_secret_ref, auth metadata fields Added via auth add system-user; validated by preflight; hard-fails on invalid/TTL/scope issues
user OAuth user context for marketing + IG app_id, token_ref, app_secret_ref, auth metadata fields Created via auth setup/auth login; long-lived exchange + debug validation enforced
page Page-scoped actions app_id, page_id, source_profile, token_ref, app_secret_ref, auth metadata fields Derived via auth page-token; source credentials and preflight checks required
app App-level service token operations app_id, token_ref, app_secret_ref, auth metadata fields Created via auth app-token set; rotatable via auth rotate

Auth metadata fields required on every profile in schema v2:

  • auth_provider
  • auth_mode
  • scopes
  • issued_at
  • expires_at
  • last_validated_at

Complete Command Reference

Core API and Schema

Command Family Purpose Key Commands
auth Authentication and profile/token lifecycle add system-user, setup, login, discover, page-token, app-token set, validate, rotate, debug-token, list
api Direct Graph API access get, post, delete, batch
insights Reporting queries and export accounts list, run
lint Request lint against schema packs request
schema Local schema pack management list, sync
changelog Version/change checks check

Marketing Workflows

Command Family Purpose Key Commands
campaign Campaign lifecycle list, create, update, pause, resume, clone
adset Ad set lifecycle list, create, update, pause, resume
ad Ad lifecycle list, create, update, pause, resume, clone
creative Creative assets upload, upload-video, create
audience Audience lifecycle create, update, delete, list, get
catalog Catalog item ingestion/mutation upload-items, batch-items

Instagram and Adjacent Product Namespaces

Command Family Purpose Key Commands
ig Instagram publishing lifecycle health, media upload, media status, caption validate, publish feed, publish reel, publish story, publish schedule list/cancel/retry/run
wa WhatsApp namespace scaffold health, capability
msgr Messenger namespace scaffold health, capability
threads Threads namespace scaffold health, capability
capi Conversions API namespace scaffold health, capability

Ops and Governance

Command Family Purpose Key Commands
ops Reliability checks and report pipeline init, run
enterprise Org/workspace authorization and execution governance context, authz check, execute, mode cutover, approval request, approval approve, approval validate, policy eval

Global flags (all commands):

  • --profile <name>
  • --output json|jsonl|table|csv
  • --debug

Use ./meta <family> --help and ./meta <family> <command> --help for full flag-level details.

Output Contract

Most commands emit the stable envelope with contract_version: "1.0":

  • contract_version
  • command
  • timestamp
  • request_id
  • success
  • data
  • paging
  • rate_limit
  • error

Error payload contract (when success=false):

  • type, code, error_subcode, status_code, message, fbtrace_id, retryable
  • remediation: category, summary, actions[], fields[]
  • diagnostics: raw Meta error diagnostics for classifier coverage gaps

Exit Codes

  • 1: unknown failure
  • 2: config failure
  • 3: auth failure
  • 4: input/validation failure
  • 5: API failure

Security Model

  • Secrets are stored in OS keychain only (meta-marketing-cli service namespace)
  • Config references secrets by keychain ref (keychain://...)
  • Commands fail closed when required auth/config/schema data is missing or invalid
  • No hidden fallback to environment variables or plaintext secrets

About

Meta API in your terminal

Resources

Stars

Watchers

Forks

Contributors