Most content APIs hand you a model and leave the hard parts — strategy, footage, captions, music licensing, scheduling — to you. This one hands you the finished artefact. Behind the endpoint is the same pipeline that runs the platform's own customers: a researcher that builds a content calendar from the brand's niche, a producer that renders 9:16 video, and a caption writer that emits a variant for all 11 platforms whether or not the brand has connected them.

The interface is deliberately small. Seventeen tools, one webhook event, one frozen package schema. Nothing expensive happens before payment: creating a brand writes a row and stops, so an agent that provisions a hundred brands and abandons them costs a hundred rows.

Today the server is deliver-only: we produce and hand over, and your agent publishes wherever it likes. Publish-through — approving a post and having us push it to the brand's connected accounts — is not yet exposed.

Quickstart

Every call below works against the live server. Replace the placeholders and run them in order; the whole funnel is nine calls.

  1. 1 Register

    Unauthenticated. The key is returned in the response body and never sent by email — no email we send ever contains a credential.

    curl -X POST https://api.myinfluenceai.com/mcp/register \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "Acme Agent",
        "contact_email": "[email protected]",
        "accepted_terms": true
      }'
  2. 2 Verify the contact address

    The key is live from the moment it is issued, but crippled: until you click the link we email, it reaches exactly three tools. Lost the mail? POST /mcp/register/resend with {"agent": "<slug>"}.

  3. 3 Connect an MCP client

    Streamable HTTP, stateless. Point any MCP-capable client — Claude, an SDK client, the MCP Inspector — at the endpoint with your key as a bearer token.

    {
      "mcpServers": {
        "myinfluenceai": {
          "type": "http",
          "url": "https://api.myinfluenceai.com/mcp",
          "headers": {
            "Authorization": "Bearer mi_live_<key_id>_<secret>"
          }
        }
      }
    }
  4. 4 Find out what you hold

    whoami answers "what am I allowed to do" in one call — principal, scopes, rate limit, verification state and the brands this key can reach. Call it first whenever something is denied and you do not know why. platform_capabilities returns the formats, pillars and caption platforms, so nothing has to be hardcoded.

  5. 5 Analyse a website and pick a plan

    website_analyze reads a public URL and proposes brand settings — niche, audience, tone, keywords. plan_list returns the plans and how much they deliver per day. Both are read-only.

  6. 6 Create the brand

    brand_create writes the brand row, grants it the MCP add-on and records the reseller consent — then stops. No research, no production, no spend. You must pass accepted_terms: true having actually read the linked documents on the brand's behalf.

  7. 7 Send a human to checkout

    checkout_create returns a Stripe URL. An agent cannot pay. A person completes that checkout, Stripe calls us, and the brand activates. Until it does, every brand-scoped tool refuses with entitlement_required.

  8. 8 Configure delivery and prove your verifier works

    delivery_configure takes your webhook URL and returns a signing secret once. Store it before you read the next line. Then delivery_test sends a signed sample so you can confirm your HMAC check accepts a good signature — and, more importantly, rejects a bad one — before any real content depends on it.

  9. 9 Receive the daily package

    From then on, each produced item is POSTed to your endpoint as a package.ready event. If your endpoint was down, package_list and package_get return the same bytes, and delivery_replay re-sends.

Authentication

Keys look like mi_live_<key_id>_<secret> and go in an Authorization: Bearer header. The secret half is shown once, at creation, and is stored only as a peppered hash — we cannot recover it for you, so a lost key is replaced, not retrieved.

Self-serve registration always grants the same thing: the developer tier and the provision preset. It ignores any tier, scope or brand grant you ask for. A self-serve key can never carry publish:write, outreach:send or pii:read — those take an irreversible or privacy-bearing action on someone's behalf, so granting them is a separate, deliberate act.

tools/list is filtered to what your key can actually call. A tool you would only ever be denied is not advertised, because it would waste your context and your calls.

Before your email is verified

Registration is unauthenticated, so a contact address is an unproven claim until someone opens the link we send. An unverified key reaches only the tools listed below; everything else returns email_verification_required. The key is issued before verification and crippled, rather than minted on click, so a live credential never has to travel through an inbox.

