EverythingMartech

Could an AI Assistant Safely Create a Marketo Program?

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:

  • Explain exactly which parts of a Marketo program an integration can create, and which still need the Marketo interface.
  • Design an architecture that keeps the AI on interpretation and leaves decisions to rules and people.
  • Build the validation, creation, verification and audit steps, with reference Python code you can adapt.
  • Decide where your own organization should draw the line between AI autonomy and human control.

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.

Quick answer

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.

Key takeaways

  1. The API can prepare a program, not finish one. Create, clone, tokens, tags and costs are documented. Building smart list filters and flow steps through the REST API is not documented, and program type and channel can only be set when the program is created.
  2. Clone, don’t compose. Cloning a reviewed template program is safer and more complete than assembling a program from parts. Keep a small, versioned library of “golden” templates.
  3. Let AI interpret, let rules decide. The AI extracts country, type, date and content. Code builds the name, checks permissions and picks the template. The AI never writes the final program name or chooses a folder ID.
  4. A success response is not proof of readiness. Verify the folder, workspace, channel, tokens, required campaigns and campaign status after every creation, and report what is missing.
  5. Draw the line at activation. Preparation can be automated. Activation, audiences, consent and high-impact changes need explicit human approval, recorded in an audit trail.

What “creating a Marketo program” actually involves

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.

What the Marketo API can and cannot do today

A comparison of program setup tasks the Marketo REST API supports, such as create, clone, tokens and tags, against tasks that still need the Marketo interface or a human, such as smart list filters, flow steps, webinar provider settings and activation decisions.
What an integration can prepare through the documented REST API, and what still needs the Marketo interface or a person. Checked against Adobe documentation in October 2026.

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)

The limits that shape the design

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):

  • Rate limit: 100 API calls per 20 seconds per instance.
  • Concurrency: a maximum of 10 concurrent API calls.
  • Daily quota: 50,000 calls per day by default, shared with every other integration on the instance (CRM connectors, data tools, enrichment).
  • Authentication: access tokens last one hour and must be sent in an 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.
  • Errors: most API errors return HTTP 200 with "success": false and an error code, so an integration must read the body, not just the HTTP status.

What about Adobe’s Marketo MCP server?

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.

A governed reference architecture

Architecture diagram: a marketer in Microsoft Teams talks to a Copilot Studio agent, which calls a Power Automate agent flow, which calls a secure integration layer containing governance rules, a secrets vault and an audit log, which calls the Marketo REST API. An approval step routes back to a human approver in Teams before anything is created.
Proposed architecture. Each layer has one job, and only the integration layer holds Marketo credentials.

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

Why put an integration layer in the middle?

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:

  1. One place for business rules. Naming conventions, template mappings and workspace permissions live in version-controlled code that marketing operations owns and reviews.
  2. Secrets stay out of flows and prompts. The Marketo client secret sits in a vault such as Azure Key Vault and never passes through the conversation.
  3. A small, allowlisted surface. The layer exposes two operations, 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).
  4. Rate limits and retries are handled once. Shared quotas are managed centrally instead of in every flow.
  5. Complete audit. Every plan, approval reference, API call and verification result is logged with a correlation ID.

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).

Where to draw the line between AI and humans

A three-column chart titled where to draw the line. AI can do: interpret the request, ask clarifying questions, draft token values, summarize the plan. AI prepares and a human approves: create the program from a template, set tokens and tags, rename. Human only: activate or schedule campaigns, choose audiences, consent-sensitive operations, delete programs.
A suggested autonomy model. Adjust the columns to your own risk appetite, but decide them before you build.

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.

Step-by-step: build the governed workflow

Five-stage process: 1 understand the request, 2 validate with deterministic rules, 3 create from an approved template, 4 verify the result, 5 require human approval for activation. An audit log runs underneath every stage.
The five stages of the governed workflow. The audit log runs underneath all of them.

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.

