# AdvisorPro AI Director — complete test handoff

Contract: `director-1-preview` · Deployment checked: **15 October 2026**.

This is the **one-file handoff**: implementation prompt, credentials, full test JSON, backend code, polling, review and troubleshooting. No companion file or npm package is required.

## Deployment status

**Available by default** for authenticated apps with existing AI and Integrations permissions. No app allowlist, special `video:director` grant or manual activation is required. AdvisorPro can reuse its existing key; no key was rotated. The separate Director API/worker and four proxy actions use the existing media infrastructure. Video, Voice and Crawl workers remain unchanged.

All apps still need an active service account, existing plan access, AI/model grants and credits. The shared Video service derives ownership from the calling app/workspace, not from the workspace that stores the platform connection. This does not expose that workspace's private configuration or media. The operator can explicitly disable Director in an incident; default availability does not bypass an explicit emergency stop.

Requests are monitored and metered under the calling app/workspace. AI charges use the existing Gateway ledger; media and polling use existing API-request billing. Prices, allowances, spending caps and Stripe enforcement settings are unchanged. No extra Director surcharge is introduced. Perform the non-generating probe below before the first submission.

Automated source, queue, authorization and cost-ceiling tests passed locally and on the VPS. On 15 October, an existing QA app completed a **76.8-second, eight-scene narrated 720p preview through Madnion**, using the AdvisorPro brief. Planning and rendering were verified in separate runs: low-reasoning GLM produced a valid plan, then the final confirmation reused it through `storyboard` after the QA app reached its daily model-call cap. Nine cents of AI charges settled across the correction tests, under the 50-cent ceiling; media/request usage is separate. No AdvisorPro key was needed or rotated. Temporary QA access was disabled afterward.

The MP4 fully decoded; all 18 captions and eight narration clips fit the timeline. Sampled final layouts were in frame. **This is a functional proof, not commercial approval:** no branded assets/music were supplied, narration measured approximately −28.4 LUFS (quiet), and the unedited proof contains an unsupported “Start free” button. The public storyboard example corrects that button. Full listening, transition/pacing review and a branded quality pass remain required. AdvisorPro's own service-key end-to-end check remains theirs to run using this guide.

## Copy/paste implementation prompt for AdvisorPro

> Add an internal AI Director test flow using the contract and examples in this file. Keep the current production video flow unchanged. Use AdvisorPro's existing Madnion service-account key from the backend secret store; never rotate it, expose it in a browser, or substitute an administrator/provider key.
>
> First check model access and the non-generating Director status probe. If activation is unavailable, stop and report the exact sanitized error; do not fake a successful test. Once access passes, persist the complete sample request and its idempotency key, submit ONE bounded smoke test, and poll from scheduled backend work. Store every job/asset under the authenticated internal AdvisorPro workspace and authorize each status, revision, cancellation and download.
>
> Inspect both job.error and result.error. Resolve previewJobId with the existing Video status action, download the proof into private workspace storage, then play and listen to it. Do not automatically increase the 50-cent AI ceiling, switch models, retry with a new paid job or publish the output. Existing media/request usage is separate from the AI ceiling.
>
> For the branded quality pass, use the exact approved logo, colors, licensed fonts/music and approved product media. Target advisors, consultants and fractional executives. Tell the story of valuable but invisible expertise becoming grounded content, a recognizable LinkedIn-style post, an intentional calendar and more relevant conversations. Explain WHY through visible actions and narration. Use approved footage for campaign, inbox and website scenes; never invent supported components, measured outcomes or actual publishing.
>
> Return UTC timings, Director and preview IDs, sanitized errors/request IDs, model charges, a private video copy and specific review findings for story, framing, transitions, narration and music. Distinguish successful orchestration from commercial approval.

## 1. Credentials and tenant boundary

Reuse the **existing Madnion app service-account key** in AdvisorPro's own backend secret store. No need to share it with Madnion, paste it into a prompt or create another key. The key needs `ai` and `integrations`; no separate Director permission or key rotation is needed.

