# AdCenter level-1 credentials (TW-301) — consumer notes

This repo **mints** AdCenter level-1 keys the same way as `api.lucos.com` (`PUT /api/key` `{ adv_id, level: 1 }`). ChatGPT never sees the admin key or minted `x_api_key`. There is **no** ensure-key MCP tool.

Canonical ops artifacts live in **`api.lucos.com`** (see Girish / TW-301):

| Artifact | Path |
| --- | --- |
| Ops runbook | `api.lucos.com/docs/ADCENTER-LEVEL1-OPS.md` |
| Fixture registry (IDs) | `api.lucos.com/docs/fixtures/adcenter-staging.json` |
| Mint script (break-glass / migration) | `api.lucos.com/scripts/mint-adcenter-level1-key.sh` |
| Smoke script | `api.lucos.com/scripts/smoke-adcenter-level1.sh` |
| Credentials overview | `api.lucos.com/docs/BUSINESS-METRICS-CREDENTIALS.md` |

Mirror of staging fixture IDs (no secrets): [`fixtures/adcenter-staging.json`](./fixtures/adcenter-staging.json).

## Primary path (ChatGPT / MCP)

Empty `ADCENTER_LEVEL1_KEYS_STAGING={}` is OK. On live AdCenter GETs, MCP resolves keys via **cache → local mint → gateway ensure-key → optional env map**:

```text
ChatGPT → adcenter_* tool
  → resolve advertiser_id (args or MNGT lookup)
  → business-mcp ensureAdCenterLevel1Key (cache)
  → PUT https://apiad.admedia.com/api/key  { adv_id, level: 1 }
       X-API-KEY = ADCENTER_ADMIN_API_KEY (MCP .env; never ChatGPT)
  → MCP uses minted key as X-API-KEY on AdCenter GET
```

Gateway `POST /api/v1/tools/adcenter_ensure_level1_key` remains a fallback when local mint is off (`ADCENTER_ENSURE_KEY_ENABLED` unset) and a user Bearer is present.

`adcenter_ensure_level1_key` is a **gateway** route only. ChatGPT never sees it as an MCP tool and never receives `x_api_key`.

The admin mint key (`ADCENTER_ADMIN_API_KEY`) lives in MCP host `.env` (gitignored). Never commit it, never put it in ChatGPT envelopes.

Local mint needs `ADCENTER_ENSURE_KEY_ENABLED=true` and `ADCENTER_ADMIN_API_KEY` (staging and prod).

### Name-first e2e

1. Pass `advertiser` with the name (e.g. `lucos_synth_adv` / sireesha) on `adcenter_list_campaigns` — the tool resolves `adv_id` via `search_advertisers`. Optionally pass numeric `advertiser_id` if already known. Never ask the user for an id.
2. The level-1 key is ensured under the hood — do not mint keys or pass API keys in ChatGPT

Do **not** omit both `advertiser` and `advertiser_id` — there is no staging default to **19880**. Synthetic **19880** is only for `lucos_synth_adv` / campaign **90553**.

## Break-glass / migration (not the ChatGPT path)

Host env / vault map, if you must pin a key without calling ensure-key:

```bash
ADCENTER_LEVEL1_KEYS_STAGING={"19880":"<level-1-key>"}
```

Use this for migration or when the gateway ensure-key path is unavailable. Prefer empty `{}` when ensure-key is on.

## Local offline

`ADCENTER_FIXTURE=true` returns synthetic JSON (no network / no real key / no gateway mint). Dev only — never on hosted staging/prod.

## Errors

MCP / gateway fail closed. Typical codes:

| Code | Meaning |
| --- | --- |
| `ADCENTER_KEY_NOT_FOUND` | No env-map key and local mint / gateway ensure-key could not run |
| `ADCENTER_ADMIN_KEY_MISSING` | `ADCENTER_ENSURE_KEY_ENABLED` on but `ADCENTER_ADMIN_API_KEY` unset |
| `ADCENTER_ENSURE_FAILED` | Mint `PUT /api/key` failed, or gateway fallback failed |
| `ADCENTER_ENSURE_DISABLED` | Gateway fallback: `ADCENTER_ENSURE_KEY_ENABLED` off |