Step 1: Prepare Marketo for governed automation

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:

  1. Create or confirm your template programs. For each program type you want the assistant to support (start with one, such as webinars), make sure a reviewed template exists in each workspace that will use it. Remember that the clone API cannot copy across workspaces, so an EMEA template is needed for EMEA programs.
  2. Name templates so they are easy to recognize, for example TPL_Webinar_v3, and keep them in a dedicated templates folder that only marketing operations can edit.
  3. Make the template “clone-ready”. Remove anything the clone API cannot handle (push notifications, in-app messages, reports, social assets). Leave all smart campaigns inactive. Use tokens such as {{my.WebinarTitle}}, {{my.EventDate}} and {{my.Language}} in emails and landing pages, so a new program only needs token values.
  4. Create target folders where prepared programs will land, for example 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).
  5. Write down the naming convention as a pattern code can check. This article uses YYYY-MM_CC_TYPE_Short-Name, for example 2026-11_DE_WBN_AI-in-Manufacturing.
  6. Create a role for the integration. In Admin > Users & Roles, create a role that grants only the asset read and write permissions the workflow needs from the Access API permission group. Do not grant lead, list or campaign execution permissions the workflow does not use. Permission names can vary slightly between instances, so review the Access API list carefully.
  7. Create an API-only user with that role, assigned only in the workspaces the assistant will serve. Adobe’s REST API guide describes creating a user with the API Only option and assigning a role (Adobe, REST API).
  8. Create a LaunchPoint custom service for the API-only user. Copy the Client ID and Client Secret straight into your secrets vault.

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).

Step 2: Define the request schema the AI must produce

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:

  1. List the fields a program request needs. Keep the list short; every extra field is another thing the AI can get wrong.
  2. Separate fields the AI extracts (country, program type, short name, launch date, token values) from fields the platform supplies (the requester’s identity, taken from their signed-in account, never from the chat text).
  3. Exclude anything the rules should derive: the final program name, folder ID, workspace and template ID are calculated by code from the registry.
  4. Write the schema down, for example as JSON Schema, and use it both in the agent’s instructions and in the integration layer’s input validation.

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”.

Step 3: Encode the governance rules

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:

  1. Template check: is there an approved template for this country and program type? If not, stop.
  2. Permission check: is the signed-in requester allowed to create programs in the template’s workspace? In production, read this from identity groups (for example Microsoft Entra ID security groups), not a hard-coded list.
  3. Date check: is the launch date valid and far enough away to allow review? The example uses a 14-day minimum lead time; use your own.
  4. Name generation: build the program name from the validated fields, then check it against the naming pattern and the 255-character limit.
  5. Uniqueness check: call GET /rest/asset/v1/program/byName.json to make sure the name is not already taken.
  6. Channel check: confirm the template’s channel exists (GET /rest/asset/v1/channels.json) and matches the program type, because channel cannot be changed after creation.
  7. Token check: all required tokens are present, and no unexpected tokens are included.
  8. Return a plan, not an action: a valid/invalid flag, a list of human-readable issues, the program name and a plan ID (a hash of the exact plan) that the approval will refer to.

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.

