Skip to main content
Glama

Metadata MCP Connector

Create or Update Offer

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

WARNING: 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.

WARNING: 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.
  WARNING: 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.

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).
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: fields[] takes ONLY the predefined types for the offer's channel; custom questions go in
customFieldSelections (see CUSTOM QUESTIONS below). 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 type, so DROP such a field from
fields[] (an existing custom question may still carry it, see CUSTOM QUESTIONS). 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.

CUSTOM QUESTIONS (every Lead Gen channel):
A Lead Gen form can also carry the account's own custom questions (a gift card terms line, "Company
Website", a monthly ad spend question). They are the account's custom fields, so they go in
data.customFieldSelections, never in fields[], each entry as get_offer returns it in
content.customFieldSelections: {"id": <custom field id>, "text": "<question as the form shows it>", "language": "EN_US"}.
- Duplicating or reworking an offer: read it with get_offer(offer_id=<id>) and copy
  content.customFieldSelections into data.customFieldSelections, so the copy keeps every custom
  question, terms included. Never tell the user they must add them back by hand.
- Keep each entry's id and language. Change its text only when the user asks to reword that question.
  A disclaimer's body and a multiple choice question's options come from the custom field itself, not
  from text: if one contradicts the new messaging (an old amount, say), tell the user it is edited in
  the platform.
- The list replaces the form's whole set. Updating (data.id set): omit customFieldSelections to keep
  the offer's current custom questions; pass [] only when the user asks to remove them all.
- This tool cannot create a custom field: reuse ids this account's offers already carry, and a new
  custom question has to be created in the platform first. Ids from another account are refused.
- If the original form has no EMAIL or WORK_EMAIL field, do not add one: its email may come from a
  custom question, and the platform refuses both together.
- Every Lead Gen form takes at most 12 questions, fields and custom questions together.

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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dataYesOffer configuration data. Required fields depend on offer_type and channel - see description for details.
offer_typeYesType of offer to create

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • addedInput schema / properties / data / properties / customFieldSelections
      Added value: +{
      +  "description": "Lead Gen ONLY: the form's custom questions (the account's custom fields), each entry as get_offer returns it in content.customFieldSelections. Copy them when duplicating an offer. The list replaces the form's whole set: on update, omit it to keep the offer's current custom questions, and pass [] only to remove them all.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "id": {
      +        "description": "Id of the account custom field, from get_offer.",
      +        "type": "integer"
      +      },
      +      "language": {
      +        "description": "Locale of the text, as get_offer shows it (EN_US for most forms).",
      +        "type": "string"
      +      },
      +      "text": {
      +        "description": "The question as this form shows it, from get_offer.",
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "text",
      +      "language"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / properties / data / properties / fields / description
      Previous value: -"Field mappings. REQUIRED for all offer types and MUST contain at least one email-type entry — for Lead Gen use 'EMAIL' or 'WORK_EMAIL'; for Landing Page use 'EMAIL' (BUSINESS_EMAIL alone is NOT accepted — it can only be an EXTRA field alongside EMAIL). WARNING: Landing Page offers without an EMAIL mapping are rejected by the platform with `INVALID_LANDINGPAGE_FORM: Email mapping is required` — there is no valid lead-capture form without it. IMPORTANT: Lead Gen and Landing Page offer types accept DIFFERENT field values - see tool description for the full list per type. Do NOT use BUSINESS_EMAIL for Lead Gen (use WORK_EMAIL). Do NOT use COMPANY for Lead Gen (use COMPANY_NAME). Do NOT use COMPANY_NAME or WORK_PHONE_NUMBER for Landing Pages (use COMPANY and PHONE)."New value: +"Field mappings. REQUIRED for all offer types and MUST contain at least one email-type entry — for Lead Gen use 'EMAIL' or 'WORK_EMAIL'; for Landing Page use 'EMAIL' (BUSINESS_EMAIL alone is NOT accepted — it can only be an EXTRA field alongside EMAIL). WARNING: Landing Page offers without an EMAIL mapping are rejected by the platform with `INVALID_LANDINGPAGE_FORM: Email mapping is required` — there is no valid lead-capture form without it. IMPORTANT: Lead Gen and Landing Page offer types accept DIFFERENT field values - see tool description for the full list per type. Do NOT use BUSINESS_EMAIL for Lead Gen (use WORK_EMAIL). Do NOT use COMPANY for Lead Gen (use COMPANY_NAME). Do NOT use COMPANY_NAME or WORK_PHONE_NUMBER for Landing Pages (use COMPANY and PHONE). A Lead Gen copy of a form that has no EMAIL or WORK_EMAIL field adds none (see CUSTOM QUESTIONS)."
  2. First observed

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations only flag destructive/openWorld/not-readOnly, but the description adds far more: permanent channel-lock on native Lead Gen offer_ids, silent mismatches in downstream campaign tools, backend HTTP-probing of URLs, silent enum fallbacks (linkedinCTA), auto-stripping of illegal characters, and server-side auto-normalization of field spellings. These are concrete behavioral traits well beyond the structured data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is very long, but the complexity justifies much of it and the most critical material (operation mode, the two warnings) is front-loaded. Minor redundancy exists where character limits and allowed-character sets are repeated from the schema, and the wall of channel rules could be tighter, but the structure is navigable with headers.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with nested objects, no output schema, and channel-divergent requirements, the description covers required fields per channel, field-type enums, custom-question handling, limits, and even the response shape ('offer_id, offer_url, and configuration details'). Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, yet the description adds substantial meaning the schema cannot: channel-specific required field sets, the 22 LinkedIn vs 19 Meta enum spellings, cross-field rules (offerDetails mandatory when offerHeader is set on FB/IG; VIEW_ON_FACEBOOK requires thankYouAssetId), and the EMAIL vs BUSINESS_EMAIL landing-page constraint. This is far above the baseline-3 expectation for a fully-covered schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ('Create or update Lead Gen or Landing Page offers') and names the platform. It immediately distinguishes the two offer types and clarifies the create-vs-update mode via the presence/absence of 'id', so an agent can place it against get_offer, find_offer_url, and the campaign tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use each path: native Lead Gen vs Landing Page lead capture, when to pick Landing Page for branded pages, and that Google Ads only accepts Landing Page offers. It names the exact number of calls needed for multi-channel native Lead Gen and directs the agent to get_offer for lookup, leaving nothing to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources