Skip to content

Ads by API token and CLI: campaigns, publisher slots, and ad visits in stats - #235

Merged
ralyodio merged 1 commit into
masterfrom
worktree-ads-api
Sep 5, 2026
Merged

Ads by API token and CLI: campaigns, publisher slots, and ad visits in stats#235
ralyodio merged 1 commit into
masterfrom
worktree-ads-api

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

What

  • POST/GET /api/ads/v1/campaigns — a campaign from a URL for a crp_ bearer caller: page read, creatives written, saved active (or status: "draft"). A live campaign for the same URL is returned with existing: true instead of a twin. Optional columns (org, summaries) degrade the same way saveCampaign does.
  • POST/GET /api/ads/v1/slots — a publisher slot on a site named by hostname or URL. The site's project is found by host or created with the tracker on; the response carries embed (unit + stats.js + ad.js) and tracker.
  • CLI: crawlproof ads create <url> [--name --budget --bid --draft --json], ads list, slots create <site> [--placement --format --formats --inactive --no-tracking --json], slots list, with CRAWLPROOF_TOKEN / --token.
  • Stats: categorize() takes the landing URL and buckets ad:crawlproof-ad-NNN (our click redirect's ?ref=) or ad:<utm_source> for paid utm tags / gclid / msclkid, ahead of the referrer. Bots stay bots. bucketLabel renders Ad · ….

Callers: the myna crawlproof plugin (mynaposter 0.6.0) opens a campaign for every blog post it publishes through this endpoint.

Tests

14 new (request parsing, clamps, embed tags, CLI bodies, ad bucketing); full suite 2065 pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2

…CLI, and ad visits in stats

POST/GET /api/ads/v1/campaigns creates a campaign from a URL for a bearer
token caller: the page is read, the creatives written, the campaign saved
active. A live campaign for the same URL is returned instead of a twin.
POST/GET /api/ads/v1/slots creates a publisher slot on a site named by
hostname, finding or creating the site's project with the tracker on, and
answers with the two tags to paste.

The CLI gains `ads create|list` and `slots create|list` over the same
endpoints, with CRAWLPROOF_TOKEN.

The tracker now buckets a visit that arrived through an ad as ad:<ref>:
our click redirect appends ?ref=crawlproof-ad-NNN to the destination, and
paid utm tags name their source. Bots stay bots; the referrer no longer
hides a paid visit behind the page that carried the unit.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

ThreatCrush Security Scan

39 finding(s)

HIGH/CRITICAL: 2 | MEDIUM: 28 | LOW: 9

Severity Rule Location
HIGH tls-verification-disabled lib/onion.ts:48
HIGH secret-generic-credential lib/sp/platforms/facebook.ts:32
MEDIUM js-unescaped-html-sink app/(app)/dashboard/admin/email-broadcast/EmailBroadcastForm.tsx:125
MEDIUM js-unescaped-html-sink app/(app)/dashboard/projects/[id]/autoblog/articles/[articleId]/page.tsx:214
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:67
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:97
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:104
MEDIUM js-unescaped-html-sink app/(marketing)/blog/[slug]/page.tsx:110
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:186
MEDIUM js-unescaped-html-sink app/(marketing)/recent/page.tsx:190
MEDIUM js-unescaped-html-sink app/c/[project]/[slug]/page.tsx:77
MEDIUM js-unescaped-html-sink app/c/[project]/page.tsx:57
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:228
MEDIUM js-unescaped-html-sink app/careers.js/route.ts:285
MEDIUM js-unescaped-html-sink app/layout.tsx:129
MEDIUM js-open-redirect app/login/form.tsx:39
MEDIUM js-unescaped-html-sink app/r/[token]/page.tsx:176
MEDIUM js-open-redirect app/signup/form.tsx:43
MEDIUM js-open-redirect components/billing/buy-credits-modal.tsx:98
MEDIUM js-unescaped-html-sink components/json-ld.tsx:8
MEDIUM js-unescaped-html-sink components/report/markdown-view.tsx:15
MEDIUM js-unescaped-html-sink lib/careers/page-templates.ts:198
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:41
MEDIUM redos-nested-quantifier lib/emailMarkdown.ts:324
MEDIUM redos-nested-quantifier lib/lx/articleGen.ts:99
MEDIUM redos-nested-quantifier lib/tracker/agent-gate.ts:61
MEDIUM sh-remote-script-execution prober/deploy/provision.sh:30
MEDIUM sql-template-interpolation scripts/detect-slot-themes.ts:31
MEDIUM sql-template-interpolation scripts/purge-constructed-keywords.ts:163
MEDIUM sql-template-interpolation scripts/purge-offniche-keywords.ts:124
LOW secret-generic-credential app/(marketing)/docs/autoblog-webhook/page.tsx:145
LOW secret-generic-credential lib/sp/platforms/linkedin.ts:25
LOW js-dynamic-code-execution tests/careers-page-templates.test.ts:21
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:19
LOW js-dynamic-code-execution tests/careers-widget-script.test.ts:69
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:51
LOW js-dynamic-code-execution tests/contract/ad-visitor-id.test.ts:52
LOW secret-generic-credential tests/contract/posthog-integration.test.ts:13
LOW secret-generic-credential tests/lead-campaign.test.ts:16

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 5163f1e into master Sep 5, 2026
9 checks passed
Comment thread lib/ads/slots.ts
} else if (enableTracking && project.tracker_enabled === false) {
await sb.from("projects").update({ tracker_enabled: true }).eq("id", project.id).eq("owner_id", userId);
}
if (!project) return { ok: false, status: 500, error: "Failed to resolve the site." };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants