Article
What if creating a Marketo program was as simple as sending a message in Teams? Here is what the Marketo API can and cannot do, a governed architecture with validation and human approval, and where to draw the line between AI autonomy and human control.
35 min read · Applies to: Marketo
Written by
What if creating a Marketo program was as simple as sending a message in Microsoft Teams?
Imagine typing:
“Create a webinar program for Germany, using our approved template, naming convention and campaign requirements.”
A few seconds later, an AI assistant replies with a summary of what it understood: the workspace, the template, the program name it intends to use, the launch date and the tokens it will fill in. You confirm. The program appears in the right folder in Adobe Marketo Engage, built from the approved template, named correctly, with its campaigns switched off and a checklist of what still needs a human.
No navigating through multiple folders. No manually recreating the same program structure for the fortieth time. No forgotten naming conventions.
Sounds interesting, right? But here is the bigger question: just because AI can create a marketing program, should we let it?
This article answers that question in two ways. First, it sets out what is technically possible today, checked against Adobe’s current developer documentation, including where the Marketo API stops. Second, it walks through a governed design you could build, step by step: a Teams assistant in front, deterministic rules in the middle, Marketo’s REST API at the back, and a human approval at every point where a mistake would be expensive.
By the end you will be able to:
Important: this is a proposed solution architecture based on documented integration capabilities. It is not a claim that this exact workflow has been tested or deployed in production. The reference code in this article was tested against a mocked Marketo API, not a live instance. Example names, IDs and conversations are illustrative.
Yes, an AI assistant can safely help create a Marketo program, as long as the AI only interprets the request and never has the final say. Marketo’s REST API can create programs, clone them from a template, set program tokens and tags, and read back what was created. A safe design lets the AI turn a chat message into structured requirements, then hands those to a deterministic governance layer that checks naming, permissions, workspace, channel and template before anything is created. The program is cloned from an approved template, verified against a checklist, and left with every campaign inactive. Activation, audience selection, consent-sensitive steps and anything the API cannot do (such as editing smart list filters and flow steps through the REST API) stay with a human, and every action is written to an audit log.
When marketers say “create a program”, they usually mean much more than one object. In Adobe Marketo Engage, a program is a container that holds everything a campaign needs. Before you automate any of it, it helps to name the parts.
| Part of a program | What it is | Typical setup decision |
|---|---|---|
| Program type | Default, Event, Event with Webinar, Engagement or Email (Adobe, Programs API) | Webinars are usually “Event with Webinar” or “Event”, depending on how you connect the webinar provider |
| Channel | Defines the program’s member statuses (for example Registered, Attended, No Show) | Must match the program type and your reporting model |
| Folder and workspace | Where the program lives in Marketing Activities, and which workspace (and therefore which team) owns it | Usually country or region, then year, then channel |
| Name | The program’s identifier in reports, smart lists and integrations | Governed by a naming convention, for example 2026-11_DE_WBN_AI-in-Manufacturing |
| Tags and period costs | Reporting dimensions and budget entries | Often mandatory for attribution and budget reporting |
| My tokens | Program-level variables such as {{my.WebinarTitle}} or {{my.EventDate}} |
Filled per program so emails and pages update automatically |
| Local assets | Emails, landing pages, forms and lists inside the program | Usually inherited from a template, then edited and approved |
| Smart campaigns | The automation: who qualifies (smart list) and what happens (flow) | Registration, confirmation, reminders, follow-up |
| Integrations | Webinar provider event, CRM campaign sync | Configured per program, often in the UI |
A mature Marketo team rarely builds this from scratch. They keep template programs that already contain the right structure, tokens, emails and campaigns, and they clone them. That habit is the key to making AI assistance safe: the safest thing an assistant can do is reproduce a reviewed template accurately, not invent a new program.
Practitioner tip: if your team does not yet have a template library, build that first. An AI assistant on top of inconsistent manual builds will only reproduce the inconsistency faster. The Marketo Program Structure Primer is a good starting point.
The table below summarizes what Adobe’s developer documentation says about the operations this workflow needs. Endpoints are relative to your instance’s REST endpoint (found under Admin > Integration > Web Services).
| Task | Supported by the REST API? | Documented endpoint and notes |
|---|---|---|
| Create a program | Yes | POST /rest/asset/v1/programs.json. Requires name, folder, type and channel; description, costs and tags are optional. Some subscriptions require tags for specific program types (Adobe, Programs API) |
| Clone a program | Yes, with limits | POST /rest/asset/v1/program/{id}/clone.json. Needs a new name (globally unique, up to 255 characters) and a parent folder in the same workspace as the source. Programs containing push notifications, in-app messages, reports or social assets cannot be cloned (Adobe, Programs API) |
| Change name, description, tags, costs | Yes | POST /rest/asset/v1/program/{id}.json. Type and channel can only be set at creation (Adobe, Programs API) |
| Look up a program, folder, channel or tag type | Yes | program/byName.json, folder/byName.json, channels.json, tagTypes.json (Adobe, Endpoint reference) |
| Set program tokens | Yes | POST /rest/asset/v1/folder/{id}/tokens.json with folderType=Program. Types: date, number, rich text, score, SFDC campaign, text. Token names up to 50 characters (Adobe, Tokens API) |
| Create, clone, activate or deactivate a smart campaign | Yes | Smart Campaigns API. Activation requires at least one trigger and one flow step, all error-free (Adobe, Smart Campaigns API) |
| Read smart list rules | Yes | Smart Lists API with includeRules=true (Adobe, Smart Lists API) |
| Create or edit smart list filters and triggers | Not documented in the REST API | The Smart Lists API documents query, clone and delete only (Adobe, Smart Lists API) |
| Create or edit flow steps | Not documented in the REST API | Community members have asked Adobe for these endpoints (Adobe Community, 2026) |
| Approve a program | Email programs only | approve.json and unapprove.json on Email programs, after dates, an approved email and a smart list are set up in the UI (Adobe, Programs API) |
| Link a webinar provider event | Not covered by the documented Programs API | Plan this as a manual step in the program’s event settings |
| Clone a program into another workspace | No | The target folder must be in the same workspace as the source program (Adobe, Programs API) |
These platform limits are small details that become big design constraints once an assistant starts making calls on behalf of many marketers (Adobe, REST API):
Authorization: Bearer header. Adobe removed support for the access_token query parameter, with a stated date of 30 June 2025, so older code that passes the token in the URL needs updating."success": false and an error code, so an integration must read the body, not just the HTTP status.In 2026 Adobe documented a hosted Marketo Engage Model Context Protocol (MCP) server that connects AI assistants to Marketo, listed with a beta badge on Experience League. Adobe describes it as offering “more than 100 operations”, including program creation and cloning, smart campaign creation with smart list filters and flow steps, and activation, and lists Copilot Studio among the supported clients (Adobe, Marketo MCP server). According to the same page, it uses API credentials from a LaunchPoint custom service, inherits your instance’s API rate limits, and respects the API user’s permissions.
That changes what is possible. It does not change what is wise. An MCP server gives an AI model a large, general toolset, including actions you may never want it to take unattended. Community discussion also notes that the beta is still changing (Adobe Community, 2026). The design in this article therefore keeps a narrow, custom integration layer between the assistant and Marketo, and treats the MCP server as an alternative you can evaluate with the same guardrails (see Alternatives).
Version note: API capabilities change. Everything above was checked against Adobe’s documentation on 18 October 2026. Re-check the Programs, Smart Campaigns and MCP pages before you build.
The architecture follows the path from the original idea:
Microsoft Teams → Copilot Studio → Power Automate → Secure Integration Layer → Marketo REST API
Each component has a narrow responsibility.
| Layer | Responsibility | What it must not do |
|---|---|---|
| Microsoft Teams | Where the marketer types the request and receives the summary, the approval request and the result | Hold any Marketo logic |
| Copilot Studio agent | Understands the request, asks for missing details, extracts structured fields, shows the plan back for confirmation | Decide whether a request is valid, generate the final name, or call Marketo directly |
| Power Automate (agent flow) | Deterministic orchestration: calls the integration layer’s “plan” endpoint, runs the approval, then calls “execute” | Contain business rules that should live in one place |
| Secure integration layer | Governance rules, Marketo credentials, allowlisted API calls, verification, audit log | Expose generic “call any Marketo endpoint” functionality |
| Marketo Engage | System of record for programs, enforced by the API user’s role and workspace permissions | Be the only line of defense |
It is tempting to let Power Automate call Marketo directly, or to hand the AI agent a Marketo connector. A separate integration layer (for example an Azure Function or a small container service behind Azure API Management) earns its place for five reasons:
plan and execute. There is no “activate”, no “delete”, no “update lead”. OWASP lists this kind of control, minimizing an agent’s functions and permissions, as a core mitigation for the risk it calls Excessive Agency (OWASP, LLM06:2025).Design principle: AI can interpret the request, but business rules should determine whether it is valid. Authorization should be enforced in the downstream systems, not by trusting the model to behave (OWASP, LLM06:2025).
The question is not “AI or human”. It is which decisions belong to which party. A practical way to decide is to ask two questions about every action: how easy is it to undo, and who could it affect?
| Action | Reversible? | Who it can affect | Recommended control |
|---|---|---|---|
| Interpret the request, ask for missing details | Fully | Nobody | AI, no approval needed |
| Generate a plan and preview the program name | Fully | Nobody | AI plus deterministic rules |
| Clone a template into an approved folder, campaigns inactive | Yes, the program can be removed | Internal team only | Requester confirms; optional approver for some workspaces |
| Set program tokens, tags, description | Yes | Internal team only | Allowed after the plan is approved |
| Approve emails and landing pages | Yes, but content can then be sent | Customers, if used | Human review in Marketo |
| Activate trigger campaigns or schedule batch campaigns | Not once people have been processed | Customers and prospects | Human only, with recorded approval |
| Choose or change the audience (smart list filters) | Not once sent | Customers and prospects | Human only |
| Anything touching consent, subscriptions or legal basis | Often not | Customers and prospects; regulatory exposure | Human only, with privacy or legal review where required |
| Delete programs or assets | Restoring is manual at best | Reporting history | Human only |
My own line is simple: AI can prepare, people activate. Campaign activation, audience eligibility, consent-sensitive operations and high-impact changes stay subject to explicit authorization and appropriate review. Every action, by the AI or by a person, is traceable in an audit log.
This is also the line the workflow enforces technically. The integration layer has no activation operation at all, so no prompt, however clever, can make the assistant switch a campaign on.
The nine steps below turn the five stages into an implementation. Steps 1 to 4 are about Marketo and the integration layer, Steps 5 to 7 are about the Microsoft side, and Steps 8 and 9 are about verification, audit and rollout.
About navigation labels: Marketo menu paths below are taken from Adobe’s documentation where cited. Microsoft is actively changing the Copilot Studio interface (in 2026 it introduced a new agent experience and “workflows” alongside agent flows), so Microsoft steps are described by function, with labels only where Microsoft’s current documentation confirms them. Always test in a sandbox or development environment first.
Objective: Give the automation a safe, predictable place to work: approved templates, a target folder structure, a naming convention, and an API user with the minimum permissions it needs.
Prerequisites: Marketo admin access; agreement from marketing operations on templates and naming; ideally a Marketo sandbox instance for development.
Navigation: Marketing Activities (templates and folders); Admin > Users & Roles (role and API-only user); Admin > Integration > LaunchPoint (custom service); Admin > Integration > Web Services (REST endpoint and identity URL) (Adobe, Authentication).
Detailed instructions:
TPL_Webinar_v3, and keep them in a dedicated templates folder that only marketing operations can edit.{{my.WebinarTitle}}, {{my.EventDate}} and {{my.Language}} in emails and landing pages, so a new program only needs token values.EMEA > Germany > 2026 > Webinars. Note the folder IDs (the ID appears in the URL when you select the folder, and can also be retrieved with GET /rest/asset/v1/folder/byName.json).YYYY-MM_CC_TYPE_Short-Name, for example 2026-11_DE_WBN_AI-in-Manufacturing.Example configuration (illustrative):
| Setting | Example value |
|---|---|
| Template program | TPL_Webinar_v3 (ID 1001), EMEA workspace |
| Target folder | EMEA > Germany > 2026 > Webinars (ID 2050) |
| Channel | Webinar |
| Required tokens | my.WebinarTitle, my.EventDate, my.Language |
| Required smart campaigns | 01 Registration, 02 Confirmation, 03 Reminder |
| API role | Asset read and write only, EMEA workspace only |
Expected result: a template you trust, a folder to clone into, and credentials stored in a vault.
How to verify: request an access token from the identity endpoint and call GET /rest/asset/v1/program/byName.json?name=TPL_Webinar_v3. You should receive the template’s details. Then try reading a program in a workspace the role does not cover: it should not be returned.
Common mistakes: reusing an admin user’s credentials for the integration; giving the API role every permission “to be safe”; keeping templates in a folder anyone can edit.
Troubleshooting: a 603 error means the role lacks permission for that API. Empty results for assets you know exist usually mean the API user is not assigned to that workspace.
Visual requirement: a screenshot from your own instance of the templates folder and the role’s Access API permissions (do not use mock screenshots).
Objective: Turn a free-text request into a fixed, validated structure. This is the contract between the AI and everything after it.
Prerequisites: the template registry from Step 1.
Navigation: your integration layer’s code repository.
Detailed instructions:
Example configuration:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MarketoProgramRequest",
"type": "object",
"required": ["country", "program_type", "short_name", "launch_date", "tokens"],
"additionalProperties": false,
"properties": {
"country": { "type": "string", "pattern": "^[A-Z]{2}$", "description": "ISO 3166-1 alpha-2 code, e.g. DE" },
"program_type": { "type": "string", "enum": ["webinar"] },
"short_name": { "type": "string", "minLength": 3, "maxLength": 60 },
"launch_date": { "type": "string", "format": "date" },
"tokens": {
"type": "object",
"additionalProperties": false,
"properties": {
"my.WebinarTitle": { "type": "string", "maxLength": 200 },
"my.EventDate": { "type": "string", "format": "date" },
"my.Language": { "type": "string", "enum": ["de", "en", "fr"] }
}
}
}
}
Expected result: a schema that rejects anything outside the supported scope (for example, a “nurture” request when only webinars are enabled).
How to verify: run the schema against a handful of test requests, including incomplete and out-of-scope ones, and confirm they fail with clear messages.
Common mistakes: letting the AI supply the requester’s identity; adding a free-text “notes” field that later gets copied into Marketo unchecked.
Troubleshooting: if the agent keeps producing values the schema rejects, show it the allowed values (enums) in its instructions, and ask it to confirm uncertain fields with the user instead of guessing.
Visual requirement: a simple diagram of “AI-extracted fields” versus “platform-supplied fields” versus “rule-derived fields”.
Objective: Make every validity decision deterministic, testable and owned by marketing operations.
Prerequisites: the naming convention, template registry and workspace permission model.
Navigation: integration layer code (the validate_request function in the appendix).
Detailed instructions:
GET /rest/asset/v1/program/byName.json to make sure the name is not already taken.GET /rest/asset/v1/channels.json) and matches the program type, because channel cannot be changed after creation.Example configuration (excerpt from the reference code):
def validate_request(req: ProgramRequest, today: date | None = None) -> Plan:
today = today or date.today()
issues = []
template = TEMPLATE_REGISTRY.get((req.country.upper(), req.program_type.lower()))
if not template:
issues.append(f"No approved template for country '{req.country}' and type '{req.program_type}'.")
return Plan(valid=False, issues=issues)
allowed = WORKSPACE_PERMISSIONS.get(req.requester.lower(), set())
if template["workspace"] not in allowed:
issues.append(f"{req.requester} is not permitted to create programs in workspace '{template['workspace']}'.")
launch = date.fromisoformat(req.launch_date)
if (launch - today).days < MIN_LEAD_TIME_DAYS:
issues.append(f"Launch date {launch} is less than {MIN_LEAD_TIME_DAYS} days away.")
name = build_program_name(req, template["type_code"]) # the AI never writes the final name
if not NAME_PATTERN.match(name):
issues.append(f"Generated name '{name}' does not match the naming convention.")
...
Expected result: for the same input, the rules always give the same answer, with reasons a marketer can understand.
How to verify: write unit tests for each rule, including the failure cases. The reference code ships with offline tests that check, among other things, that a requester without workspace access, a launch date inside the lead time, missing tokens and unexpected tokens are all rejected.
Common mistakes: asking the AI whether a name “looks compliant”; spreading rules across flows, prompts and code so nobody knows which one wins.
Troubleshooting: if marketers often fail a rule, the rule may be wrong or badly explained. Review the audit log for the most common rejection reasons each month.
Visual requirement: a decision tree showing the checks in order, with a stop at each failure.
Objective: Create the program from the approved template, set its tokens, prove the result is correct, and record everything.
Prerequisites: Steps 1 to 3; a hosting option (for example Azure Functions or Azure Container Apps) with managed identity access to your secrets vault; Python 3.10+ and the requests library for the reference code.
Navigation: integration layer code and hosting configuration.
Detailed instructions:
<identity URL>/oauth/token with the client credentials grant, cache it, refresh it before it expires (tokens last 3,600 seconds), and send it as Authorization: Bearer <token> (Adobe, Authentication).success and errors in every response. Re-authenticate on 601 or 602. Back off and retry on 606 (rate limit), 615 (concurrency), 604 and 608. Fail clearly on everything else (Adobe, Error codes).POST /rest/asset/v1/program/{templateId}/clone.json, sending the generated name, the target folder as {"id": 2050, "type": "Folder"} and a description that records the requester, approval reference and correlation ID.POST /rest/asset/v1/folder/{programId}/tokens.json and folderType=Program, one call per token, using the right token type (for example date for dates).prepared or needs_attention), program ID and name, each check with pass or fail, and the list of manual steps.Example configuration (excerpt):
def execute_plan(req, client, approved_plan_id, approval_ref):
correlation_id = str(uuid.uuid4())
plan = plan_program(req, client) # fresh validation, including name uniqueness
if not plan.valid:
return {"status": "rejected", "issues": plan.issues, "correlation_id": correlation_id}
if plan.plan_id != approved_plan_id: # the approval covers this exact plan only
return {"status": "rejected", "issues": ["Request changed after approval. Submit it again."]}
tpl = plan.template
created = client.clone_program(tpl["template_program_id"], plan.program_name, tpl["target_folder_id"],
f"Prepared by AI assistant for {req.requester}. Approval ref {approval_ref}.")[0]
for token_name, value in req.tokens.items():
client.set_program_token(created["id"], token_name, value, "date" if token_name == "my.EventDate" else "text")
verification = verify_program(client, created["id"], plan, req)
audit("program_verified", correlation_id=correlation_id, program_id=created["id"], passed=verification["passed"])
return {"status": "prepared" if verification["passed"] else "needs_attention", "program_id": created["id"],
"program_name": plan.program_name, "verification": verification, "correlation_id": correlation_id}
The complete, commented file, including the Marketo client with token caching and retries, is in the appendix.
Expected result: a cloned program in the target folder, tokens set, and a verification report.
How to verify: run the offline tests first. Then, in a sandbox, run a dry run (plan_program) and a real execution against a test template, and compare the program in the Marketo UI with the verification report.
Common mistakes: passing the access token as a query parameter (no longer supported); ignoring "success": false because the HTTP status was 200; not handling the shared daily quota.
Troubleshooting: see the troubleshooting guide for error codes 601, 602, 603, 606, 607, 615, 709, 710 and 711.
Visual requirement: a sequence diagram of plan, approve, execute, verify, audit.
Objective: Give the agent two safe tools, “plan a program” and “execute an approved plan”, through a deterministic flow.
Prerequisites: Power Automate or Copilot Studio access in an environment your admins have approved; an authentication method for the integration layer (for example Microsoft Entra ID OAuth through a custom connector, or Azure API Management); confirmation from your Microsoft licensing admin about which connectors your licenses cover (HTTP actions and custom connectors can require premium licensing).
Navigation: Copilot Studio agent flows, or Power Automate cloud flows. Microsoft documents that agent flows using the When an agent calls the flow trigger can be added to agents as tools, and that agent flow runs consume Copilot Studio capacity (Microsoft Learn, Agent flows).
Detailed instructions:
plan and execute endpoints, authenticated with Entra ID. Do not put the Marketo client secret anywhere in the flow.plan. Return valid, issues, program_name and plan_id to the agent.plan_id. This flow runs the approval (Step 7) and, only if approved, calls execute.Example configuration (illustrative flow outline):
Flow: Execute approved Marketo program plan
Trigger: When an agent calls the flow
Inputs: country, program_type, short_name, launch_date, tokens (JSON), plan_id
1. Get requester from the signed-in user context
2. Call integration layer: POST /plan -> confirm plan_id matches and valid = true
3. Start and wait for an approval (Approvals connector)
Title: "Prepare Marketo program 2026-11_DE_WBN_AI-in-Manufacturing"
Assigned to: requester (confirmation) or regional marketing ops lead (approval)
Details: plan summary, template, folder, tokens, what will NOT be done
4. Condition: outcome = Approve
Yes -> Call integration layer: POST /execute (plan_id, approval reference)
No -> Return "Not approved" with the approver's comments
5. Return status, program name, verification checks and manual steps to the agent
Expected result: the agent can only plan and execute through these two flows. There is no path from the agent to Marketo that bypasses the integration layer.
How to verify: run each flow manually with test inputs. Then confirm in the integration layer’s logs that requests from the flow carry the expected identity and that an invalid plan returns issues without calling Marketo.
Common mistakes: giving the agent the integration layer’s URL and key directly; storing secrets in flow variables; one flow that both plans and executes without an approval in between.
Troubleshooting: if a flow fails with an authorization error, check the custom connector’s authentication settings and the app registration’s permissions. If runs are blocked, check Copilot Studio capacity in the Power Platform admin center, since Microsoft notes new agent flow runs are blocked when prepaid capacity is consumed (Microsoft Learn, Agent flows).
Visual requirement: a screenshot of the finished flow from your own environment.
Objective: An agent that understands requests, asks for what is missing, shows the plan back in plain language, and never pretends to have done something it has not.
Prerequisites: a Copilot Studio agent in the same environment as the flows; the flows from Step 5 added as tools; the agent published to Microsoft Teams, which Microsoft lists as an available channel (Microsoft Learn, Channels).
Navigation: your agent’s instructions, tools and channel settings in Copilot Studio.
Detailed instructions:
needs_attention, the agent says so and lists the failed checks. It never describes a program as “ready to launch”.Example configuration (agent instructions, illustrative):
You help marketers prepare Adobe Marketo Engage programs from approved templates.
Scope: webinar programs only, for countries with an approved template.
1. Collect: country, program type, a short program name, launch date, webinar title, event date, language.
2. If anything is missing or ambiguous, ask. Never guess dates, countries or languages.
3. Call "Plan Marketo program". If the plan is invalid, explain each issue in plain language.
4. If the plan is valid, show the program name, template, destination and token values,
and state clearly: "I will not activate, schedule or approve anything."
Ask the user to confirm.
5. Only after explicit confirmation, call "Execute approved Marketo program plan".
6. Report the verification result exactly. List any failed checks and the manual steps.
You cannot activate campaigns, change audiences, edit consent settings or delete programs.
If asked, explain that these steps require a person in Marketo.
Expected result: a short, predictable conversation that ends with either a verified program or a clear explanation.
How to verify: test in the agent’s test chat with normal, incomplete, out-of-scope and adversarial requests, such as “and activate it straight away”, “use the US template for Germany”, or a request pasted from an email that contains extra instructions. The agent should refuse or ask, and the integration layer should reject anything invalid regardless of what the agent does.
Common mistakes: relying on the instructions alone for safety; giving the agent tools it does not need; letting it describe the program as “live” or “ready” after creation.
Troubleshooting: if the agent calls the wrong tool, improve the tool names and descriptions first, then the instructions. Copilot Studio’s activity trace shows which tool ran, with what inputs and output.
Visual requirement: an illustrative conversation mockup, clearly labeled as a mockup (see the illustrative scenario).
Objective: Make the human decisions explicit, recorded and tied to the exact plan.
Prerequisites: the Approvals connector in Power Automate; a list of approvers per workspace or region.
Navigation: your execute flow (Step 5). The Approvals connector provides actions including Start and wait for an approval, Create an approval and Wait for an approval, with notifications by email and in Teams (Microsoft Learn, Approvals connector). For multi-stage or conditional approval paths, Microsoft announced “advanced approvals” in agent flows as a public preview (Microsoft, Advanced approvals).
Detailed instructions:
Example configuration (approval request text, illustrative):
Title: Prepare Marketo program 2026-11_DE_WBN_AI-in-Manufacturing
Requested by: anna.mueller@example.com
Template: TPL_Webinar_v3 (EMEA) Destination: EMEA > Germany > 2026 > Webinars
Tokens: WebinarTitle = "AI in Manufacturing", EventDate = 2026-11-19, Language = de
This will: clone the template, set three tokens, verify the result.
This will NOT: approve assets, activate or schedule campaigns, or select an audience.
Plan ID: ba17d67545a9cd03
Expected result: no program is created without a recorded “Approve”, and no campaign is activated by the assistant at all.
How to verify: reject a test approval and confirm nothing is created. Approve one, change a field, and confirm the execute step rejects the changed plan.
Common mistakes: one approval that covers “everything” including activation; approvals with no plan details, which approvers rubber-stamp.
Troubleshooting: if approvers do not see requests, check that they have access to approvals in their tenant and that notifications are enabled. Guest users cannot act on approvals from Outlook and need the Power Automate portal (Microsoft Learn, Approvals connector).
Visual requirement: an example approval card from your own tenant.
Objective: Prove that what was created matches the plan, and tell the marketer exactly what still needs doing.
Prerequisites: the template’s required tokens and smart campaigns, defined in the registry.
Navigation: the integration layer’s verify_program function.
Detailed instructions:
GET /rest/asset/v1/program/{id}.json. Check name, folder, workspace and channel.GET /rest/asset/v1/folder/{id}/tokens.json?folderType=Program and compare every required value.prepared only when every check passes. Otherwise return needs_attention with the failing checks, and say so in Teams.Example configuration: in the reference code, verification returns a list such as:
{
"passed": false,
"checks": [
{ "check": "Name matches plan", "ok": true },
{ "check": "Created in approved folder", "ok": true },
{ "check": "Token my.EventDate set", "ok": true },
{ "check": "Smart campaign '03 Reminder' present", "ok": true },
{ "check": "No smart campaign is active", "ok": false }
],
"active_campaigns": ["01 Registration"]
}
Illustrative output from the mocked test, not from a live instance.
Expected result: every program the assistant prepares comes with a pass/fail report.
How to verify: deliberately break a test template (for example, leave a campaign active) and confirm verification fails and the Teams message says so.
Common mistakes: treating the clone response as proof; checking only that the program exists.
Troubleshooting: if token checks fail, confirm the template defines those tokens and that token names are sent without the my. prefix. If the campaign list is empty, check the API user’s permissions in that workspace.
Visual requirement: the verification checklist as a reference graphic.
Objective: Make every action traceable, know how to undo a mistake, and introduce the assistant gradually.
Prerequisites: an append-only log store; owners for review; a pilot group.
Navigation: your log store; Marketo Audit Trail, which records create, edit and delete actions for programs, smart campaigns, lists and Design Studio assets, with six months of history (the last 30 days in the app, the rest by export) (Adobe, Audit Trail).
Detailed instructions:
Example configuration (audit record, illustrative):
{"ts": "2026-10-18T09:14:03Z", "event": "program_cloned", "correlation_id": "6f1c...", "requester": "anna.mueller@example.com",
"program_id": 5000, "program_name": "2026-11_DE_WBN_AI-in-Manufacturing", "approval_ref": "APPROVAL-42", "template_program_id": 1001}
Expected result: you can answer “who asked for this program, who approved it, what was created and did it pass verification” for any program, in minutes.
How to verify: pick a random program from the pilot and reconstruct its history from the logs and Audit Trail.
Common mistakes: logging full chat transcripts with unnecessary personal data; no owner for the logs; skipping the sandbox.
Troubleshooting: if a program exists in Marketo with no matching audit record, someone created it outside the assistant or logging failed. Treat it as an incident and investigate.
Visual requirement: a rollout roadmap: sandbox, pilot, expand.
Want the next lab in your inbox? The Enterprise MarTech Lab publishes practical architecture and governance guides like this one. Subscribe to the newsletter.
Illustrative scenario: the company, people and values below are fictional. They show how the design is meant to behave, not results from a real deployment.
The business problem. A global B2B manufacturer runs dozens of webinars a year across EMEA. Each one starts with a marketer asking marketing operations to “set up the program”. Requests arrive by email with missing details, names drift from the convention, and setup competes with more valuable work.
The technical context. One Marketo instance with regional workspaces. A reviewed TPL_Webinar_v3 template exists in the EMEA workspace. The company already uses Microsoft 365 and has approved Copilot Studio and Power Automate for internal use.
The conversation.
2026-11_DE_WBN_AI-in-Manufacturing, confirm it is unique and check the lead time.How success would be measured. Time from request to a correctly prepared program; percentage of programs passing naming and verification checks; number of setup corrections found in QA; marketer satisfaction with the request process. Measure a baseline before the pilot, so improvements are evidence, not impressions.
Potential risks and limitations. The assistant only covers one program type and one workspace at first. Webinar provider linking and smart list review remain manual. If the template is wrong, every program built from it is wrong, so template governance matters more than ever. Shared API quotas need monitoring.
You do not need this full architecture to get most of the value. Compare the options honestly.
| Option | How it works | Strengths | Trade-offs | Best for |
|---|---|---|---|---|
| Manual clone with a checklist | Marketers or ops clone templates in the UI and follow a QA checklist | No build cost, full control | Slow at scale, naming drift, relies on discipline | Low program volumes |
| Request form plus automation, no AI | A structured form (for example Microsoft Forms) feeds the same rules and integration layer | Deterministic, easy to audit, cheapest automated option | Less convenient than chat; no help with incomplete requests | Teams that want governance first, AI later |
| Governed Teams assistant (this article) | AI interprets; rules validate; integration layer clones; humans approve | Convenient, consistent, auditable | Build and run cost; Microsoft licensing; ongoing maintenance | Medium to high volume, Microsoft 365 organizations |
| Adobe Marketo MCP server connected to an agent | An agent such as Copilot Studio adds Adobe’s hosted MCP server as a tool (Microsoft Learn, MCP in Copilot Studio) and calls its operations directly | Broad operations, including flow steps and smart list rules, without custom API code | Large tool surface; beta; governance depends on your agent design and API permissions | Experienced ops teams in a sandbox, with strict permissions and review |
| Third-party integration platform | Vendors such as Workato publish Marketo program operations tooling | Prebuilt connectors and monitoring | Another vendor, another data path; check capabilities yourself | Organizations already standardized on that platform |
A sensible path for many teams is to start with the request form plus automation, prove the rules and templates, then add the conversational layer once the governance is solid. The AI is the friendliest part of this system, but it is not the part that makes it safe.
Use the three levels from Enterprise MarTech Lab #001: technical success, operational effectiveness and business impact.
| Level | What to measure | Where the data comes from |
|---|---|---|
| Technical success | Plans created, executions, API errors, verification pass rate, quota usage | Integration layer audit log, Marketo API usage |
| Operational effectiveness | Time from request to prepared program, naming compliance, QA corrections per program, approval cycle time, rejected plans by reason | Audit log, QA checklist results, approval history |
| Business impact | Ops time redirected to higher-value work; campaign launch timeliness; fewer launch errors reaching customers | Ops time tracking, campaign calendar, incident log |
Capture a baseline for at least a few weeks before the pilot. Without one, you will only have impressions.
| Symptom | Likely cause | Fix |
|---|---|---|
| Error 601 or 602 | Access token invalid or expired (Adobe, Error codes) | Request a new token; cache tokens and refresh before the one-hour expiry |
| Error 603 | The API user’s role lacks permission for this API | Review the role’s Access API permissions |
| Error 606 | More than 100 calls in 20 seconds | Back off and retry; reduce parallel calls; queue requests in the integration layer |
| Error 607 | Daily quota reached (resets at 12:00 AM US Central time) | Find which integration is consuming the quota; ask your account manager about limits |
| Error 615 | More than 10 concurrent calls | Limit concurrency in the integration layer |
| Error 709 | Business rule violation, for example a duplicate name or a missing required tag | Check name uniqueness and your instance’s required tags (GET /rest/asset/v1/tagTypes.json) |
| Error 710 | Parent folder not found | Check the folder ID and that the API user can see that workspace |
| Error 711 | Incompatible folder type | Send the folder as {"id": ..., "type": "Folder"} for clones |
| Clone fails on certain templates | Template contains push notifications, in-app messages, reports or social assets | Remove them from the template |
| Clone into another region fails | Target folder is in a different workspace | Keep a template in each workspace |
| Program created but tokens missing | Token names sent with the my. prefix, wrong folderType, or wrong type |
Send names without my., use folderType=Program, check the type |
| Assets you know exist return empty results | API user not assigned to that workspace | Assign the role in that workspace |
| Agent says “done” but the program is incomplete | Agent summarizing instead of reporting verification | Make the agent repeat the verification result verbatim; never say “ready to launch” |
| Approval approved but execution rejected | Plan changed or name taken since approval | Expected behavior; submit again |
Yes. POST /rest/asset/v1/programs.json creates a program with a name, folder, type and channel, and optionally a description, costs and tags. Type and channel cannot be changed afterwards (Adobe, Programs API).
Yes, with POST /rest/asset/v1/program/{id}/clone.json. The new name must be globally unique and the target folder must be in the same workspace. Programs with push notifications, in-app messages, reports or social assets cannot be cloned (Adobe, Programs API).
Adobe’s REST documentation covers reading smart list rules, and cloning and deleting smart lists, but not creating or editing filters, triggers or flow steps. Adobe’s Marketo MCP server, listed as beta, describes operations for smart list filters and flow steps (Adobe, Marketo MCP server). Check the current documentation before relying on either.
In this design, no. Activation affects real people and cannot be undone once they are processed. Keep it as a separate human step with its own approval.
No. The governance layer and integration code work with any front end, including a form, a ticketing tool or another AI assistant. Copilot Studio and Teams are a convenient choice for Microsoft 365 organizations.
It can replace the custom API code, but not the governance. If you connect an agent to the MCP server, you still need a narrow toolset, a least-privilege API user, approvals and an audit trail.
In the reference code, about 10 calls per program: the access token, a name check when planning and again when executing, the clone, one call per token, and three verification reads. Exact numbers depend on your template and retries.
Yes, with one approved template per workspace and permissions mapped per workspace. Clones cannot cross workspaces.
The real opportunity is not simply using AI to create Marketo programs faster. It is using AI to make marketing operations more accessible, consistent and governed by design.
The technology already allows an assistant to prepare a program from an approved template in the right place, with the right name and tokens. What makes it safe is everything around the AI: templates you trust, rules in code, a narrow integration, verification after every action, and humans in charge of anything that reaches customers.
Imagine marketers spending less time navigating systems and more time developing campaigns that deliver business value. That future is reachable. But automation without governance is simply a faster way to scale mistakes.
Your turn: would you trust an AI assistant to create Marketo programs if every program had to pass automated validation and human approval before activation? And where would you draw the line between AI autonomy and human control?
Continue reading: Can AI Agents Safely Manage Marketing Automation? · The Risks of Uncontrolled AI Automation · Power Automate Approval Flows for Campaign Governance · Marketing Automation QA Checklist · Why Marketing Automation Fails Even When the Technology Works
The file below is the complete reference implementation used in this article. It validates a request, clones an approved template, sets tokens, verifies the result and writes an audit log. It deliberately contains no activation, approval or deletion operations.
Status: illustrative reference code, tested offline against a mocked Marketo API (including a rate-limit retry, a duplicate-name rejection, a changed-plan rejection and an active-campaign verification failure). It has not been run against a live Marketo instance. Run it against a sandbox before use, and adapt the template registry, permissions and naming rules to your organization.
"""
Governed Marketo program preparation service (reference implementation).
EverythingMartech, The Enterprise MarTech Lab #002.
What it does
1. Validates a structured program request with deterministic business rules.
2. Builds the program name itself (the AI never writes the final name).
3. Clones an approved template program into an approved folder.
4. Sets program tokens, then verifies the result and reports anything missing.
5. Writes an audit record for every plan and every execution.
What it deliberately does NOT do
- Activate, schedule or request smart campaigns.
- Approve programs or assets.
- Touch leads, lists or consent fields.
Status: illustrative reference code. It was tested against a mocked Marketo API,
not against a live Marketo instance. Run it against a sandbox first.
Requires: Python 3.10+, requests (pip install requests)
Environment variables: MKTO_BASE_URL (for example https://123-ABC-456.mktorest.com),
MKTO_CLIENT_ID, MKTO_CLIENT_SECRET. Keep the secrets in a vault, never in code.
"""
from __future__ import annotations
import hashlib
import json
import os
import re
import time
import uuid
from dataclasses import dataclass, field
from datetime import date, datetime, timezone
import requests
# ---------------------------------------------------------------------------
# 1. Governance configuration (owned by marketing operations, not by the AI)
# ---------------------------------------------------------------------------
# Approved templates per (country, program type). IDs are examples: replace
# them with the IDs of your own template programs and target folders.
TEMPLATE_REGISTRY = {
("DE", "webinar"): {
"template_program_id": 1001, # e.g. "TPL_Webinar_v3" in the EMEA workspace
"target_folder_id": 2050, # e.g. "EMEA > Germany > 2026 > Webinars"
"workspace": "EMEA",
"channel": "Webinar",
"type_code": "WBN",
"required_tokens": ["my.WebinarTitle", "my.EventDate", "my.Language"],
"required_smart_campaigns": ["01 Registration", "02 Confirmation", "03 Reminder"],
},
("FR", "webinar"): {
"template_program_id": 1002,
"target_folder_id": 2051,
"workspace": "EMEA",
"channel": "Webinar",
"type_code": "WBN",
"required_tokens": ["my.WebinarTitle", "my.EventDate", "my.Language"],
"required_smart_campaigns": ["01 Registration", "02 Confirmation", "03 Reminder"],
},
}
# Which workspaces each requester may create programs in. In production, read
# this from an identity group (for example Microsoft Entra ID), not a dict.
WORKSPACE_PERMISSIONS = {
"anna.mueller@example.com": {"EMEA"},
"ops.lead@example.com": {"EMEA", "NA", "APAC"},
}
# Naming convention: YYYY-MM_CC_TYPE_Short-Name, for example 2026-11_DE_WBN_AI-in-Manufacturing
NAME_PATTERN = re.compile(
r"^(?P<yyyy>20\d{2})-(?P<mm>0[1-9]|1[0-2])_(?P<cc>[A-Z]{2})_(?P<type>[A-Z]{3})_(?P<short>[A-Za-z0-9-]{3,60})$"
)
MIN_LEAD_TIME_DAYS = 14 # business rule: no programs prepared for launches inside 14 days
MAX_NAME_LENGTH = 255 # Marketo limit for cloned program names
@dataclass
class ProgramRequest:
"""Structured requirements extracted by the AI assistant from the chat message."""
requester: str # set by the platform from the signed-in user, never by the AI
country: str # ISO 3166-1 alpha-2, e.g. "DE"
program_type: str # e.g. "webinar"
short_name: str # e.g. "AI-in-Manufacturing"
launch_date: str # ISO date, e.g. "2026-11-19"
tokens: dict = field(default_factory=dict) # e.g. {"my.WebinarTitle": "..."}
@dataclass
class Plan:
valid: bool
issues: list
program_name: str | None = None
template: dict | None = None
plan_id: str | None = None
# ---------------------------------------------------------------------------
# 2. Deterministic validation (no AI involved)
# ---------------------------------------------------------------------------
def build_program_name(req: ProgramRequest, type_code: str) -> str:
launch = date.fromisoformat(req.launch_date)
short = re.sub(r"[^A-Za-z0-9-]+", "-", req.short_name.strip()).strip("-")
return f"{launch:%Y-%m}_{req.country.upper()}_{type_code}_{short}"
def validate_request(req: ProgramRequest, today: date | None = None) -> Plan:
today = today or date.today()
issues = []
template = TEMPLATE_REGISTRY.get((req.country.upper(), req.program_type.lower()))
if not template:
issues.append(f"No approved template for country '{req.country}' and type '{req.program_type}'.")
return Plan(valid=False, issues=issues)
allowed = WORKSPACE_PERMISSIONS.get(req.requester.lower(), set())
if template["workspace"] not in allowed:
issues.append(f"{req.requester} is not permitted to create programs in workspace '{template['workspace']}'.")
try:
launch = date.fromisoformat(req.launch_date)
if (launch - today).days < MIN_LEAD_TIME_DAYS:
issues.append(f"Launch date {launch} is less than {MIN_LEAD_TIME_DAYS} days away.")
except ValueError:
issues.append(f"Launch date '{req.launch_date}' is not a valid ISO date (YYYY-MM-DD).")
return Plan(valid=False, issues=issues, template=template)
name = build_program_name(req, template["type_code"])
if not NAME_PATTERN.match(name):
issues.append(f"Generated name '{name}' does not match the naming convention.")
if len(name) > MAX_NAME_LENGTH:
issues.append("Program name exceeds 255 characters.")
missing = [t for t in template["required_tokens"] if not str(req.tokens.get(t, "")).strip()]
if missing:
issues.append("Missing required token values: " + ", ".join(missing))
unknown = [t for t in req.tokens if t not in template["required_tokens"]]
if unknown:
issues.append("Tokens not allowed by this template: " + ", ".join(unknown))
plan_id = hashlib.sha256(
json.dumps([req.requester, name, template["template_program_id"], req.tokens], sort_keys=True).encode()
).hexdigest()[:16]
return Plan(valid=not issues, issues=issues, program_name=name, template=template, plan_id=plan_id)
# ---------------------------------------------------------------------------
# 3. Marketo REST client (token caching, rate-limit retries, error handling)
# ---------------------------------------------------------------------------
class MarketoError(Exception):
def __init__(self, errors):
self.errors = errors
super().__init__("; ".join(f"{e.get('code')}: {e.get('message')}" for e in errors))
class MarketoClient:
RETRYABLE = {"606", "615", "604", "608"} # rate limit, concurrency, timeout, temporarily unavailable
REAUTH = {"601", "602"} # invalid or expired access token
def __init__(self, base_url: str, client_id: str, client_secret: str, session=None):
self.base_url = base_url.rstrip("/")
self.client_id = client_id
self.client_secret = client_secret
self.http = session or requests.Session()
self._token = None
self._token_expiry = 0.0
def _get_token(self) -> str:
if self._token and time.time() < self._token_expiry - 60:
return self._token
resp = self.http.get(
f"{self.base_url}/identity/oauth/token",
params={"grant_type": "client_credentials",
"client_id": self.client_id, "client_secret": self.client_secret},
timeout=30,
)
resp.raise_for_status()
body = resp.json()
self._token = body["access_token"]
self._token_expiry = time.time() + int(body.get("expires_in", 0))
return self._token
def call(self, method: str, path: str, params=None, data=None, max_attempts: int = 5):
for attempt in range(1, max_attempts + 1):
headers = {"Authorization": f"Bearer {self._get_token()}"} # header, not query parameter
resp = self.http.request(method, f"{self.base_url}{path}", params=params,
data=data, headers=headers, timeout=60)
resp.raise_for_status()
body = resp.json()
if body.get("success"):
return body.get("result", [])
codes = {str(e.get("code")) for e in body.get("errors", [])}
if codes & self.REAUTH:
self._token = None
continue
if codes & self.RETRYABLE and attempt < max_attempts:
time.sleep(min(2 ** attempt, 20)) # back off; the window is 100 calls per 20 seconds
continue
raise MarketoError(body.get("errors", []))
raise MarketoError([{"code": "retry", "message": "Gave up after retries"}])
# Thin wrappers over documented Asset API endpoints
def program_by_name(self, name):
try:
return self.call("GET", "/rest/asset/v1/program/byName.json", params={"name": name})
except MarketoError as e:
if any(str(x.get("code")) == "702" for x in e.errors): # 702: no data found
return []
raise
def program_by_id(self, program_id):
return self.call("GET", f"/rest/asset/v1/program/{program_id}.json")
def clone_program(self, template_id, name, folder_id, description):
return self.call("POST", f"/rest/asset/v1/program/{template_id}/clone.json", data={
"name": name,
"folder": json.dumps({"id": folder_id, "type": "Folder"}),
"description": description,
})
def set_program_token(self, program_id, name, value, token_type="text"):
# Token names are sent without the "my." prefix
return self.call("POST", f"/rest/asset/v1/folder/{program_id}/tokens.json", data={
"folderType": "Program", "name": name.removeprefix("my."), "type": token_type, "value": value,
})
def program_tokens(self, program_id):
return self.call("GET", f"/rest/asset/v1/folder/{program_id}/tokens.json",
params={"folderType": "Program"})
def smart_campaigns_in(self, program_id):
return self.call("GET", "/rest/asset/v1/smartCampaigns.json", params={
"folder": json.dumps({"id": program_id, "type": "Program"}), "maxReturn": 200,
})
# ---------------------------------------------------------------------------
# 4. Verification: a successful API response is not the same as "ready"
# ---------------------------------------------------------------------------
def verify_program(client: MarketoClient, program_id: int, plan: Plan, req: ProgramRequest) -> dict:
checks = []
program = client.program_by_id(program_id)[0]
tpl = plan.template
checks.append(("Name matches plan", program.get("name") == plan.program_name))
checks.append(("Created in approved folder", (program.get("folder") or {}).get("value") == tpl["target_folder_id"]))
checks.append(("Workspace is correct", program.get("workspace") == tpl["workspace"]))
checks.append(("Channel is correct", program.get("channel") == tpl["channel"]))
token_result = client.program_tokens(program_id)
tokens = {f"my.{t['name']}": t.get("value") for t in (token_result[0].get("tokens", []) if token_result else [])}
for t in tpl["required_tokens"]:
checks.append((f"Token {t} set", tokens.get(t) == req.tokens.get(t)))
campaigns = client.smart_campaigns_in(program_id)
names = {c.get("name"): c for c in campaigns}
for required in tpl["required_smart_campaigns"]:
checks.append((f"Smart campaign '{required}' present", required in names))
active = [c["name"] for c in campaigns if c.get("isActive")]
checks.append(("No smart campaign is active", not active))
return {
"passed": all(ok for _, ok in checks),
"checks": [{"check": c, "ok": ok} for c, ok in checks],
"active_campaigns": active,
"manual_steps": [
"Link the webinar provider event in the program's event settings (not covered by the documented Programs API).",
"Review smart list filters and flow steps in the Marketo UI.",
"Approve emails and landing pages after content review.",
"Activate or schedule campaigns only after the activation approval is recorded.",
],
}
# ---------------------------------------------------------------------------
# 5. Orchestration and audit
# ---------------------------------------------------------------------------
def audit(event: str, **fields):
record = {"ts": datetime.now(timezone.utc).isoformat(), "event": event, **fields}
# Replace with an append-only store (for example Azure Table Storage, Log Analytics or Dataverse)
with open(os.environ.get("AUDIT_LOG_PATH", "audit-log.jsonl"), "a", encoding="utf-8") as f:
f.write(json.dumps(record, default=str) + "\n")
return record
def plan_program(req: ProgramRequest, client: MarketoClient | None = None) -> Plan:
"""Dry run. Validates and previews; changes nothing in Marketo."""
plan = validate_request(req)
if plan.valid and client is not None and client.program_by_name(plan.program_name):
plan.valid = False
plan.issues.append(f"A program named '{plan.program_name}' already exists.")
audit("plan", requester=req.requester, plan_id=plan.plan_id, valid=plan.valid,
program_name=plan.program_name, issues=plan.issues)
return plan
def execute_plan(req: ProgramRequest, client: MarketoClient, approved_plan_id: str, approval_ref: str) -> dict:
"""Creates the program only if the approved plan still matches a fresh validation."""
correlation_id = str(uuid.uuid4())
plan = plan_program(req, client)
if not plan.valid:
return {"status": "rejected", "issues": plan.issues, "correlation_id": correlation_id}
if plan.plan_id != approved_plan_id:
return {"status": "rejected", "issues": ["Request changed after approval. Submit it again."],
"correlation_id": correlation_id}
tpl = plan.template
created = client.clone_program(
tpl["template_program_id"], plan.program_name, tpl["target_folder_id"],
f"Prepared by AI assistant for {req.requester}. Approval ref {approval_ref}. Correlation {correlation_id}.",
)[0]
program_id = created["id"]
audit("program_cloned", correlation_id=correlation_id, requester=req.requester,
program_id=program_id, program_name=plan.program_name, approval_ref=approval_ref,
template_program_id=tpl["template_program_id"])
for token_name, value in req.tokens.items():
token_type = "date" if token_name == "my.EventDate" else "text"
client.set_program_token(program_id, token_name, value, token_type)
verification = verify_program(client, program_id, plan, req)
audit("program_verified", correlation_id=correlation_id, program_id=program_id,
passed=verification["passed"], checks=verification["checks"])
return {
"status": "prepared" if verification["passed"] else "needs_attention",
"program_id": program_id,
"program_name": plan.program_name,
"verification": verification,
"correlation_id": correlation_id,
}
def client_from_env() -> MarketoClient:
return MarketoClient(os.environ["MKTO_BASE_URL"], os.environ["MKTO_CLIENT_ID"], os.environ["MKTO_CLIENT_SECRET"])
if __name__ == "__main__":
# Dry run example: validates without calling Marketo
example = ProgramRequest(
requester="anna.mueller@example.com", country="DE", program_type="webinar",
short_name="AI in Manufacturing", launch_date="2026-11-19",
tokens={"my.WebinarTitle": "AI in Manufacturing", "my.EventDate": "2026-11-19", "my.Language": "de"},
)
p = validate_request(example, today=date(2026, 10, 18))
print(json.dumps({"valid": p.valid, "program_name": p.program_name, "issues": p.issues, "plan_id": p.plan_id}, indent=2))
Sources were checked on 18 October 2026. Platform menus, API capabilities and documentation change frequently; confirm them in your own instance and tenant.
Get the next insight in your inbox.
Prefer to talk it through?
Book a 30 or 60 minute consultation on MarTech, automation or AI workflows.
Newsletter
Practical insights on marketing automation, AI, enterprise architecture, and technology strategy.
Marketing Operations
How global organisations balance a consistent central platform with the speed and nuance local marketing teams need.
Article · 8 min read
Enterprise MarTech
A layered framework for assigning clear responsibilities to CRM, automation, CMS, social, analytics and AI so the stack can grow without tangling.
Architecture Framework · 11 min read
Data & Integrations
Bad data rarely causes one dramatic failure. It quietly raises the cost of every campaign, report and integration. Here is where that cost hides.
Article · 7 min read
Comments