Step 4: Build the integration layer’s create, verify and audit functions

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:

  1. Authenticate correctly. Request a token from <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).
  2. Handle Marketo’s error model. Read 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).
  3. Re-validate at execution time. Between approval and execution, a name may have been taken or a permission removed. Run the plan again and compare its plan ID with the one that was approved. If they differ, stop.
  4. Clone the template with 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.
  5. Set tokens with 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).
  6. Verify (see Step 8) and write audit records for the plan, the clone and the verification.
  7. Return a structured result to the flow: status (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.

Step 5: Expose the integration layer to Power Automate

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:

  1. Create a custom connector or an HTTP action that calls your integration layer’s plan and execute endpoints, authenticated with Entra ID. Do not put the Marketo client secret anywhere in the flow.
  2. Create flow 1, “Plan Marketo program”, using the When an agent calls the flow trigger. Inputs: the structured request fields. Add the requester’s identity from the authenticated user context rather than from a text input. Call plan. Return valid, issues, program_name and plan_id to the agent.
  3. Create flow 2, “Execute approved Marketo program plan”, also triggered by the agent. Inputs: the request fields and plan_id. This flow runs the approval (Step 7) and, only if approved, calls execute.
  4. Keep both flows in a solution so they can be versioned and moved between development, test and production environments.
  5. Apply your organization’s data policies (Power Platform data loss prevention policies) so the custom connector can only be used in approved environments.

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.

Step 6: Design the Copilot Studio agent and conversation

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:

  1. Write focused instructions. Tell the agent its single job, the fields it must collect, the allowed values, and the rules of behavior (see the example below).
  2. Add only the two tools. Name and describe them precisely so the agent knows when to use each. Do not add general-purpose Marketo tools to this agent.
  3. Require a confirmation turn. After calling “Plan Marketo program”, the agent shows the plan: program name, template, folder, tokens, what will happen and what will not. Only after the user confirms does it call the execute flow.
  4. Return results honestly. If verification returns needs_attention, the agent says so and lists the failed checks. It never describes a program as “ready to launch”.
  5. Publish to Teams for a small pilot group first.

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).

Step 7: Add human approval at the right points

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:

  1. Approval 1, preparation (lightweight): the requester, or a regional marketing operations lead for sensitive workspaces, approves the plan before the program is created. Include the plan ID in the approval so the approval cannot be reused for a different plan.
  2. Approval 2, activation (outside the assistant): activation is a separate, human process in Marketo, after content review, audience review and any privacy checks. The assistant is not involved, and the integration layer has no activation operation.
  3. Record the approval reference (the approval’s ID and the approver) in the program description and the audit log.
  4. Set a timeout for unanswered approvals so stale plans are not executed weeks later, and require re-validation at execution time anyway.

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.

Step 8: Verify the result and report the gaps

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:

  1. Read the program back with GET /rest/asset/v1/program/{id}.json. Check name, folder, workspace and channel.
  2. Read the tokens with GET /rest/asset/v1/folder/{id}/tokens.json?folderType=Program and compare every required value.
  3. List the smart campaigns in the program with the Smart Campaigns browse endpoint, filtered to the program. Check that every required campaign exists and that none is active.
  4. List the manual steps that the API does not cover for this program type: linking the webinar provider event, reviewing smart list filters and flow steps, approving emails and landing pages, and the separate activation approval.
  5. Return 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.

Step 9: Audit, roll back and roll out

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:

  1. Log every event with a correlation ID: the request (with personal data kept to the requester’s identity), the plan and its issues, the approval reference and approver, each Marketo call and its result, and the verification report.
  2. Review the log regularly alongside Marketo’s Audit Trail. Look for rejected plans, failed verifications and unusual volumes.
  3. Define rollback. Because campaigns are never activated by the assistant, a mistaken program has not touched anyone yet. The safest rollback is for a Marketo user to review and delete or archive it in the UI. If you automate deletion, restrict it to programs the assistant created, that have no members, and only within a short window. The Programs API documents a delete endpoint, but deletion is destructive, so keep it out of the assistant’s tools.
  4. Roll out in stages: sandbox, then a pilot of a few marketers in one workspace with one template, then more templates and workspaces once the audit log shows clean results.

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: a Germany webinar, end to end

Illustrative mockup of a chat conversation. The marketer asks for a Germany webinar program. The assistant asks for the event date and language, shows the plan with the generated program name and the statement that it will not activate anything, then reports the program as prepared with all campaigns inactive and three manual steps remaining.
Illustrative mockup, not a screenshot of Microsoft Teams or Copilot Studio.

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.

  1. Anna, a field marketer in Munich, writes in Teams: “Create a webinar program for Germany using our approved template. Topic: AI in Manufacturing, mid-November.”
  2. The assistant asks: “Which date in November, and should the program be in German or English?” Anna answers: “19 November, German.”
  3. The assistant calls the plan flow. The rules find the EMEA template, confirm Anna’s EMEA access, build the name 2026-11_DE_WBN_AI-in-Manufacturing, confirm it is unique and check the lead time.
  4. The assistant replies with the plan and the sentence “I will not activate, schedule or approve anything.” Anna confirms.
  5. Because the EMEA workspace requires it, an approval goes to the regional marketing operations lead with the full plan. She approves in Teams.
  6. The integration layer re-validates, clones the template, sets three tokens and verifies. All checks pass.
  7. The assistant reports: “Prepared: 2026-11_DE_WBN_AI-in-Manufacturing in EMEA > Germany > 2026 > Webinars. All campaigns are inactive. Still to do in Marketo: link the webinar provider event, review the smart lists, approve the emails. Activation needs a separate approval.”

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.