ToolWhat it gives you
plan_listList plans
platform_capabilitiesPlatform capabilities
whoamiWho am I

Scopes

A key carries an explicit, literal list of scopes. Presets are a shortcut at minting time only — the stored key always holds the expanded list, so widening a preset later can never widen a key that already exists.

ScopeGrants
brands:readRead brand profiles, schedules and pipeline status.
content:readRead hooks, strategy, calendar and produced content.
content:writeCreate or edit hooks, strategy and captions.
produce:writeSpend the brand’s quota to research or produce content.
analytics:readRead post metrics and pillar performance.
publish:write never self-serveApprove content for publication to connected social accounts.
social:readList connected social accounts (names and platforms only).
billing:writeCreate checkout sessions and change plans.
outreach:readRead cold-outreach campaigns and prospects.
outreach:send never self-serveSend cold outreach on the brand’s behalf.
pii:read never self-serveRead end-customer personal data (emails, names).
delivery:writeConfigure webhook delivery and replay past deliveries.

Presets

  • readonlybrands:read, content:read, analytics:read, social:read
  • deliverbrands:read, content:read, delivery:write
  • provisionbrands:read, content:read, billing:write, delivery:write

Self-serve registration always grants provision, and the scopes marked above are never included in any preset and never implied by another scope.

Tool reference

All 17 tools, generated from the running registry. Brand-scoped tools take a brand slug and require both the MCP add-on and an active subscription on that brand; UUIDs are refused. Writes change state and are audited.

brand_activate

Activate a brand

Idempotent. Normally fired automatically by the Stripe webhook — call it only if payment completed but the brand is still inactive. Safe to retry.

  • Scopes: billing:write
  • Brand-scoped: yes
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

brand_create

Create a brand

Create a brand owned by this agent. All work — publishing profile, strategy, first production — is DEFERRED until payment completes, so this call is cheap and reversible. Requires accepted_terms: you accept as a reseller on the brand’s behalf, and that assertion is recorded against your agent id. Follow with checkout_create.

  • Scopes: billing:write
  • Brand-scoped: no
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand_name": {
      "type": "string",
      "maxLength": 120,
      "description": "Display name. The slug is derived from it unless you pass one."
    },
    "slug": {
      "type": "string",
      "maxLength": 63,
      "pattern": "^[a-z0-9][a-z0-9-]*[a-z0-9]$",
      "description": "Optional explicit slug."
    },
    "website": {
      "type": "string",
      "maxLength": 2000
    },
    "niche": {
      "type": "string",
      "maxLength": 500
    },
    "tone": {
      "type": "string",
      "maxLength": 200
    },
    "plan": {
      "type": "string",
      "maxLength": 40,
      "description": "Intended plan id; used by checkout_create."
    },
    "owner_email": {
      "type": "string",
      "maxLength": 320,
      "description": "Optional. The end brand’s email, for the claim invite and consent record."
    },
    "accepted_terms": {
      "type": "boolean",
      "description": "Must be true. You are asserting authority to accept on the brand’s behalf."
    }
  },
  "required": [
    "brand_name",
    "accepted_terms"
  ],
  "additionalProperties": false
}

brand_get

Get a brand

Full profile for one brand: niche, tone, goal, keywords, pillar ratios, palette, schedule and subscription state.

  • Scopes: brands:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

brand_list

List brands

Every brand this key can reach, with plan and subscription status.

  • Scopes: brands:read
  • Brand-scoped: no
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

checkout_create

Create a checkout session