- All calls originate on the server. Keep credentials and signed media URLs out of public logs.
- Do not substitute a builder/admin key, user JWT, ElevenLabs/OpenRouter key or the upstream Video-Gen key.
- Put the pilot behind an internal feature flag and limit it to authorized testers.
| Setting | Value |
| --- | --- |
| Madnion app ID | `app_HNTaxkT8g6pU1jJ8RFaOQIw1` |
| Madnion workspace | `pym4` / `0271c921-5a6a-4239-aa05-a4e71aadb433` |
| Proxy endpoint | `https://api.madnion.com/functions/v1/custom-proxy` |
| Proxy slug | `series-x-video-api` |
| Auth header | `x-service-account-key: <existing AdvisorPro key>` |
| Other header | `Content-Type: application/json` |
| Contract | `director-1-preview` |

Reuse any public `apikey` header already required by your established Madnion transport. Never use a Supabase service-role key. Do not send `X-Madnion-Media-*` or `X-Madnion-Director-*` scope headers; those are generated by Madnion.

**Internal tenant isolation:** these credentials identify the Madnion AdvisorPro app. They do not create a distinct Madnion app identity for every workspace/customer inside AdvisorPro. AdvisorPro must separately authorize its own users and store each job/asset against its internal workspace. Never let a browser select an arbitrary Madnion app ID, job ID or asset ID. Check internal workspace membership on every create/status/revise/cancel/download request.

## 2. Preflight — no inference or render submission

From the backend, call:

```http
GET https://api.madnion.com/functions/v1/ai-proxy/models?app_id=app_HNTaxkT8g6pU1jJ8RFaOQIw1
x-service-account-key: <existing AdvisorPro service-account key>
```

Select a granted OpenRouter **text** model with `supports_structured_output: true`. The sample uses `core/or-glm-5.3-flash`; the example is not itself a grant. Do not substitute a higher-priced model automatically. Omit `reviewModel` for the first smoke test unless Madnion has confirmed a suitable vision-review model and budget.

Use the existing proxy's `scene_catalog` action to confirm `interface-motion-1` is available. A healthy scene catalog or Voice response does **not** prove the app has the necessary AI permissions or credits.

Before submitting the first job, use this non-generating access probe:

```json
{
  "slug": "series-x-video-api",
  "appId": "app_HNTaxkT8g6pU1jJ8RFaOQIw1",
  "action": "director_status",
  "pathParams": { "jobId": "dir_00000000000000000000000000000000" }
}
```

Expected: HTTP `404` with `{"error":"not_found"}` from the Director, because this reserved test ID has no job. A generic “action not found” or proxy-not-found response is **not** the same result. Read the status and body. This probe starts no AI/media work, although a forwarded request may still count under existing API-request usage.

## 3. Submit one bounded smoke test

Send the **complete JSON below** as the POST body to the proxy endpoint. It is embedded in this file; no external JSON download is required. It is an **asset-free functional smoke test**, not a branded final deliverable. Its colors/font are placeholders to replace with AdvisorPro's verified brand settings for the quality pass.

The key is `data.idempotencyKey` in the body. Persist the full request and this key in your backend before sending. Keep both identical if recovering a lost submission response; never generate a new key automatically after a timeout. A new key starts a separately budgeted job.

### Complete request