Security, privacy and compliance considerations

  • Least privilege at every layer. The Marketo API user has asset permissions only, in the workspaces it serves. The integration layer exposes two operations. The agent has two tools. OWASP’s guidance on Excessive Agency recommends exactly this combination: minimal functions, minimal permissions, and human approval for high-impact actions (OWASP, LLM06:2025).
  • Permissions are not a complete control. An asset read-write permission may allow endpoints the workflow never calls, such as smart campaign activation. That is why the allowlist in the integration layer matters as much as the Marketo role.
  • Identity comes from authentication, not text. The requester is taken from the signed-in Microsoft account. A message saying “I’m the EMEA ops lead” changes nothing.
  • Prompt injection. Requests may include pasted text from emails or documents. Treat everything in the conversation as untrusted. The deterministic rules and allowlist mean injected instructions cannot widen what the system does.
  • Secrets management. Store the Marketo Client ID and Secret in a vault, rotate them, and never log them. Adobe’s own MCP guidance likewise recommends a dedicated API user with minimal permissions and keeping secrets out of version control (Adobe, Marketo MCP server).
  • Personal data. Creating a program needs no customer personal data. Keep it that way: the assistant should not read or write leads. Log only what you need (the requester’s identity and the plan), and check your retention rules.
  • Consent. Consent-sensitive steps, such as choosing who receives an email or changing subscription logic, stay with people. Local rules differ (for example, GDPR in the EU), so involve your privacy team when you define the “human only” column.
  • Data processing by the AI service. Prompts are processed by the AI platform your organization has approved. Confirm data residency and retention settings with your Microsoft administrator before the pilot.
  • Change management. Templates, rules and agent instructions are production configuration. Version them, review changes, and test them in a sandbox.

Alternatives to a custom Teams assistant

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.

How to measure whether it is working

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.

Common mistakes to avoid

  1. Letting the AI decide validity. A model can explain a naming rule; it should not enforce one. Put rules in code.
  2. Giving the agent a generic Marketo toolset. Every tool you add is an action someone can talk the agent into. Expose only what the workflow needs.
  3. Building from scratch instead of cloning. Composing programs from parts multiplies the number of API calls and the number of things that can go wrong.
  4. Ignoring workspace boundaries. Clones must stay in the source program’s workspace, so plan one template per workspace.
  5. Trusting a success response. Always read the program back and check it.
  6. Mixing preparation and activation. One “approve” button for both encourages rubber-stamping the step that matters most.
  7. Hard-coding identities and secrets. Use identity groups and a vault.
  8. Skipping the sandbox. Test with a sandbox instance and test templates before touching production folders.
  9. Forgetting the shared API quota. Your assistant shares 50,000 daily calls (by default) with every other integration.
  10. No owner. Templates, rules, the agent and the audit log each need a named owner in marketing operations.

Troubleshooting guide

A reference chart of common Marketo REST API errors in this workflow, with the likely cause and fix for each: 601 and 602 token problems, 603 access denied, 606 rate limit, 607 daily quota, 615 concurrency, 709 business rule violation, 710 parent folder not found, 711 incompatible folder type, and clone failures caused by unsupported assets.
Quick reference for the errors you are most likely to meet. Error meanings from Adobe’s error code documentation.
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

Frequently asked questions