Return a Stripe Checkout URL for a brand. PASSTHROUGH billing: the end brand pays directly — hand them the URL. On payment our webhook activates the brand automatically.

  • Scopes: billing:write
  • Brand-scoped: yes
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "plan": {
      "type": "string",
      "maxLength": 40,
      "description": "Plan id from plan_list. Defaults to the brand’s current plan."
    },
    "success_url": {
      "type": "string",
      "maxLength": 2000
    },
    "cancel_url": {
      "type": "string",
      "maxLength": 2000
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

delivery_configure

Configure delivery

Set or update where and how this brand’s daily content package is delivered. Returns the signing secret ONCE, on first configuration or when rotate_secret is true — store it, it cannot be read back. telegram_enabled is FALSE by default: an agent-provisioned brand is webhook-only unless you deliberately turn the Telegram approval card back on, in which case BOTH fire.

  • Scopes: delivery:write
  • Brand-scoped: yes
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "webhook_url": {
      "type": "string",
      "maxLength": 2000,
      "description": "Public https:// endpoint. Private and loopback addresses are refused."
    },
    "mode": {
      "type": "string",
      "enum": [
        "deliver_only",
        "publish_through"
      ],
      "description": "deliver_only is the Phase 1 product. publish_through is not yet available."
    },
    "platforms": {
      "type": "array",
      "description": "Caption variants to include. Omit for all 11."
    },
    "telegram_enabled": {
      "type": "boolean",
      "description": "Also send the Telegram approval card. Default false."
    },
    "paused": {
      "type": "boolean",
      "description": "Stop delivering without losing configuration."
    },
    "rotate_secret": {
      "type": "boolean",
      "description": "Mint a new signing secret. Breaks existing verification until you redeploy."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

delivery_replay

Replay a delivery

Re-queue a past delivery. KEEPS THE ORIGINAL idempotency key, so if the first attempt did reach you, your own deduplication recognises the duplicate and drops it.

  • Scopes: delivery:write
  • Brand-scoped: yes
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "delivery_id": {
      "type": "string",
      "maxLength": 40,
      "description": "From package_list."
    }
  },
  "required": [
    "brand",
    "delivery_id"
  ],
  "additionalProperties": false
}

delivery_test

Send a test delivery

Queue a signed sample payload to the configured webhook so you can verify your HMAC check before real content depends on it. Verify X-MI-Signature as t=<unix>,v1=<hex hmac-sha256 of "<t>.<raw body>"> using your signing secret, with 5 minutes of clock tolerance.

  • Scopes: delivery:write
  • Brand-scoped: yes
  • Writes: yes
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

job_get

Get a job

Status of one queued job by id. Scoped to the brand, so a job id from another tenant is simply not found.

  • Scopes: brands:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "job_id": {
      "type": "integer",
      "description": "Job id returned by a mutating tool."
    }
  },
  "required": [
    "brand",
    "job_id"
  ],
  "additionalProperties": false
}

package_get

Get a content package

The full content package for one request_id — media URLs, all caption variants, and the music credit. Returns exactly what was PUSHED if it was delivered, so a pull and a push can never disagree. Works even for content produced before delivery was configured.

  • Scopes: content:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "request_id": {
      "type": "string",
      "maxLength": 120,
      "description": "From a package.ready payload or pipeline_status."
    }
  },
  "required": [
    "brand",
    "request_id"
  ],
  "additionalProperties": false
}

package_list

List deliveries

Delivery attempts for a brand, newest first. THE CATCH-UP TOOL: after an outage, list with status "failed" or "dead" and replay what you missed.

  • Scopes: content:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    },
    "status": {
      "type": "string",
      "enum": [
        "pending",
        "sending",
        "delivered",
        "failed",
        "dead"
      ]
    },
    "since": {
      "type": "string",
      "maxLength": 40,
      "description": "ISO 8601 timestamp."
    },
    "limit": {
      "type": "integer",
      "minimum": 1,
      "maximum": 100,
      "description": "Default 20."
    },
    "cursor": {
      "type": "string",
      "maxLength": 40,
      "description": "next_cursor from a previous page."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

pipeline_status

Pipeline status

Live production state for a brand: queued and recent jobs, video counts by status, unused hooks (the real runway figure), and the five most recent items.

  • Scopes: brands:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

plan_list

List plans

Available subscription plans with price and the DAILY DELIVERABLE VOLUME each one buys (videos_per_day, carousels_per_day, posts_per_day) — the numbers that decide how many packages a webhook will receive.

  • Scopes: any valid key
  • Brand-scoped: no
  • Writes: no (read-only)
  • Works before email verification: yes
Input schema
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

platform_capabilities

Platform capabilities

What this platform can produce: content formats, content pillars, the caption platforms generated for every post, video statuses, and the available scopes. Read this once at integration time instead of hardcoding enums.

  • Scopes: any valid key
  • Brand-scoped: no
  • Writes: no (read-only)
  • Works before email verification: yes
Input schema
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

subscription_status

Subscription status

Plan, status, renewal date and add-ons for a brand. Check this before assuming packages will keep arriving — delivery stops when a subscription is cancelled.

  • Scopes: brands:read
  • Brand-scoped: yes
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "brand": {
      "type": "string",
      "description": "Brand slug."
    }
  },
  "required": [
    "brand"
  ],
  "additionalProperties": false
}

website_analyze

Analyze a website

Read a public website and return suggested brand settings (name, industry, tone, goal, audience, keywords) plus scraped brand colours. Step one of onboarding: feed the result into brand_create. Results are cached, so re-analysing the same URL is free.

  • Scopes: any valid key
  • Brand-scoped: no
  • Writes: no (read-only)
Input schema
{
  "type": "object",
  "properties": {
    "website": {
      "type": "string",
      "maxLength": 2000,
      "description": "Public https:// URL of the brand’s site."
    }
  },
  "required": [
    "website"
  ],
  "additionalProperties": false
}

whoami

Who am I

Identify the calling key: principal, scopes, rate limit, and the brands it can reach. Call this first when debugging a permission problem — it answers "what am I allowed to do" without guessing.

  • Scopes: any valid key
  • Brand-scoped: no
  • Writes: no (read-only)
  • Works before email verification: yes
Input schema
{
  "type": "object",
  "properties": {},
  "additionalProperties": false
}

Errors

A tool failure comes back as a normal result marked isError, not a protocol fault, so your agent can read it and correct itself. Every error carries a code, a message and a next_step written for a program rather than a person.

CodeMeaningNext step
auth_requiredMissing or malformed Authorization header.Send `Authorization: Bearer mi_live_<key_id>_<secret>`. No key yet? POST /mcp/register, or read https://agents.myinfluenceai.com/developers/mcp/.
key_invalidThat API key is not valid.Check the key was copied whole. Secrets are shown once at creation and cannot be recovered. See https://agents.myinfluenceai.com/developers/mcp/.
key_revokedThat API key has been revoked.Mint a new key from the developer console.
key_expiredThat API key has expired.Mint a new key from the developer console.
agent_disabledThis agent account is disabled.Contact [email protected].
email_verification_requiredConfirm your contact email before using this tool.Open the link we emailed when you registered. Lost it? POST /mcp/register/resend {"agent":"<slug>"}. See https://agents.myinfluenceai.com/developers/mcp/.
scope_requiredThis key does not carry the scope required for that call.Mint a key with the scope listed in `required_scope`.
brand_not_foundNo brand with that identifier is available to this key.Call `brand_list` to see the brands this key can reach.
brand_not_authorizedNo brand with that identifier is available to this key.Call `brand_list` to see the brands this key can reach.
entitlement_requiredThe MCP add-on is not enabled for this brand.Complete checkout for this brand, or contact support to enable the add-on.
rate_limitedToo many calls. Slow down.Wait `retry_after_seconds` and retry. Back off exponentially on repeats.
quota_exceededThis brand has reached its plan limit for that action.Upgrade the plan with `plan_change`, or wait for the limit window to roll.
quota_unavailableUsage limits are temporarily unavailable, so the call was refused rather than allowed.Retry in a minute. This is a transient server condition, not a limit you hit.
invalid_inputOne or more arguments were rejected.Read `details` for the offending field, fix it, and retry.
unsafe_urlThat URL points somewhere we will not fetch or deliver to.Supply a public https:// URL. Private, loopback and link-local addresses are refused.
consent_requiredTerms must be accepted before a brand can be created.Read the documents in `legal_urls`, then retry with `accepted_terms: true`.
not_foundNo such record.Check the identifier. List endpoints return the ids this key can use.
conflictThat action conflicts with the current state.Read `state` and retry only if it makes sense to.
not_configuredDelivery is not configured for this brand.Call `delivery_configure` with a webhook_url first.
internal_errorSomething went wrong on our side.Retry once. If it persists, contact [email protected] with the `request_id`.

brand_not_found and brand_not_authorized return the same message on purpose: a key must not be able to discover which brands exist by probing.

Webhook delivery

Every delivery carries an X-MI-Signature header in the form t=<unix>,v1=<hex>, where v1 is an HMAC-SHA256 of the literal string "<t>.<raw body>" under your signing secret. The timestamp is inside the signed value, so a captured body cannot be replayed with a fresh timestamp.

Three things a correct verifier does, and a plausible one does not: hash the raw request body (not a re-serialised object — key order changes the bytes), compare with a constant-time comparison, and reject anything outside a ±5 minute window. A verifier that skips the timestamp check silently accepts forged calls forever.

We accept any 2xx as delivered. Anything else retries on a fixed schedule and then stops. Respond fast and process asynchronously: a slow endpoint is indistinguishable from a broken one.

Headers

POST /your/webhook HTTP/1.1
Content-Type: application/json
X-MI-Event: package.ready
X-MI-Signature: t=1756512000,v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

Verify it — Node

import crypto from 'crypto';

// body must be the RAW request bytes, not a re-serialised object:
// JSON.stringify(JSON.parse(body)) can reorder keys and change the hash.
export function verify(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(
    String(header || '').split(',').map(kv => kv.split('=').map(s => s.trim())),
  );
  const t = parseInt(parts.t, 10);
  if (!Number.isFinite(t)) return false;

  // Without this, a captured body replays forever.
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSeconds) return false;

  const expected = crypto.createHmac('sha256', secret)
    .update(`${t}.${rawBody}`, 'utf8').digest('hex');
  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(String(parts.v1 || ''), 'hex');
  if (a.length !== b.length || a.length === 0) return false;
  return crypto.timingSafeEqual(a, b);
}

Verify it — Python

import hashlib, hmac, time

