Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2624ca2
feat(platform): add Reddit Ads client (OAuth2 + token refresh)
mrautela365 Jul 9, 2026
c15f6f5
fix(review): address Copilot findings on Reddit client (PR #21)
mrautela365 Jul 11, 2026
d6ec469
fix(review): budget overflow cap, start-vs-end guard, normalized camp…
mrautela365 Jul 11, 2026
fcd7f27
fix(review): validate registration URL, surface body-read errors, pin…
mrautela365 Jul 11, 2026
1b42b21
refactor(reddit): extract magic literals to named constants (PR #21)
mrautela365 Jul 11, 2026
172c3b4
fix(review): harden Reddit URL/budget/id handling and tests (PR #21 r…
mrautela365 Jul 11, 2026
54fddfe
fix(review): surface read errors, validate account id, fix encoded-sl…
mrautela365 Jul 11, 2026
30be5c5
fix(review): validate account id charset, anchor post-id, make token-…
mrautela365 Jul 11, 2026
6ce20a5
fix(review): use canonical tlf slug, scope community warning, fix bud…
mrautela365 Jul 11, 2026
49981a7
fix(review): anchor post-path regex and validate ISO geo codes (PR #2…
mrautela365 Jul 11, 2026
5c403b2
fix(review): coalesce token refresh, trim ids, match escaped path (PR…
mrautela365 Jul 11, 2026
37e40b9
fix(review): re-check cache inside token single-flight; escape redd.i…
mrautela365 Jul 11, 2026
700c9db
fix(review): coalesce token refresh with stdlib only, drop x/sync (PR…
mrautela365 Jul 11, 2026
6665fd7
fix(review): share token-refresh result across waiters; leader honors…
mrautela365 Jul 11, 2026
435954e
fix(review): accept scheme-less Reddit post URLs (PR #21 round 11)
mrautela365 Jul 11, 2026
d73ae70
fix(review): ctx-cancel during /ads is fatal; make failure-shared tes…
mrautela365 Jul 11, 2026
d55f054
fix(review): surface partial state when ad-group creation fails after…
mrautela365 Jul 11, 2026
5ce79b7
feat(reddit): add 429 rate-limit retry with bounded backoff (PR #21)
mrautela365 Jul 13, 2026
51c5f08
fix(review): allocation-free truncate; race-safe test flags (PR #21)
mrautela365 Jul 13, 2026
4ae520c
fix(review): drain 429 body, bound token/Retry-After overflow, partia…
mrautela365 Jul 13, 2026
ace36cd
fix(review): allow at-cap Retry-After; fix past-start nudge wording (…
mrautela365 Jul 13, 2026
e0b5424
docs(okf): add recommended tags and timestamp frontmatter (PR: reddit…
mrautela365 Jul 13, 2026
bd9d643
fix(review): report raw Retry-After in over-cap abort; dated OKF log …
mrautela365 Jul 13, 2026
06b5e07
fix(review): reject empty EventName up front (PR #21)
mrautela365 Jul 13, 2026
ee03a08
fix(review): conversion pixel, video goal, retry-safe start (PR #21)
mrautela365 Jul 13, 2026
27e7dcc
fix(review): gate conversion_pixel_id on objective (PR #21)
mrautela365 Jul 13, 2026
c9b080a
fix(review): resolve subreddit names to IDs for targeting (PR #21)
mrautela365 Jul 13, 2026
675094a
fix(review): reject URL userinfo; align api-catalog on ID resolution
mrautela365 Jul 13, 2026
b492a09
fix(review): document partial-result contract; de-flake cancel test
mrautela365 Jul 13, 2026
8b4fb7a
fix(review): use /targeting/subreddits, resolve before campaign POST
mrautela365 Jul 13, 2026
443830c
fix(review): case-insensitive r/ strip, dedup, redact URL, de-flake
mrautela365 Jul 13, 2026
51e4e6b
fix(review): send subreddit names not IDs; require Project (PR #21)
mrautela365 Jul 14, 2026
4fc6ed7
fix(review): redact URLs in Steps and post-ID errors (PR #21)
mrautela365 Jul 14, 2026
cdc8434
fix(review): AdWarning field, redact validate errors, size start buff…
mrautela365 Jul 14, 2026
ba6f360
fix(review): allowlist UTM keys, redact all URL errors, horizon-nudge…
mrautela365 Jul 14, 2026
0a8b155
fix(review): gate community fallback on HTTP 400; status-only ad-fail…
mrautela365 Jul 14, 2026
36ad05f
fix(review): reject malformed query; never echo unparseable URL creds…
mrautela365 Jul 14, 2026
a411c1d
fix(review): partial on no campaign id; no OAuth body in errors; unco…
mrautela365 Jul 14, 2026
1a85ebd
fix(review): per-attempt timeout; UNCONFIRMED ambiguous creates; adgr…
mrautela365 Jul 14, 2026
b4c936b
fix(review): only UNCONFIRMED a campaign create that actually reached…
mrautela365 Jul 14, 2026
0cfae41
fix(review): ctx cancel during verify aborts, not UNCONFIRMED (PR #21)
mrautela365 Jul 14, 2026
770d893
fix(review): fix secretlint CI; validate EventName/Project length up …
mrautela365 Jul 14, 2026
0f5c218
fix(review): unify create-ambiguity classification; enforce composed …
mrautela365 Jul 14, 2026
c6dc0ea
fix(review): validate composed ad-group name before the campaign POST…
mrautela365 Jul 14, 2026
bc5f1ee
fix(review): preserve trace ctx on token refresh; precise budget/ad-f…
mrautela365 Jul 14, 2026
b72fffc
test(review): cover composed campaign-name limit + token-refresh canc…
mrautela365 Jul 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/api-catalog.md
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ pacingLabel: string — underspending | normal | constrained | overs

### Reddit Ads
- Token refresh with expiry buffer (tokens expire; must refresh before expiry)
- Subreddit targeting uses subreddit IDs, not names
- Subreddit targeting uses subreddit **names** (the `r/` prefix stripped), not `t5_` IDs — the Ads API `communities` field rejects `t5_` values as "invalid communities" (matches the reference TS implementation, which sends the stripped names directly); if any supplied name is invalid the ad-group create falls back to keyword/geo-only targeting with a warning rather than orphaning the PAUSED campaign
- Account must be whitelisted in runtime config

### X/Twitter Ads
Expand Down
1 change: 1 addition & 0 deletions docs/knowledge/code/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* [internal/infrastructure/config](internal-infrastructure-config.md) - Application configuration from CLI flags and env vars, including PG* composition into a PostgreSQL DSN.
* [internal/infrastructure/postgres](internal-infrastructure-postgres.md) - PostgreSQL pool (otelpgx), migrations, repositories, and Ready() for readiness probes.
* [internal/middleware](internal-middleware.md) - Package middleware provides HTTP middleware for the service.
* [internal/platform/reddit](internal-platform-reddit.md) - Reddit Ads API v3 client: OAuth2 token refresh and Campaign -> Ad Group -> Ad creation.
* [internal/platform/twitter](internal-platform-twitter.md) - X (Twitter) Ads v12 client: OAuth 1.0a signing and the campaign -> line_item -> promoted_tweet creation flow.
* [internal/service](internal-service.md) - Campaign service business logic, including Readyz (DB-backed readiness) and Livez (process-only liveness).
* [pkg/constants](pkg-constants.md) - Application-wide constants, including PG* and DATABASE_URL environment variable names.
Expand Down
45 changes: 45 additions & 0 deletions docs/knowledge/code/internal-platform-reddit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
type: "Go Package"
title: "internal/platform/reddit"
description: "Reddit Ads API v3 client: OAuth2 token refresh and Campaign -> Ad Group -> Ad creation."
resource: "internal/platform/reddit"
tags:
- platform-client
- reddit
- reddit-ads
- oauth2
- go-package
timestamp: "2026-07-13T23:55:00Z"
---

# internal/platform/reddit

Package reddit provides a Go client for the Reddit Ads API v3, porting the
upstream TypeScript `reddit-ads.service.ts` client. Credentials and account
configuration are injected via `NewClient`; the client never reads the process
environment.

Authentication uses OAuth 2.0 refresh-token exchange with a cached access token
and an expiry buffer (refresh shortly before the stated expiry). The token
endpoint and API base URL are overridable via functional options for `httptest`.

`CreateCampaign` drives the Campaign -> Ad Group -> Promoted Post (Ad) hierarchy,
creating everything PAUSED with a lifetime budget and objective-aware bid params.
It normalizes geo targets once (trimmed, uppercased) so the ad-group label,
targeting, and region derive from a single source of truth, and computes the
Comment thread
mrautela365 marked this conversation as resolved.
Comment thread
mrautela365 marked this conversation as resolved.
start time up front so a same-day (past midnight-UTC) start is nudged to
now+buffer before the campaign POST. Post URLs are validated by parsing the URL
authority (`reddit.com`/`redd.it` and subdomains only) to prevent host spoofing,
and UTM parameters are merged into the URL query while preserving any fragment.

Supplied subreddit names (`r/golang` or `golang`) are sent to the ad-group
`communities` targeting field as NAMES with the `r/` prefix stripped (and
case-insensitive duplicates removed) -- Reddit's Ads API targets communities by
name, not `t5_` ID, and rejects `t5_` values as "invalid communities". This
matches the reference TS implementation, which sends the stripped names
directly. If the ad-group create returns a 400 "invalid communities" the client
retries once WITHOUT communities (keyword/geo-only) and emits a
communities-skipped warning step, so an invalid subreddit never orphans the
PAUSED campaign.

See [internal/platform/reddit](../../../internal/platform/reddit).
8 changes: 8 additions & 0 deletions docs/knowledge/log.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Log

## 2026-07-13

**Update** — Added OKF-recommended `tags` and `timestamp` frontmatter to the
internal/platform/reddit concept doc (queryable fields per OKF v0.1 §4.1).

## 2026-07-10

**Update** — Addressed Copilot review on the X/Twitter Ads client (PR #19):
Expand All @@ -13,6 +18,9 @@ errors instead of masking them as not-found. Added the
**Update** — Mount connection routes in the HTTP server (LFXV2-2556): the
`cmd/campaign-service` concept now notes that every container-wired service
must also be mounted in `server.go`, or its routes 404 despite compiling.
**Creation** — Added the `internal/platform/reddit` concept doc for the new
Reddit Ads API v3 client (OAuth2 token refresh + Campaign -> Ad Group -> Ad
creation) and listed it in the code index.
Comment thread
Copilot marked this conversation as resolved.

**Update** — Dropped the Goa CLI path allowlist; twitter-api-secret FP is
fingerprint-only in `.gitleaksignore`. Clarified `.grype.yaml` rationale
Expand Down
Loading
Loading