```json
{
  "slug": "series-x-video-api",
  "appId": "app_HNTaxkT8g6pU1jJ8RFaOQIw1",
  "action": "director_create",
  "data": {
    "version": "director-1-preview",
    "idempotencyKey": "advisorpro-director-smoke-002",
    "model": "core/or-glm-5.3-flash",
    "reasoning": { "effort": "low" },
    "budget": {
      "maxCalls": 3,
      "maxOutputTokens": 16000,
      "maxAiCostCents": 50,
      "maxRepairs": 2
    },
    "brand": {
      "name": "AdvisorPro",
      "background": "#faf9fb",
      "foreground": "#171717",
      "accent": "#7652ff",
      "font": "sans"
    },
    "output": { "aspectRatio": "16:9", "resolution": "1080p", "fps": 30 },
    "voice": { "id": "am_michael", "rate": 1 },
    "assets": {},
    "fonts": {},
    "brief": {
      "audience": "Independent advisors, consultants and fractional executives",
      "objective": "A narrated product demonstration showing why making expertise visible can support more relevant business conversations; target 45–75 seconds, not a feature-list presentation.",
      "pain": "You deliver valuable expertise, but the right potential clients do not see it. Content and outreach feel disconnected from your real work.",
      "benefits": [
        "Start from professional expertise and source knowledge rather than generic messages.",
        "Turn one useful insight into content that a potential client can understand.",
        "Review what will be published and when.",
        "Use activity to decide what deserves attention next, without promising invented commercial results."
      ],
      "evidence": [
        {
          "id": "knowledge_demo",
          "kind": "illustrative",
          "claim": "The test illustrates website and portfolio context informing a draft; this is not a live upload or product recording.",
          "source": "Integration-test scenario. AdvisorPro must confirm product behaviour before commercial use."
        },
        {
          "id": "publishing_demo",
          "kind": "illustrative",
          "claim": "The same draft is reviewed as a LinkedIn-style post and placed in a calendar; no real post is published.",
          "source": "Illustrative UI test; no account or real publishing operation."
        }
      ],
      "cta": {
        "label": "Put your expertise to work.",
        "destination": "app.advisorpro.ai"
      }
    },
    "feedback": "Build a causal story: invisible expertise → website/portfolio source library → selected insight → editable draft → recognisable LinkedIn-style post → real-looking weekly calendar → outcome → CTA. Preserve the same subject, thumbnail and title through editor, social-post and calendar; use shared transitions only between adjacent supported components with that subject. Use short functional labels and narration explaining WHY, not repeating UI text. Do not invent meetings, conversion lifts or customer testimonials. Do not claim a real screen recording, actual upload or publication. The asset-free request is a functional smoke test, not final brand approval. Keep text readable, button labels aligned and interfaces fully in frame. Use only registered components. Omit campaign/inbox functionality if no approved media is supplied; do not invent component types. End with the exact brief CTA."
  }
}
```

Budget in the sample:

- Up to three model calls total, including up to two invalid-output repairs.
- Up to 16,000 combined reasoning and visible-output **tokens** per model call, with explicit low reasoning. GLM supports `low`, `high`, `max`; thinking cannot be disabled. Omitting `reasoning` on this GLM alias defaults to `low`, without raising your submitted output allowance. Other aliases require their own supported effort; provider rejection stops the job.
- A 50-cent managed Gateway AI ceiling. This is a safety ceiling, **not a price quote** and not the total cost of the video.
- Existing speech/render/proxy request prices and usage rules still apply, including polling. No new prices or Stripe settings are introduced by this pilot.

Expected submission: HTTP `202`, with a `jobId` beginning `dir_`, plus `state`, `stage`, `createdAt`, `expiresAt`, `revises` and `result`. Store `jobId`. Do not wait for the video inside a single browser request.

## 4. Poll and interpret the result correctly

POST the following every 5–10 seconds initially; back off with jitter if busy or rate-limited. Use backend scheduling so the job is not dependent on a tab staying open. `createdAt`/`expiresAt` on Director jobs are Unix seconds.

```json
{
  "slug": "series-x-video-api",
  "appId": "app_HNTaxkT8g6pU1jJ8RFaOQIw1",
  "action": "director_status",
  "pathParams": { "jobId": "dir_REPLACE_WITH_RETURNED_32_HEX_ID" }
}
```

Use the **exact returned ID**, not the placeholder above.