Can the Marketo REST API create a program?

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).

Can I clone a Marketo program through the 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).

Can the API build smart list filters and flow steps?

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.

Should an AI assistant be allowed to activate Marketo campaigns?

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.

Do I need Copilot Studio and Power Automate?

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.

Is Adobe’s Marketo MCP server a replacement for this design?

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.

How many API calls does one program take?

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.

Can this work across multiple Marketo workspaces?

Yes, with one approved template per workspace and permissions mapped per workspace. Clones cannot cross workspaces.

Conclusion: governed by design

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

Appendix: complete reference code

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.

Show the complete reference code (about 350 lines of Python)
"""
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))

References

  1. Adobe Experience League. “Programs” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/assets/programs
  2. Adobe Experience League. “Endpoint reference” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/endpoint-reference
  3. Adobe Experience League. “Tokens” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/assets/tokens
  4. Adobe Experience League. “Smart Campaigns” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/assets/smart-campaigns
  5. Adobe Experience League. “Smart Lists” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/assets/smart-lists
  6. Adobe Experience League Community. “Marketo API becoming obsolete: new endpoints needed to programmatically create/edit smart campaigns flows and filters and smart lists.” Community post, February 2026. https://experienceleaguecommunities.adobe.com/adobe-marketo-engage-general-27/marketo-api-becoming-obsolete-new-endpoints-needed-to-programatically-create-edit-smart-campaigns-flows-and-filters-and-smart-lists-248308
  7. Adobe Experience League. “REST API” (Marketo Engage developer documentation: limits, authentication header, API-only users). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/rest-api
  8. Adobe Experience League. “Marketo Engage MCP server” (beta). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/mcp-server
  9. Adobe Experience League Community. “Marketo’s MCP vs REST API.” Community discussion, May to July 2026. https://experienceleaguecommunities.adobe.com/adobe-marketo-engage-general-27/marketo-s-mcp-vs-rest-api-250496
  10. OWASP GenAI Security Project. “LLM06:2025 Excessive Agency.” https://genai.owasp.org/llmrisk/llm062025-excessive-agency/
  11. Adobe Experience League. “Authentication” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/authentication
  12. Adobe Experience League. “Error Codes” (Marketo Engage REST API). https://experienceleague.adobe.com/en/docs/marketo-developer/marketo/rest/error-codes
  13. Microsoft Learn. “Agent flows overview” (Microsoft Copilot Studio). https://learn.microsoft.com/en-us/microsoft-copilot-studio/flows-overview
  14. Microsoft Learn. “Publication channels overview” (Microsoft Copilot Studio). https://learn.microsoft.com/en-us/microsoft-copilot-studio/agents-experience/publication-channels-overview
  15. Microsoft Learn. “Approvals” connector reference. https://learn.microsoft.com/en-us/connectors/approvals/
  16. Microsoft Copilot Blog. “Build advanced approvals in agent flows.” 8 May 2025. https://www.microsoft.com/en-us/copilot/blog/copilot-studio/build-advanced-approvals-in-agent-flows/
  17. Adobe Experience League. “Audit Trail Overview” (Marketo Engage). https://experienceleague.adobe.com/en/docs/marketo/using/product-docs/administration/audit-trail/audit-trail-overview
  18. Microsoft Learn. “Add a Model Context Protocol (MCP) server to your agent as a tool” (Copilot Studio, prerelease documentation). https://learn.microsoft.com/en-us/microsoft-copilot-studio/agents-experience/tools-add-mcp-server

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.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Newsletter

Get the next insight in your inbox.

Practical insights on marketing automation, AI, enterprise architecture, and technology strategy.

Newsletter consent

Related resources

Editorial illustration for Global Marketing Automation: Centralization vs Local Flexibility

Marketing Operations

How global organisations balance a consistent central platform with the speed and nuance local marketing teams need.

Article · 8 min read

Editorial illustration for Designing Scalable Enterprise MarTech Architecture

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

Editorial illustration for The Hidden Cost of Poor Marketing Data Governance

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