def verify(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool:
    parts = dict(
        kv.strip().split("=", 1) for kv in (header or "").split(",") if "=" in kv
    )
    try:
        t = int(parts["t"])
    except (KeyError, ValueError):
        return False

    # Without this, a captured body replays forever.
    if abs(int(time.time()) - t) > tolerance_seconds:
        return False

    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Retries

A non-2xx response (or a timeout) is retried 6 times in total, then marked dead:

1m → 5m → 30m → 2h → 6h → 24h → dead

That is roughly 33 hours of tolerance for an endpoint that is down. A dead delivery is not lost — package_list still returns it and delivery_replay re-sends it.

The daily package

One frozen shape, assembled once and used identically by the webhook push and the package_get pull — so a consumer catching up after an outage gets exactly what it normally receives.

The one thing consumers get wrong: media.kind is "video" or "carousel". For a carousel, video_url is null and the ordered slide URLs are in media.slides. Branch on kind; a downloader that assumes a single URL breaks on the first carousel day.

Music credit is not optional. When music.credit_required is true, the ready-made music.credit_line must appear in the caption you publish — not in file metadata. Instagram, TikTok and YouTube all re-encode uploads and strip container tags, so metadata attribution is not attribution. Nearly every track in the library is CC BY.

Media URLs expire. expires_at is on every package: fetch and store the bytes, do not hotlink.

Captions

A variant is generated for all 11 platforms on every post, regardless of what the brand has connected, so a deliver-only consumer can publish anywhere: linkedin, tiktok, x, reddit, instagram, facebook, youtube, bluesky, pinterest, threads, mastodon. Narrow the set with the platforms argument to delivery_configure. A platform with no stored caption is absent rather than null, so "not generated" is distinguishable from "generated empty".

Shape

A real payload, assembled by the same code that builds yours. This one was configured for three platforms so the sample stays readable; by default captions holds all 11.

{
  "package_version": "1",
  "brand": "acme-dental",
  "request_id": "req_9f4c21",
  "date": "2026-08-30",
  "format": "talking_points",
  "pillar": "educational",
  "hook": "Three things every new patient asks — answered in 30 seconds.",
  "media": {
    "kind": "video",
    "video_url": "https://cdn.my-influence-ai.com/v/req_9f4c21.mp4",
    "slides": null,
    "thumbnail_url": "https://cdn.my-influence-ai.com/v/req_9f4c21.jpg",
    "duration_seconds": 28
  },
  "captions": {
    "linkedin": {
      "text": "Patients ask the same three questions before every first appointment. Here is how we answer them.",
      "hashtags": []
    },
    "tiktok": {
      "text": "The 3 questions we get every single day",
      "hashtags": [
        "#dentaltok",
        "#patienttips"
      ]
    },
    "instagram": {
      "text": "Three things every new patient asks. Save this for later 🦷",
      "hashtags": [
        "#dentalcare",
        "#patienttips"
      ]
    }
  },
  "default_caption": "Three things every new patient asks. Save this for later.",
  "music": {
    "title": "Sunlit",
    "artist": "Kirkoid",
    "license": "CC BY 3.0",
    "license_url": "https://creativecommons.org/licenses/by/3.0/",
    "credit_required": true,
    "credit_line": "🎵 Music: \"Sunlit\" by Kirkoid (ccMixter, CC BY 3.0)"
  },
  "expires_at": "2026-09-29T06:00:00.000Z"
}

Rate limits and quotas

Rate limits are per key and reported by whoami; a rate_limited error carries retry_after_seconds. Back off exponentially — a retry loop against a limit is the fastest way to get a key disabled.

Plan quotas are enforced per brand and fail closed: if we cannot determine your remaining quota, the call is refused rather than allowed. That is deliberate for an automated caller, and it is why quota_unavailable is a distinct code from quota_exceeded — the first is transient and worth retrying, the second is not.

Every response carries a request_id. Quote it when you contact support; it is how we find your call.

Versioning

The tool schemas and the package schema are frozen contracts, checked against a fixture in CI — an accidental change to either fails our build, not yours.

Additive fields do not bump a version. A breaking change mounts a new path (/mcp/v2); /mcp never silently becomes v2. Deprecations run 90 days, are flagged on the affected responses, and are emailed to the owners of affected keys.

Machine-readable discovery lives at GET https://api.myinfluenceai.com/mcp/info and https://agents.myinfluenceai.com/.well-known/mcp/server.json; both are unauthenticated and always describe the deployed server.

Frequently asked questions

Does Agents by MyInfluence AI have an MCP server?

Yes. It is hosted at https://api.myinfluenceai.com/mcp, speaks Streamable HTTP, and authenticates with a bearer API key. Registration is self-serve at POST /mcp/register and returns a key immediately. There is nothing to install and no local process to run.

How does an AI agent subscribe programmatically?

Register for a key, verify the contact email, then call website_analyze, plan_list and brand_create to provision a brand, and checkout_create to get a Stripe URL. A human completes that checkout — an agent cannot pay on its own. Once Stripe confirms, the brand activates and daily delivery begins.

What is in a daily content package?

A rendered 9:16 short-form video (or an ordered set of carousel slides), a thumbnail, the hook and content pillar, a caption variant for each of 11 platforms, a default caption, and a music block with a ready-made credit line that must be published in the caption. Media URLs carry an explicit expiry.

How do I verify the webhook signature?

Compute an HMAC-SHA256 over the string "<timestamp>.<raw request body>" using your signing secret, and compare it in constant time to the v1 value in the X-MI-Signature header. Reject any request whose t is more than five minutes from now. Prove it works with delivery_test before real content depends on it.

What does it cost?

The same plans humans buy: from $149/month. The agent-facing interface itself carries no separate fee — you pay per brand, per plan, and the brand's human owner completes the checkout.

Can an agent publish to social accounts through the API?

Not today. The server is deliver-only: it hands over the finished content and your agent publishes it wherever it likes. Publish-through exists in the product for human-approved posts, but it is not exposed to agents, and no self-serve key can be granted the publish:write scope.

Can an agent create brands for other people's businesses?

Yes, and that is the intended reseller pattern — but brand_create records who accepted the terms and on whose behalf, and the human who pays is the one who owns the account. Creating brands without a real customer behind them just creates rows that never activate.