**Lucos mints level-1 keys in MCP (or via the gateway fallback), not ChatGPT.** Tool envelopes must not leak admin or level-1 keys.

## Locked decisions

- **Auth:** level-1 `X-API-KEY` only. Never UI level-2. Never Basic + `X-API-ADVID`. Never SSH keys.
- **Staging synthetic advertiser:** `19880` (`lucos_synth_adv`)
- **API base env:** `ADCENTER_API_BASE_URL_STAGING` (default `https://apiad.admedia.com/v1/`)
- **Hosts:**
  - REST API: `https://apiad.admedia.com/v1/` (Lucos connector target)
  - UI only: `https://stagingadcenter.admedia.com` (login UI — **not** an API base; `/v1/*` 404s there)
- **Prod mint:** same as staging — `ADCENTER_ENSURE_KEY_ENABLED` + `ADCENTER_ADMIN_API_KEY` (`ADCENTER_ENSURE_KEY_PROD` is ignored)
- **Mint owner:** MCP (and gateway fallback) using AdCenter admin level-10 (`ADCENTER_ADMIN_API_KEY` in host `.env`, never ChatGPT)

## Remaining (out of band — not git)

1. Set `ADCENTER_ENSURE_KEY_ENABLED=true` and inject `ADCENTER_ADMIN_API_KEY` in MCP host `.env` (never git)
2. Confirm staging smoke: empty MCP key map + `search_advertisers` `lucos_synth_adv` → `adcenter_list_campaigns`
3. Fill `campaign_id` / `creative_id` / report dates in fixture JSON after live reads
4. Confirm Lucos staging uses prod `apiad` + synthetic advertiser vs a separate API host

## In this repo

- `connectors/adcenter/credentials.ts` — `ensureAdCenterLevel1Key` (cache → local mint by advertiser_id → gateway → env map last). Mint is `PUT /api/key` `{ adv_id, level: 1 }`. Never returns admin/minted keys to ChatGPT.
- `connectors/adcenter/` — HTTP connector (**TW-302**): allowlisted **GET** only for display + SM + AdWords L1 reads. Never writes, uploads, link/assign, `creatives/all`, insights/demo, key mint, or POST-only report routes (`/sm/report/*`, `/adwords/reports/*`).
- Display tools in `tools.ts`; SM/AdWords tools in `tools-social.ts`. Pass `advertiser` (name) or numeric `advertiser_id`; the tool resolves `adv_id` internally. No staging default to **19880**. Responses are redacted (secret-like keys + pixel `tag` HTML stripped).
- `adcenter_get_campaign` accepts **`campaign_id` OR `name`** (not both). Name uses allowlisted `GET /campaigns?search=`; unique hit → `GET /campaigns/{id}`; multiple hits → `ambiguous` match list.
- Display reads: list/get campaigns & creatives, campaign creatives, sizes, summary/campaign/creative reports, targeting lists, keyword target ids, retargeting pixels, channels.
- Report formats (`adcenter_get_report` / `adcenter_get_campaign_report`): `daily`, `monthly`, `countries`, `os`, `browsers`, `sources`, `domains`, `keywords` (`destination_url` when present; otherwise bid-keyword trends, labeled). CTR may be derived in MCP (`ctr_source`). ChatGPT sequences (business-mcp only; no AdCenter route changes): [`ADCENTER-QUESTION-PLAYBOOK.md`](./ADCENTER-QUESTION-PLAYBOOK.md).
- SM reads: `adcenter_sm_*` (campaigns, adsets, ads, media, adaccount, pixels, pages, Instagram, targeting helpers, delivery estimate, api search, split test).
- AdWords reads: `adcenter_adwords_*` (campaigns, adgroups, keywords, ads, responsive/image ads, budget, adaccount, targeting, audience).