| State | Handling |
| --- | --- |
| `queued` / `running` | Keep the job visible and poll; do not resubmit |
| `needs_review` | Stop polling Director. Inspect both `job.error` and `job.result.error` before showing success |
| `failed` / `cancelled` / `expired` | Stop; display a safe reason; no automatic new paid attempt |

Successful orchestration has no error and returns:

- `result.storyboard`: beats, narration, visible action and consequence.
- `result.video`: editable composition for final rendering after approval.
- `result.previewJobId`: a **Video job**, beginning `job_`, not a Director ID.
- `result.duration`: actual narration-led duration. A 45–75 second request is creative guidance, not a guaranteed duration bound.
- `result.ai`: confirmed charges, request IDs and whether cost accounting is complete.
- `result.review`: commercial approval remains `false`; `audio: "not_listened"` means no human listening has happened.

`needs_review` alone is **not success**. It can contain a failed step or an unknown result after a worker interruption. Do not treat a JSON response as proof of a completed video.

## 5. Get the proof, then review it

With the returned `result.previewJobId`, call the **existing** Video `status` action:

```json
{
  "slug": "series-x-video-api",
  "appId": "app_HNTaxkT8g6pU1jJ8RFaOQIw1",
  "action": "status",
  "pathParams": { "jobId": "job_REPLACE_WITH_PREVIEW_32_HEX_ID" }
}
```

When Video `state` is `done`, its response supplies `downloadUrl`. Download the bytes server-side, verify a successful video response and store them in AdvisorPro's own authorized workspace storage. The proof uses 720p; `result.video` retains the requested delivery resolution. A signed link is temporary and must not be used as permanent storage.

Only fetch a URL obtained from this authenticated status response. Require HTTPS and the exact `video-api.series-x.ai` host with the expected `/media/job_...` path; reject credentials in URLs, redirects and browser-supplied download targets. Use a timeout and a bounded download size.

Require actual playback and listening. Check:

1. Clear audience pain → visible workflow → useful consequence → CTA; narration explains why, not just feature names.
2. Same source/draft/thumbnail continues across the editor, recognizable LinkedIn-style post and calendar. No generic floating-card substitute.
3. No clipped UI, logos, controls, text, calendar slots or captions; no inaccurate button alignment.
4. Narration begins with its scene; no awkward pauses, masking music, pops or clipped peaks.
5. Sources and claims are accurate. Illustrative analytics stay marked as illustrative; no invented verified outcomes.
6. Exact brand assets and authorized CTA. No confidential names, conversations or customer data unless specifically approved.

Director uses registered components; it is **not** autonomous product recording, unrestricted Blender/code execution, guaranteed commercial art direction, acoustic transcription, or automatic publication. The polished hand-authored AdvisorPro film remains a visual benchmark, not a guarantee that every API composition matches it.

## 6. Branded quality pass: use owned assets

After the smoke flow succeeds, prepare a reviewed request with:

- An exact approved logo as a PNG/JPEG data URI in `data.assets.logo`. Do not pass SVG or a remote logo URL directly. Preserve the logo's proportions.
- Current product captures/footage with explicit permission and enough duration. Upload footage with `asset_init`, then PUT raw bytes to the returned signed `uploadUrl`; confirm `asset_status` is `ready` using the **same AdvisorPro app credential**.
- Custom font: `asset_init` with `{ "kind":"font", "bytes": ACTUAL_BYTE_COUNT }`, upload it, then use `data.fonts.brandfont = { "assetId": RETURNED_ID }` and `data.brand.font = "brandfont"`. Use licensed supported font files; validate before rendering.
- Music: upload a licensed audio bed as `kind: "audio"`. Use `data.assets.bed = { "assetId": RETURNED_ID }` and `data.music = { "asset":"bed", "volume":0.16, "duckVolume":0.035 }`. Its measured duration must cover the complete narration-led timeline; there is no implicit looping.

