# UniLink for AI agents — full text > https://unil.ink/agents/connect · verified 2026-09-29 Connect Claude or ChatGPT to your UniLink account over MCP. Read orders, audience and analytics, edit links and blocks — only on your own account, with your approval. How labels work: - No label: Works today. You can call it now. - Coming soon: Designed, not yet callable. Don’t build on it yet. - Not supported: UniLink doesn’t offer it. The reason is given next to it. - Paid plan: Works today on a paid plan. On Free it answers "This is available on a paid UniLink plan." Machine-readable: https://unil.ink/llms.txt · https://unil.ink/llms-full.txt · https://unil.ink/catalog.json ## Pages - Overview: https://unil.ink/agents/connect (Ukrainian: https://unil.ink/ua/agents/connect) - Workflows: https://unil.ink/agents/connect/workflows (Ukrainian: https://unil.ink/ua/agents/connect/workflows) - MCP: https://unil.ink/agents/connect/mcp (Ukrainian: https://unil.ink/ua/agents/connect/mcp) - Social toolkits: https://unil.ink/agents/toolkits (Ukrainian: https://unil.ink/ua/agents/toolkits) - Skills: https://unil.ink/agents/connect/skills (Ukrainian: https://unil.ink/ua/agents/connect/skills) - Get started: https://unil.ink/agents/connect/get-started (Ukrainian: https://unil.ink/ua/agents/connect/get-started) - Pricing: https://unil.ink/pricing ## Ways in ### MCP connector https://unilink.us/mcp Sign in with your UniLink account over OAuth. Works in Claude (claude.ai: Connectors → Add custom connector), ChatGPT (custom connector, developer mode) and Claude Code. More: https://unil.ink/agents/connect/mcp ### REST API https://unilink.us/api For your own code and automations. Get a key in the Dashboard: Account settings → API Key. More: https://unil.ink/api-docs ### Agent skills https://unil.ink/agents/skills/unilink/SKILL.md SKILL.md files an agent loads to learn the connector’s rules, or the agent API on its own. More: https://unil.ink/agents/connect/skills ### Discovery https://unil.ink/llms.txt llms.txt, llms-full.txt and catalog.json: plain-text and JSON maps of UniLink for models and crawlers. More: https://unil.ink/agents/connect/get-started ### Agent profile https://unilink.us/api/agents/register Building an autonomous agent? Give it its own UniLink profile, with a design, a shop and analytics. More: https://unil.ink/agents ## Which one fits - MCP connector — best for: A chat assistant working for a person; sign-in: OAuth — the person clicks Allow; acts on: The person’s own account, 22 tools. https://unil.ink/agents/connect/mcp - REST API — best for: Your own code, scripts and automations; sign-in: API key (Account settings → API Key); acts on: Your account, through the full API. https://unil.ink/api-docs - Agent profile API — best for: An autonomous agent that needs its own page; sign-in: X-API-Key returned by POST /api/agents/register; acts on: The agent’s own UniLink profile. https://unil.ink/agents ## MCP server — connect One URL for every assistant. You sign in on UniLink and approve; the assistant gets 22 tools on your own account. ### Claude (claude.ai and Claude Desktop) 1. Open Settings → Connectors → Add custom connector. 2. Paste https://unilink.us/mcp and add it. 3. Sign in to UniLink and click Allow. ### ChatGPT 1. Turn on developer mode, then add a custom connector. 2. Use the same URL: https://unilink.us/mcp. 3. Sign in to UniLink and click Allow. ### Claude Code claude mcp add --transport http unilink https://unilink.us/mcp 1. Run the command above. 2. In Claude Code, run /mcp, pick unilink and sign in to UniLink. 3. Click Allow. ### Cursor [Not supported] Cursor needs Dynamic Client Registration, which UniLink doesn’t offer. ## MCP server — auth - OAuth 2.1, authorization code with PKCE (S256 only). - You approve on a UniLink screen; nothing happens until you click Allow. - Access tokens last 1 hour. Refresh tokens rotate: each one works once. - Changing your password, logging out everywhere or changing MFA ends the grant. - Disconnect any time in Account settings → Connected apps. - Discovery: https://unilink.us/.well-known/oauth-protected-resource/mcp and https://unilink.us/.well-known/oauth-authorization-server. - Client registration: Client ID Metadata Documents from claude.ai and chatgpt.com. Dynamic Client Registration is not supported. ## MCP server — transport - Endpoint: POST https://unilink.us/mcp - Transport: Streamable HTTP, JSON-RPC 2.0 - Responses: application/json, one per request (no SSE stream) - Sessions: Stateless — no Mcp-Session-Id - Protocol versions: 2025-06-18, 2025-03-26 - Batching: Not supported - Request body: Up to 1 MB - Resource metadata: https://unilink.us/.well-known/oauth-protected-resource/mcp - Authorization server: https://unilink.us/.well-known/oauth-authorization-server - Client registration: Client ID Metadata Documents (claude.ai, chatgpt.com); Dynamic Client Registration not supported ## MCP server — first call (client builders) curl -i -X POST https://unilink.us/mcp HTTP/1.1 401 Unauthorized WWW-Authenticate: Bearer resource_metadata="https://unilink.us/.well-known/oauth-protected-resource/mcp", Bearer 1. Read resource_metadata: it names the authorization server, https://unilink.us. 2. Fetch https://unilink.us/.well-known/oauth-authorization-server for the authorize and token endpoints. 3. Run the authorization code flow with PKCE (S256); the person signs in and clicks Allow. 4. Send the access token as Authorization: Bearer on every POST. Only client IDs on claude.ai and chatgpt.com are accepted. Building your own client? Use the REST API with an API key. ## MCP server — errors - Every tool error comes back with isError: true and the text {"success":false,"errorCode":…,"message":…,"retryable":…} (plus "field" when one argument is at fault). errorCode is one of INVALID_ARGUMENT, NOT_FOUND, PAID_PLAN_REQUIRED, LIMIT_REACHED, NOT_ALLOWED, NOT_CONNECTED, INTERNAL. Only INTERNAL can be retryable. Relay the message to the person. - publish_to_social and schedule_social_post skip platforms that are not connected and return NOT_CONNECTED when none of them is (connect the account in the UniLink Dashboard first; a Facebook login without a Page or a Discord connection without a channel counts as not connected). They return LIMIT_REACHED at the daily social post limit. - An unknown tool or method gets a JSON-RPC error. ## MCP server — pagination take/skip: list_orders, list_audience, list_products, list_repeat_customers, list_smm_campaigns. Other tools return everything in one call. ## MCP server — limits - add_link_item counts the profile’s live links against your plan’s link limit; at the limit it answers "Link limit for your plan reached." - Links to onlyfans.com, fansly.com and fanvue.com need a paid plan. - Link URL: at most 511 characters. Link title: at most 512. - Text block text: at most 10 000 characters, plain text. Heading: at most 512. - list_repeat_customers returns at most 100 per page. - publish_to_social and schedule_social_post: text at most 5000 characters, up to 10 media URLs (public https, at most 2048 characters each), and the profile’s daily social post limit (10 by default) per 24 hours. - Request body: up to 1 MB. ## Tool reference (22 tools) Paid plan: get_profile_analytics, list_orders, list_audience, get_audience_stats, list_products, get_product_details, get_revenue_summary, list_repeat_customers, publish_to_social, schedule_social_post. Every other tool works on every plan. ### get_account_info — Get account and profiles Plan: Every plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get current user account info including list of profiles, plan info. Call this first to discover available profileId values. Arguments: none ### get_profile_analytics — Get profile analytics Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get analytics for a profile: views, unique visitors, clicks, signups, top referrers and countries. Arguments: - profileId (integer, required): Profile ID (get from get_account_info) - period (integer, optional, default 5): 1=Today, 2=Yesterday, 3=Week, 4=Month, 5=AllTime, 6=Custom - startDate (string, optional): Start date ISO 8601 (only for period=6) - endDate (string, optional): End date ISO 8601 (only for period=6) ### list_blocks — List blocks Plan: Every plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List all blocks on a profile. Shows type, title, visibility, position, and for link blocks each link's ID (the itemId update_link_item takes), title and URL. Arguments: - profileId (integer, required): Profile ID ### list_orders — List orders Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List orders for a profile with pagination. Arguments: - profileId (integer, required): Profile ID - take (integer, optional, default 20): Number of orders (default 20) - skip (integer, optional, default 0): Offset (default 0) ### list_audience — List audience Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List audience members (contacts). Supports search and pagination. Arguments: - profileId (integer, required): Profile ID - search (string, optional): Search by name, email, phone - take (integer, optional, default 50): Limit (default 50) - skip (integer, optional, default 0): Offset (default 0) ### get_audience_stats — Get audience stats Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get audience stats: contacts, forms, subscribers, customers counts. Arguments: - profileId (integer, required): Profile ID ### list_products — List products Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List products for a profile store with pagination. Arguments: - profileId (integer, required): Profile ID - take (integer, optional, default 20): Limit (default 20) - skip (integer, optional, default 0): Offset (default 0) ### get_product_details — Get product details Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get detailed info about a product: price, description, media, status. Arguments: - profileId (integer, required): Profile ID - productId (integer, required): Product ID (from list_products) ### get_revenue_summary — Get revenue summary Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get revenue from paid orders, grouped by currency, optionally within a date range on the order's creation time (UTC). A paid order is one with status paid, confirmed, processing, in-progress, packed, shipped, delivered or completed. Not included: orders not yet paid, failed, cancelled, refunded or on hold. This differs from the Revenue figure on the UniLink Analytics screen, which counts only orders still in status 'paid'. Arguments: - profileId (integer, required): Profile ID - startDate (string, optional): Start date ISO 8601, UTC (optional) - endDate (string, optional): End date ISO 8601, UTC (optional). A date alone (2026-01-31) includes that whole day; a full timestamp is inclusive of that exact instant. ### list_repeat_customers — List repeat customers Plan: Paid plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List customers (audience contacts) who bought at least minOrders times, most purchases first, with order count, total spent per currency, and first/last purchase dates. A purchase is a paid order (status paid, confirmed, processing, in-progress, packed, shipped, delivered or completed) - the same definition as get_revenue_summary, and wider than the UniLink Analytics screen, which counts only 'paid'. Orders not linked to a contact cannot be attributed and are reported as a count only. Arguments: - profileId (integer, required): Profile ID - minOrders (integer, optional, default 2): Minimum number of purchases (default 2, at least 2) - take (integer, optional, default 20): Number of customers (default 20, max 100) - skip (integer, optional, default 0): Offset (default 0) ### create_block — Create a block Plan: Every plan. readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true Create a new block on a profile page. Common types: link, text, faq, contact, html, video, map, timer, gallery, banner, shop, digital, appointment, event, course, donation, tip. A new link block starts with no links; add them with add_link_item. A new text block starts with placeholder text under a "Text" heading; replace both with update_text_block. Call list_blocks to get the new block's UID. Arguments: - profileId (integer, required): Profile ID - pageId (integer, optional): Page ID (the Page shown by list_blocks). Omit or 0 for the profile's main page. - type (string, required): Block type (e.g. link, text, faq, shop, video) - position (integer, optional, default 0): Position index (0 = top) ### update_block — Rename a block Plan: Every plan. readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true Rename a block (its name/title). It changes nothing else: to change a block's content use add_link_item / update_link_item (link blocks) or update_text_block (text blocks). Arguments: - profileId (integer, required): Profile ID - blockUID (string, required): Block UID (from list_blocks) - name (string, required): New block name/title ### toggle_block_visibility — Show or hide a block Plan: Every plan. readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true Show or hide a block on the profile page. Arguments: - profileId (integer, required): Profile ID - blockUID (string, required): Block UID (from list_blocks) - isActive (boolean, required): true=visible, false=hidden ### reorder_blocks — Reorder blocks Plan: Every plan. readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true Change the order of blocks on a profile page. Pass block UIDs in the desired order. Arguments: - profileId (integer, required): Profile ID - blockUIDs (array of string, required): Array of block UIDs in desired order ### add_link_item — Add a link to a link block Plan: Every plan. readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true Add a link (a button to a web page) at the end of an existing LINK block. Works only on blocks of type 'link' (see list_blocks); create one first with create_block type=link if needed. The address must be an http:// or https:// web address (a bare domain such as example.com is taken as https://), at most 511 characters. Arguments: - profileId (integer, required): Profile ID - blockUID (string, required): UID of a link block (from list_blocks) - title (string, required): Button text, at most 512 characters - url (string, required): Web address the button opens (http or https) ### update_link_item — Edit a link in a link block Plan: Every plan. readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true Change the title and/or web address of one link in a LINK block. Get itemId from list_blocks. Only links that open a web address can get a new url; for other link kinds (phone, email, page, file, group, ...) only the title can change here. Arguments: - profileId (integer, required): Profile ID - blockUID (string, required): UID of the link block (from list_blocks) - itemId (integer, required): Link ID (from list_blocks) - title (string, optional): New button text (optional), at most 512 characters - url (string, optional): New web address, http or https (optional) ### update_text_block — Set the text of a text block Plan: Every plan. readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true Set the text of an existing TEXT block (type 'text' in list_blocks), replacing what is there. A text block shows one tab unless its owner turned tabs on; this always writes the first tab. text is plain text; line breaks become paragraphs. HTML or other markup is not interpreted: it shows as typed. Arguments: - profileId (integer, required): Profile ID - blockUID (string, required): UID of a text block (from list_blocks) - text (string, required): The new text, at most 10000 characters - title (string, optional): Heading shown above the text (optional), at most 512 characters ### smm_list_connected_accounts — List connected social accounts Plan: Every plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List the social media accounts connected to a profile, per platform (facebook, instagram, twitter, linkedin, telegram, pinterest, threads, discord, youtube), marking the account publish_to_social and schedule_social_post post to. Accounts are connected in the UniLink Dashboard, not here. Call this before publishing. Not paginated. Arguments: - profileId (integer, required): Profile ID ### publish_to_social — Publish a social post Plan: Paid plan. readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true Publish a post now to one or more of the profile's connected social accounts: one account per platform, the one smm_list_connected_accounts marks. It creates a campaign (see list_smm_campaigns) and sends it to each platform. Platforms that are not connected are skipped; if none is connected the call fails with NOT_CONNECTED. The text lists each platform as SUCCESS, FAILED or SKIPPED; for Instagram, SUCCESS means accepted and still processing, not yet live. Each platform applies its own rules (e.g. X allows 280 characters, Instagram needs an image or video, YouTube needs a video). Paid UniLink plans only, up to the profile's daily social post limit (10 by default) per 24 hours. Arguments: - profileId (integer, required): Profile ID - content (string, required): Post text, at most 5000 characters - platforms (array of string, required): Platforms to post to, each at most once: facebook, instagram, twitter, linkedin, telegram, pinterest, threads, discord, youtube - mediaUrls (array of string, optional): Optional public https:// image or video URLs to attach, at most 10, each at most 2048 characters. A URL whose path ends in .mp4, .mov, .avi or .webm is sent as a video, any other as an image. - title (string, optional): Optional campaign title shown in list_smm_campaigns, at most 512 characters (default: the start of content) ### schedule_social_post — Schedule a social post Plan: Paid plan. readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true Schedule a post to go out later to one or more of the profile's connected social accounts. Same as publish_to_social, but nothing is sent now: at scheduleAt UniLink publishes to each platform; follow it with list_smm_campaigns. Platforms that are not connected are skipped; if none is connected the call fails with NOT_CONNECTED. Paid UniLink plans only, up to the profile's daily social post limit (10 by default) per 24 hours, counted when the post is scheduled. Arguments: - profileId (integer, required): Profile ID - content (string, required): Post text, at most 5000 characters - platforms (array of string, required): Platforms to post to, each at most once: facebook, instagram, twitter, linkedin, telegram, pinterest, threads, discord, youtube - scheduleAt (string, required): When to publish: ISO 8601 in UTC, e.g. 2026-03-01T10:00:00Z (a time without an offset is taken as UTC). Must be in the future. - mediaUrls (array of string, optional): Optional public https:// image or video URLs to attach, at most 10, each at most 2048 characters. A URL whose path ends in .mp4, .mov, .avi or .webm is sent as a video, any other as an image. - title (string, optional): Optional campaign title shown in list_smm_campaigns, at most 512 characters (default: the start of content) ### list_smm_campaigns — List social campaigns Plan: Every plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false List the profile's social media campaigns, newest first, with each campaign's status and platforms and each platform's post status and link. Paginated with take/skip: the text ends with a line Pagination: {"hasMore":true|false,"nextSkip":n|null}; while hasMore is true, call again with skip = nextSkip. Arguments: - profileId (integer, required): Profile ID - take (integer, optional, default 20): Number of campaigns (default 20, max 100) - skip (integer, optional, default 0): Offset (default 0) ### get_campaign_details — Get social campaign details Plan: Every plan. readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false Get one social media campaign: status, schedule, text, and per platform the post status, link, error and engagement metrics. campaignId comes from list_smm_campaigns or from publish_to_social's result. Arguments: - profileId (integer, required): Profile ID - campaignId (integer, required): Campaign ID (from list_smm_campaigns) ## Workflows Recipes that run today with the connector’s tools. Paste the prompt; the assistant picks the steps. ### Thank your repeat buyers (paid plan) Find everyone who bought twice or more and get a short thank-you note for each. 1. list_repeat_customers — Who bought at least twice, and how much. 2. list_audience — Contact details, searched by email. Note: Sending email is not a tool. The assistant drafts the notes; you send them. Prompt: Find everyone who bought from me at least twice on UniLink and draft a short, personal thank-you note for each. Don’t send anything. ### Weekly sales summary (paid plan) Revenue by currency, the latest orders and page traffic for the last 7 days. 1. get_revenue_summary — Paid revenue per currency, last 7 days. 2. list_orders — The latest orders. 3. get_profile_analytics — Views, visitors and clicks this week. Prompt: Give me a UniLink sales summary for the last 7 days: revenue by currency, my latest orders, and how many people visited my page. ### Tidy your page Hide what’s stale and put the rest in a better order — after you say yes. 1. list_blocks — Every block, its position and visibility. 2. toggle_block_visibility — Hide the blocks you agree to hide. 3. reorder_blocks — Apply the new order. Note: Nothing is deleted: hidden blocks stay in your Dashboard. Prompt: Look at the blocks on my UniLink page. Suggest which to hide and a better order, and apply it only after I say yes. ### Put a new link up top Add a link to a link block, then move that block right under your header. 1. list_blocks — Find your link block and every block’s UID. 2. add_link_item — Add the link. 3. reorder_blocks — Move the link block first, right under the header. Note: Your header always stays on top. A new link goes to the end of its block; the connector can’t reorder links inside a block. Prompt: Add a link called "New drop" to https://example.com to my UniLink link block, then move that block right under my header. Show me the plan first. ### Refresh your text Rewrite a text block — shorter, clearer — and save it once you approve. 1. list_blocks — Find the text block. 2. update_text_block — Save the new text (replaces the old). Note: Works on text blocks. Your header bio isn’t editable through the connector. Prompt: Rewrite the text block on my UniLink page to be shorter and friendlier. Show me the new text and save it only after I approve. ### See what’s working (paid plan) Read where visitors come from and reorder your page for them. 1. get_profile_analytics — Views, clicks, top referrers and countries. 2. list_blocks — The current order. 3. reorder_blocks — Apply the order you agree on. Note: Analytics cover the whole page, not single blocks. The assistant suggests; you decide. Prompt: Check my UniLink analytics for this month — where visitors come from — and suggest a block order that fits them. Apply it after I agree. ## Skills A skill is a SKILL.md file your agent loads when it needs it. Ours teaches the UniLink connector’s rules. - UniLink connector: https://unil.ink/agents/skills/unilink/SKILL.md — Teaches an agent to use the 22 connector tools well: resolve ids with list_blocks, read before write, confirm overwrites, respect plan limits. - UniLink Agent Platform: https://unilink.us/api/agents/skill.md — For autonomous agents with their own profile: register, design the profile, manage blocks, sell, publish. Uses the agent API with an X-API-Key. ## Get started Two facts before you start: - A person signs up and clicks Allow. An agent can’t create the account or approve access for them. - Sales, audience, analytics and social publishing tools need a paid plan. Profile and block tools work on every plan. 1. Discover — Read /llms.txt for the map, or /catalog.json for tools, workflows and links as JSON. 2. Create a free account — A person signs up; the connector acts on that account. 3. Connect over MCP — Add https://unilink.us/mcp in Claude, ChatGPT, Claude Code. Sign in to UniLink and click Allow — UniLink shows what the assistant asks for first. 4. Or use the REST API — For your own code: create a key in the Dashboard (Account settings → API Key) and call https://unilink.us/api. 5. Ask your first question — Try "List the blocks on my page" — it works on every plan. 6. Upgrade for sales, audience and analytics — 10 tools need a paid plan, the same as in the Dashboard. On Free they answer "This is available on a paid UniLink plan." Sign up: https://unilink.us/signup?s=landing ## FAQ Q: Can an agent create a UniLink account by itself? A: For the connector, no: a person signs up and approves access. An autonomous agent that needs its own profile can register through the Agent Platform (POST /api/agents/register). Q: What can the connector do without a person? A: Only what the approved tools allow, on that person’s account. It cannot delete anything, send emails, or change orders or money. Q: How does an agent get a paid plan? A: It can’t buy one. It gives its person the pricing link; they upgrade in the Dashboard. Q: MCP or REST API — which one? A: MCP for assistants that chat with a person (Claude, ChatGPT). The REST API for your own code, scripts and automations. Q: Which apps can connect? A: Today: Claude, ChatGPT, Claude Code. Not supported: Cursor.