MMetadata /docs
Docs/Tools/create_update_offer

create_update_offer

Create or update Lead Gen or Landing Page offers in Metadata platform.

Destructive campaigns

Deletes, archives, launches or moves budget. Do not give an agent this one without a human gate.

What it does

Create or update Lead Gen or Landing Page offers in Metadata platform.
    Returns direct URL to access the offer in the platform.

    OPERATION MODE:
    - CREATE: When 'id' field is NOT included in data
    - UPDATE: When 'id' field IS included in data

    OFFER TYPES:
    1. Lead Gen - Native forms rendered inside LinkedIn, Facebook, or Instagram. User never leaves the platform.
    2. Landing Page - External page hosted on the advertiser's website. Works on EVERY channel (LinkedIn, Facebook, Instagram, Google Ads, Reddit, Microsoft Ads).

    ⚠️ LEAD GEN ON LINKEDIN / META CAN BE DONE TWO WAYS — pick the right one for the user's intent:
    - Native Lead Gen form (offer_type='Lead Gen', data.channel=<channel>, goal=CPL): user fills the form INSIDE LinkedIn/Meta. Lower friction, higher conversion, but leads stay in the ad platform until synced. Channel-locked — see below.
    - Landing Page lead capture (offer_type='Landing Page', goal=CPL, landingPageUrl + thankYouUrl + EMAIL field): user clicks through to the advertiser's own landing page and submits the form there. Choose this when the user wants branded landing pages, tracking pixels, or server-side enrichment. Channel-agnostic — one offer works on every channel.
    Both flows count as Lead Gen (goal=CPL). Default to native Lead Gen for LinkedIn/Meta unless the user explicitly wants their own landing page.

    ⚠️ CHANNEL-LOCK ON NATIVE LEAD GEN OFFERS (read this BEFORE planning your offer batch) ⚠️
    The data.channel value you set on a Lead Gen offer PERMANENTLY locks the returned offer_id to that one channel. The same offer_id CANNOT be used in another channel's slot inside create_campaign / add_and_edit_campaign_elements — those tools accept the mismatch SILENTLY and produce a broken campaign that returns a successful response but cannot serve. Landing Page offers are NOT channel-locked and can be safely reused across channels.

    What this means for planning:
    - Want a native Lead Gen "Demo" concept on BOTH LinkedIn AND Facebook? You MUST call create_update_offer TWICE for that one concept — once with data.channel='LINKEDIN', once with data.channel='FACEBOOK'. Track both offer_ids and assign each to the matching channel slot.
    - Three concepts × two native Lead Gen channels = SIX create_update_offer calls. That is correct, not over-creation. The "2-3 offers" guidance refers to distinct CONCEPTS, not call count.
    - Want a single offer that works on every channel including Google? Use offer_type='Landing Page' (omit data.channel — Landing Pages have no channel field and are channel-agnostic). Google Ads ONLY accepts Landing Page offers.
    - Want Lead Gen on LinkedIn using the advertiser's OWN landing page instead of LinkedIn's native form? Use offer_type='Landing Page' with goal='CPL' — then assign the returned offer_id to linkedin.offers[]. This is fully supported and a single LP offer_id can serve LinkedIn, Meta, Google simultaneously.

    Right after each successful create_update_offer call, write down (offer_id, channel, concept) so you can correctly map them in the create_campaign call later. If you lose track, call get_offer(name=<offer_name>) to look up the offer and read back the channel field before submitting a campaign.

    REQUIRED PARAMETERS:
    - offer_type: "Lead Gen" or "Landing Page"
    - data: Offer configuration object
      - id: (optional) Include only when updating an existing offer

    IMPORTANT LIMITS:
    - offerName: MUST be unique AND under 50 characters. If name + timestamp exceeds 49 chars, shorten the base name.
      ⚠️ ALLOWED CHARACTERS: letters (a-z, A-Z), numbers (0-9), spaces, and these punctuation marks ONLY:
        _ . ? , : ' / + - = & ; % $ # [ ] | ( )
      Any other character (em-dashes, smart quotes, emoji, non-ASCII letters, ©, ™, !, @, *, {, }, <, >, ", \, ^, ~, `) will be REJECTED by the platform on Landing Page offers and stripped by this tool before the request is sent. Stick to ASCII letters/digits/space and the punctuation set above.
    - offerHeader: max 60 chars
    - offerDetails: max 160 chars
    - thankYouMessage: max 60 chars (LinkedIn)
    - privacyText: max 70 chars (Facebook/Instagram)
    - Fields should always be included

    CHANNEL-SPECIFIC REQUIREMENTS:

    LinkedIn Lead Gen (offer_type="Lead Gen", channel="LINKEDIN"):
    - REQUIRED: channel, offerName, offerHeader, thankYouMessage, thankYouUrl, privacyUrl, privacyText, fields
    - CTA (linkedinCTA): REQUIRED by the backend. Defaults to DOWNLOAD_NOW when omitted. Must be one of
      LEARN_MORE | VIEW_NOW | DOWNLOAD_NOW | TRY_NOW | VISIT_COMPANY_WEBSITE. Any other value silently falls
      back to VISIT_COMPANY_WEBSITE on LinkedIn's side — always set linkedinCTA explicitly for predictable behavior.
    - offerDetails: OPTIONAL but STRONGLY RECOMMENDED. Populates the description shown above the form on LinkedIn
      and improves conversion. Not mandatory on LinkedIn (unlike FB/IG) but there's no reason to skip it.

    Facebook/Instagram Lead Gen (offer_type="Lead Gen", channel="FACEBOOK" or "INSTAGRAM"):
     - REQUIRED: channel, offerName, offerHeader, offerDetails, thankYouHeadline,
       thankYouMessage, thankYouDescription, thankYouPageButtonText, websiteUrl, privacyUrl
     - Meta's thank-you screen has THREE distinct text blocks — populate all three for a complete
       UX:
         • thankYouHeadline → title
         • thankYouMessage  → short_message (small text under the headline)
         • thankYouDescription → body (longer copy). Falls back to thankYouMessage if omitted,
           but supplying both is strongly preferred.
     - thankYouPageButtonType (enum, default VIEW_WEBSITE). Cross-field rules:
         • VIEW_WEBSITE → websiteUrl (or thankYouUrl fallback) is required
         • VIEW_ON_FACEBOOK → thankYouAssetId (library creative ID) is required; without it the
           backend validation will reject the offer.
     - CRITICAL: offerDetails is MANDATORY when offerHeader is provided. If the user doesn't
       specify offer details, generate a brief description based on the offer context.
       Omitting offerDetails when offerHeader is set will cause an API validation error.
     - The backend HTTP-probes followUpActionUrl (derived from websiteUrl / thankYouUrl) during
       validation, so the URL MUST resolve to a live page — not a placeholder.
     - linkedinCTA is ignored on FB/IG.

    Landing Page (offer_type="Landing Page"):
    - REQUIRED: offerName, landingPageUrl, thankYouUrl, fields
    - CRITICAL: fields MUST include an EMAIL entry SPECIFICALLY. The platform's
      `INVALID_LANDINGPAGE_FORM: Email mapping is required` check is satisfied
      ONLY by EMAIL — BUSINESS_EMAIL alone does NOT satisfy it and the offer is
      rejected. Without an email capture, the form cannot deliver a lead. If the
      user does not mention form fields at all, default to:
        fields: [{"name": "email", "field": "EMAIL"}, {"name": "firstName", "field": "FIRST_NAME"}, {"name": "lastName", "field": "LAST_NAME"}, {"name": "company", "field": "COMPANY"}]
      BUSINESS_EMAIL may be added as an EXTRA field when the user explicitly wants
      to filter out personal email domains, but it can NEVER replace EMAIL — keep
      EMAIL in the form. (The server auto-corrects a lone BUSINESS_EMAIL to EMAIL.)

    AVAILABLE FIELD TYPES BY OFFER TYPE:

    For Lead Gen offers, the supported field types DIFFER BY CHANNEL — and the enum
    SPELLINGS differ too (LinkedIn PHONE_NUMBER/ZIP_CODE vs Meta PHONE/ZIP). Mixing
    them up is what causes the "Cannot deserialize Convo LeadForm JSON" attach failure.
    PREFER the channel's DEFAULT-VISIBLE fields (marked [default] below) — they cover
    the standard B2B form; only reach for the rest when the brief specifically needs them.

    LinkedIn Lead Gen (channel="LINKEDIN") — these 22 types ONLY (source of truth:
    the platform's LinkedIn Lead Gen Form dialog):
    [default] "EMAIL", "WORK_EMAIL", "FIRST_NAME", "LAST_NAME", "COMPANY_NAME", "LINKEDIN_PROFILE_LINK"
    [more]    "PHONE_NUMBER", "WORK_PHONE_NUMBER", "COUNTRY", "CITY", "STATE", "ZIP_CODE", "JOB_TITLE",
              "JOB_FUNCTION", "SENIORITY", "INDUSTRY", "COMPANY_SIZE", "DEGREE", "SCHOOL",
              "FIELD_OF_STUDY", "GRADUATION_DATE", "START_DATE"
    LinkedIn has NO field for REVENUE, EMPLOYEES (use COMPANY_SIZE), GENDER, DOB, MESSENGER,
    STREET_ADDRESS, etc. LinkedIn caps a form at 12 fields.

    Facebook/Instagram Lead Gen (channel="FACEBOOK"/"INSTAGRAM") — these 19 types ONLY
    (source of truth: the platform's FB/IG Lead Gen Form dialog):
    [default] "EMAIL", "WORK_EMAIL", "FIRST_NAME", "LAST_NAME", "COMPANY_NAME", "COUNTRY"
    [more]    "PHONE", "WORK_PHONE_NUMBER", "STREET_ADDRESS", "CITY", "STATE", "PROVINCE",
              "POST_CODE", "ZIP", "GENDER", "MARITIAL_STATUS", "RELATIONSHIP_STATUS",
              "MILITARY_STATUS", "JOB_TITLE"
    Meta has NO field for REVENUE, EMPLOYEES, COMPANY_SIZE, SENIORITY, INDUSTRY, JOB_FUNCTION,
    or the education fields (those are LinkedIn-only). "MARITIAL_STATUS" is the literal enum
    (the typo is load-bearing — do not "fix" it). No field-count cap on Meta.
    IMPORTANT: Do NOT use "BUSINESS_EMAIL" for Lead Gen - use "WORK_EMAIL" instead.
    IMPORTANT: Do NOT use "COMPANY" for Lead Gen - use "COMPANY_NAME" instead.
    IMPORTANT: Do NOT use "PHONE" for LinkedIn Lead Gen - use "PHONE_NUMBER" instead (auto-corrected if provided).
    IMPORTANT: Do NOT use "PHONE_NUMBER" or "ZIP_CODE" for Facebook/Instagram - use "PHONE" / "ZIP" instead (auto-corrected if provided).
    IMPORTANT: Custom / free-text questions are NOT supported. Every fields[].field MUST be one of the
    predefined types for the offer's channel. Do NOT pass "CUSTOM", and do NOT invent a field for a concept
    the brief names but that has no predefined type (e.g. "monthly transaction volume", "budget", "ad spend",
    "use case", "team size"). Neither channel has a revenue/spend/budget field — DROP such a field. For
    headcount: LinkedIn uses COMPANY_SIZE; Meta has no equivalent (drop). This server auto-normalizes near-miss
    spellings (LinkedIn: EMPLOYEES->COMPANY_SIZE, ZIP->ZIP_CODE, POST_CODE->ZIP_CODE, PROVINCE->STATE; Meta:
    PHONE_NUMBER->PHONE, ZIP_CODE->ZIP) and then DROPS any field the offer's channel still can't render
    (logged), so the offer ships with its valid fields instead of failing at campaign-attach — but pass
    channel-correct fields up front so nothing the user asked for is silently dropped.

    For Landing Page offers (LandingPageFieldType):
    "EMAIL", "BUSINESS_EMAIL", "FIRST_NAME", "LAST_NAME", "COUNTRY", "PHONE", "CITY", "STATE", "ZIP",
    "JOB_TITLE", "SENIORITY", "COMPANY", "COMPANY_DOMAIN", "COMPANY_PHONE", "EMPLOYEES", "INDUSTRY",
    "REVENUE", "LINKEDIN_URL", "UTM_SOURCE", "UTM_MEDIUM", "UTM_TERM", "UTM_CONTENT"
    IMPORTANT: Do NOT use "COMPANY_NAME" for Landing Pages - use "COMPANY" instead.
    IMPORTANT: Do NOT use "WORK_PHONE_NUMBER" for Landing Pages - use "PHONE" instead.

    EXAMPLES:

    CREATE LinkedIn Offer:
    create_or_update_offer("Lead Gen", {
        "channel": "LINKEDIN",
        "status": "COMPLETED",
        "offerName": "Free AI Marketing Audit",
        "offerHeader": "Unlock Growth with AI",
        "offerDetails": "A 30-minute audit of your current AI stack with tailored recommendations.",
        "linkedinCTA": "DOWNLOAD_NOW",
        "thankYouMessage": "We'll contact you soon!",
        "thankYouUrl": "https://example.com/thanks",
        "privacyUrl": "https://example.com/privacy",
        "privacyText": "We respect your privacy",
        "fields": [
            {"name": "email", "field": "EMAIL"},
            {"name": "firstName", "field": "FIRST_NAME"},
        ]
    })

    UPDATE LinkedIn Offer:
    create_or_update_offer("Lead Gen", {
        "id": 62148,
        "channel": "LINKEDIN",
        "status": "COMPLETED",
        "offerName": "Free AI Marketing Audit 2025",
        "offerHeader": "Unlock Growth with AI",
        "offerDetails": "A 30-minute audit of your current AI stack with tailored recommendations.",
        "linkedinCTA": "LEARN_MORE",
        "thankYouMessage": "We'll contact you soon!",
        "thankYouUrl": "https://example.com/thanks",
        "privacyUrl": "https://example.com/privacy",
        "privacyText": "We respect your privacy",
        "fields": [
            {"name": "email", "field": "EMAIL"},
            {"name": "firstName", "field": "FIRST_NAME"},
        ]
    })

    CREATE Facebook/Instagram Offer:
    create_or_update_offer("Lead Gen", {
        "channel": "FACEBOOK",
        "status": "COMPLETED",
        "offerName": "Free Consultation",
        "offerHeader": "Get Expert Advice",
        "offerDetails": "Book a 30-minute consultation with our senior strategists.",
        "thankYouHeadline": "Thank You!",
        "thankYouMessage": "We'll be in touch soon",
        "thankYouDescription": "A member of our team will email you within one business day to schedule your session.",
        "thankYouPageButtonText": "Visit Website",
        "thankYouPageButtonType": "VIEW_WEBSITE",
        "websiteUrl": "https://example.com",
        "privacyUrl": "https://example.com/privacy",
        "privacyText": "Privacy Policy",
        "fields": [
            {"name": "email", "field": "EMAIL"},
            {"name": "firstName", "field": "FIRST_NAME"},
        ]
    })

    CREATE Landing Page Offer:
    create_or_update_offer("Landing Page", {
        "offerName": "Free Demo",
        "landingPageUrl": "https://example.com/landing",
        "thankYouUrl": "https://example.com/thanks",
        "fields": [
            {"name": "email", "field": "EMAIL"},
            {"name": "firstName", "field": "FIRST_NAME"},
        ]
    })

    RESPONSE: Success returns offer_id, offer_url, and configuration details.
    VALIDATION: Checks URLs start with https://, character limits, required fields per channel.

Arguments

ArgumentTypeNotes
offer_type string required Type of offer to create
one of: Lead Gen, Landing Page
data object required Offer configuration data. Required fields depend on offer_type and channel - see description for details.

Request

curl
curl -s -X POST https://mcp-server.metadata.io/mcp \
  -H "Authorization: $METADATA_PAT" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_update_offer","arguments":{"offer_type":"Lead Gen","data":{}}}}'

Response

No recorded response. This tool can delete, archive, launch or move budget, so it is never executed to build these docs. The request above is still the exact shape to send. Nothing on this page is invented.

Other campaigns tools:

All 160 tools · machine-readable index