`asset_init` uses the same proxy envelope, with its upload metadata in `data`. `asset_status` uses `pathParams: { "assetId": "asset_..." }`. Uploaded references are not shared with another Madnion app. AdvisorPro must also enforce its own internal workspace ownership before including an asset in a request.

Use descriptive aliases and brief evidence to explain media. The planner receives aliases and kinds, not an independent understanding or verification of all asset contents. Supply or revise `storyboard` explicitly if a particular shot/media choice must be guaranteed. Do not invent additional request keys: the schema rejects unknown fields.

Supported first-release components: `hero`, `library`, `editor`, `social-post`, `calendar`, `analytics`, `showcase`, `cta`. For campaigns/inbox/real website shots, use approved footage/images in `showcase`; no dedicated inbox or campaign component exists in this release. Interface mockups must not be presented as real recordings. `showcase` preserves the supplied media; review small-text readability even when nothing is cropped.

The Director generates per-scene American English narration with the sample voice `am_michael`. Caption timing is estimated by phrase, not word-level acoustic alignment. Music ducking follows narration windows, not audio analysis. Music/SFX are not automatically sourced; do not claim they are included in the asset-free test.

## 7. Revise, cancel and final render

### Reproducible storyboard option

An optional [validated AdvisorPro storyboard example](https://www.madnion.com/advisorpro-director-storyboard-example.json) is available for testing the narration/rendering path without another planning call. Download and review that JSON, then set `requestEnvelope.data.storyboard` to the parsed object and persist an explicitly approved new `idempotencyKey`. Keep the sample brief, evidence IDs and CTA consistent with the example. This is optional; the one-file brief-to-plan request above remains self-contained.

The example was generated by the managed GLM planner with low reasoning, then passed the actual component validator; its CTA button was changed to neutral “Get started” because the brief does not establish a free offer. A valid supplied storyboard skips planning inference; narration, rendering and proxy requests retain their existing billing. A malformed storyboard can enter the configured repair path, so set `maxRepairs: 0` when testing only a caller-supplied plan. It is illustrative and asset-free, not a branded commercial reference or a promise that every future model output will validate.

**Revise:** `director_revise` with `pathParams.jobId` set to the original Director ID. Supply the **complete** request data, a new `idempotencyKey` and explicit `feedback`; optionally include a corrected `storyboard`. This starts a new budgeted job and preserves the original. Persist the returned `revises` relationship. Reusing the original key is rejected.

**Cancel:** `director_cancel` with the Director ID and `data: {}`. It stops future orchestration. Already submitted speech/render jobs can finish and remain chargeable; cancellation does not refund work already performed.

**Final:** only after a person approves the proof, use existing `render` with `data: { "video": APPROVED_RESULT_VIDEO, "idempotencyKey": NEW_FINAL_RENDER_KEY }`. Poll existing `status`, then download. This is a separately submitted render; there is no `director_approve` or automatic final publication in this release.

## 8. Minimal backend transport

This is dependency-free JavaScript for a trusted server with `fetch`. Load secrets using your platform's secret manager, and authorize the AdvisorPro user/workspace before calling it. `env` below means your already-loaded private configuration; do not log it.

```javascript
const BASE = 'https://api.madnion.com/functions/v1';
const APP_ID = 'app_HNTaxkT8g6pU1jJ8RFaOQIw1';
const HEADERS = {
  'Content-Type': 'application/json',
  'x-service-account-key': env.MADNION_SERVICE_ACCOUNT_KEY,
  ...(env.MADNION_ANON_KEY ? { apikey: env.MADNION_ANON_KEY } : {})
};

async function proxy(action, data, pathParams) {
  const response = await fetch(`${BASE}/custom-proxy`, {
    method: 'POST',
    headers: HEADERS,
    redirect: 'error',
    signal: AbortSignal.timeout(30000),
    body: JSON.stringify({
      slug: 'series-x-video-api', appId: APP_ID, action,
      ...(data === undefined ? {} : { data }),
      ...(pathParams ? { pathParams } : {})
    })
  });
  const text = await response.text();
  let body;
  try { body = JSON.parse(text); } catch { body = null; }
  if (!response.ok) {
    const code = typeof body?.error === 'string'
      ? body.error : body?.error?.code || `HTTP_${response.status}`;
    // Keep any detailed server response in restricted diagnostics, not the UI.
    throw Object.assign(new Error(code), { status: response.status });
  }
  if (!body) throw new Error('Expected JSON response');
  return body; // custom-proxy returns upstream JSON directly
}

// Only after the access probe passes; requestEnvelope is the complete JSON above.
// Save requestEnvelope.data and its idempotency key BEFORE this request.
const submitted = await proxy('director_create', requestEnvelope.data);
// Persist submitted.jobId against the authenticated AdvisorPro workspace.
// Subsequent calls come from scheduled backend work, not one long HTTP request:
// await proxy('director_status', undefined, { jobId: submitted.jobId });
// await proxy('status', undefined, { jobId: completed.result.previewJobId });
```

The dependency-free transport above is sufficient; no SDK package or companion file is required.

## 9. Errors, retention and evidence to return

| Result | What to do |
| --- | --- |
| Action missing / generic `404` | Madnion registration/deployment may be pending; stop and send sanitized details |
| `503 director_not_enabled` | Madnion has temporarily disabled Director; do not retry in a loop |
| `403 director_scopes_required` | Check existing `ai` and `integrations` scopes; no special Director grant is needed |
| `401` / other `403` | Verify the correct existing app credential, enabled state, grants and connection; never switch to admin auth |
| `402` | AI credit/ceiling issue; request approval before changing budget/model |
| `409` | Idempotency/state conflict; reconcile the existing job instead of resubmitting |
| `429` | Bounded queue/rate limit; back off with jitter and preserve request identity |
| Timeout / uncertain write / `5xx` | Save diagnostics, reconcile the original request/job; never blindly create a new paid job |
| `needs_review` plus error | Stop and review the failed/uncertain step; not a completed proof |
| `model_output_limit` | The combined thinking/output allowance was exhausted. Stops after that call; no identical repair or automatic increase. Review usage, then explicitly authorize a new revision |
| `model_empty_output` / `model_incomplete_response` | No usable completed answer; stops without paid repair. Inspect the recorded finish reason |
| `model_repair_repeated` | The same JSON/schema failure recurred; stop further repairs |
| `invalid_scene_schema` | Inspect `errorDetails` for the component/text/timing constraint. The schema is supplied in the prompt; local validation is authoritative |

`budget.maxOutputTokens` accepts 512–32,768 and must not exceed the granted model limit. `reasoning: {effort: ...}` controls planning; optional `reviewReasoning` separately controls `reviewModel`. Reasoning contents are not stored or returned; token counts and `finishReason` are retained in `result.ai.requests`. Excluding reasoning text does not remove its token cost. The 50-cent ceiling is shared across all planning/repair/review calls, not multiplied per call. Uncertain paid calls stop with `costComplete:false` for reconciliation. Never automatically retry with a new request key.

Director payloads and media have seven-day retention, with separate clocks from their own creation times. Refreshable media download links last about 15 minutes. Fetching a fresh link does not extend storage retention. Download promptly into your own durable storage. There are no Director completion webhooks in this release; use polling. AI charge totals are not VPS compute-cost measurements.

Return to Madnion: exact UTC start/end times, app ID, Director/preview job IDs, states/errors, HTTP status and available response request IDs, model alias, AI ledger, total wall time, a privately accessible downloaded proof, and a short visual/listening review. Do not send credentials, signed URLs in public tickets, or real customer data.

Further reference: [Video API](https://www.madnion.com/docs/video-api), [AI Gateway](https://www.madnion.com/docs/ai-api), [Custom proxies](https://www.madnion.com/docs/custom-proxies). The request and transport needed for this pilot are fully included above.
