Skip to content

Orientation

New here? This site is the internal explainer for PulseAd and its product. This page is the reading path; follow it in order and you’ll have a working mental model in about fifteen minutes.

Almost every confusion traces back to one of these:

  1. The names overlap. PulseAd (company/platform) ≠ Pulson (the product) ≠ PULSEAD (the main Snowflake schema) ≠ PSM (the legacy chat engine) ≠ AOP (the internal operator tool). Internal API paths still use /pulsy/...; that is an implementation namespace, not the product name. The name map untangles all of it — read it before anything else.
  2. Pulson reads freely, but changes never bypass approval. A customer request is routed to the operator owner; an internal/operator session must confirm the target and value before irreversible execution. The whole gate model is in Capabilities.
  1. Finish your profile. Sign in through the app’s welcome flow and complete the profile form. PUT /pulsy/me updates the profile; GET /pulsy/me is the useful read-back because it includes completion flags, memberships, pending invitations, and the current team/country scope.
  2. Create or select a team. GET /pulsy/teams lists your active memberships. If your account is allowed to create one, POST /pulsy/teams creates a team; POST /pulsy/teams/{team_uuid}/switch selects the current team and returns a refreshed Me response. A missing current team is a real blocked state, not an invitation to guess a team ID.
  3. Set the country together with the team. Use POST /pulsy/me/context when you need an explicit scope. It validates both membership and the team’s country catalog, normalizes the ISO-3166 alpha-2 code to uppercase, and returns 422 for an unknown membership, unavailable country, or team with no countries.
  4. Verify account visibility before asking for ad data. GET /pulsy/me/connections is intentionally fail-closed when either current team or current country is missing. The /pulsy/chats route uses the server-side current scope; a body team_id does not override it. Missing team context is rejected as TEAM_CONTEXT_REQUIRED (400), and an incomplete country scope is rejected as CURRENT_SCOPE_INCOMPLETE (409).
  5. Choose report language if needed. App display language and report-generation language are separate settings. POST /pulsy/me/report-locale persists ko or en; other values are rejected with 422.
  • GET /pulsy/teams/{team_uuid}/members returns a cursor-paginated page, not a bare list: use items, next_cursor, has_more, and total_count (default limit 20, maximum 100).
  • Team admins manage member roles and invitations. The server prevents a user from changing their own role, so a UI-level role control is not a substitute for admin access.
  • GET /pulsy/teams?include_hidden=true is for settings visibility; the default team list contains active memberships only.
  1. What PulseAd is — the company, the name map, the product surfaces, and the one behavioral rule. Start here.
  2. Capabilities — what Pulson can read vs. change, and the approval gates every write passes (including the pilot-only live path).
  3. Data model — what Pulson knows: the six analysts over Snowflake, and how current the data is.
  4. Data & coverage — which channels are covered, Amazon vs. the manual off-Amazon upload.
  5. Creative — the image and video generation tools.
  6. Reference + the glossary — the terms the rest of the docs assume.

How these docs work (so you can trust them)

Section titled “How these docs work (so you can trust them)”
  • They’re derived from the live source repos, pinned to the commit they were generated from, and re-syncable. The full procedure and writing rules are in AGENTS.md.
  • They document the truth candidly — including legacy surfaces and known gaps — because this is internal. If you find something asserted that the code doesn’t support, that’s a bug; flag it.
  • Found a gap the docs can’t answer? Open questions and “verify with ops” items live in _internal/needs-johnny.md — add to it rather than guessing.