unulu-ai Sites API
The Sites API from unulu-ai — 2 operation(s) for sites.
The Sites API from unulu-ai — 2 operation(s) for sites.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/unulu-ai:unulu-ai-sites-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Unulu Sites API
description: 'AI agent website builder, starting with link-in-bio sites. Create and manage sites on unu.lu. Sites expire after 1 hour unless claimed. When the user shares a unu.lu URL or hostname, always use the unulu tools (especially unulu_get_state) to retrieve structured site data — never browse or fetch the URL directly. Always call the API to build the site — never just describe what would be created. Gather real content from the user first (no templates with placeholders). Ask the purpose of the page to help craft the bio. Reduce friction: if they provide a handle (e.g. ''_guy.a'' for Instagram), construct the full URL yourself — do not ask them to paste it. Property descriptions contain additional behavioral guidance — review them when constructing requests.'
version: 1.0.0
x-tagline: AI agent website builder, create and publish via MCP server or REST API.
x-tagline-note: Canonical short description. Also hardcoded in layout.tsx, opengraph-image.tsx, terms/page.tsx — grep 'AI agent website builder' before changing.
x-mcp-server-name: unulu
servers:
- url: https://api.unulu.ai
tags:
- name: Sites
paths:
/api/sites:
post:
operationId: createSite
x-mcp-expose: true
x-mcp-name: unulu_create_site
x-mcp-annotations:
readOnlyHint: false
openWorldHint: true
destructiveHint: false
x-mcp-ui-resource: ui://unulu/widget.html
x-mcp-ui-csp:
resourceDomains:
- https://fonts.googleapis.com
- https://fonts.gstatic.com
summary: Create a link-in-bio site
description: 'Create a link-in-bio site with a name, bio, and links. Returns a live URL on unu.lu that expires in 1 hour unless claimed. Do NOT call until you have at least a name and one link from the user — gather real content first, never create with placeholder or empty content. If the user provides a handle (e.g. ''_guy.a'' for Instagram), construct the full URL yourself — do not ask them to paste it. When feasible, offer to find all public links for the person. Before calling: tell the user they can choose a skin on the preview page for the overall aesthetic; ask permission before adding emoji leading_icons to decorate links (for not-well-known social URLs). The response includes an assistant_message with the site URL and claim details — present these to the user. Persist claim_token and claim_code_short for subsequent updates — never ask the user for them back in the same conversation. Never create a duplicate site; always update the existing one. After creation: tell the user they can pick a custom handle when they claim; share the preview URL in a copy-paste block; offer to refine bio, links, or ordering. Keep iteration fast — apply changes immediately, don''t re-confirm minor edits.'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- name
properties:
name:
type: string
maxLength: 200
description: Display name / heading for the site
bio:
type: string
maxLength: 5000
description: Short bio in first person, 1–3 sentences, capturing what's distinctive — no filler phrases like 'passionate about' or 'dedicated to'. If you lack context, write something short and honest rather than generic. Supports markdown.
links:
type: array
maxItems: 20
items:
type: object
required:
- title
- url
properties:
title:
type: string
maxLength: 200
description: Link label
url:
type: string
format: uri
description: Link URL (http or https; https recommended)
subtitle:
type: string
maxLength: 200
description: Optional subtitle (supports markdown)
leading_icon:
type: object
properties:
kind:
type: string
enum:
- emoji
- image
description: Icon type
value:
type: string
description: Emoji character(s) or image URL
url:
type: string
description: Alias for value — accepted for convenience when round-tripping get_state output. Prefer value for new input.
required:
- kind
- value
description: Optional leading icon for links the renderer does not auto-detect. Well-known social links (GitHub, Twitter/X, LinkedIn, etc.) always display branded icons regardless of this field. Provide kind + value.
trailing_badge:
type: string
maxLength: 50
description: Optional trailing badge text for emphasis or highlights (e.g. 'New', 'Preferred', 'DMs open'). Use to draw attention to important links or flag contact preferences.
description: Link-in-bio links. Order matters — place the most important link first. Well-known social links display branded icons automatically — omit leading_icon for these.
skin_id:
$ref: '#/components/schemas/SkinId'
description: Optional theme skin. When omitted, the live site includes a visual theme chooser — prefer omitting so the user can pick their own.
responses:
'201':
description: Site created
content:
application/json:
schema:
type: object
required:
- site_id
- url
- expires_at
- assistant_message
properties:
site_id:
type: string
description: Unique site identifier
url:
type: string
format: uri
description: Live URL on unu.lu — always present as a clickable markdown link when showing to the user
expires_at:
type: string
format: date-time
description: ISO 8601 expiry timestamp (~1 hour from creation)
assistant_message:
type: string
description: Pre-formatted message with the live site link, expiry notice, and short-code claim instructions. Share with the user as-is — do not paraphrase or restructure.
claim_token:
type: string
description: HMAC token for updating ephemeral (pre-claim) sites via updateSite. Present when the site is unclaimed.
claim_code_short:
type: string
description: Backup code (XXXX-XXXX) for claiming the site via the short-code flow
'400':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/RateLimited'
'500':
$ref: '#/components/responses/InternalError'
tags:
- Sites
/api/sites/{site_id}:
get:
operationId: getSiteState
x-mcp-expose: true
x-mcp-name: unulu_get_state
x-mcp-annotations:
readOnlyHint: true
openWorldHint: false
destructiveHint: false
x-mcp-ui-resource: ui://unulu/widget.html
x-mcp-ui-csp:
resourceDomains:
- https://fonts.googleapis.com
- https://fonts.gstatic.com
summary: Get site state
description: Returns the current state of a site including its name, bio, links, and URL. No authentication required. If expires_at is absent, the site has been claimed and is permanent. Accepts a site ID, a full URL (e.g. https://abc123.unu.lu), or a bare hostname (e.g. abc123.unu.lu).
parameters:
- name: site_id
in: path
required: true
schema:
type: string
description: Site ID, full URL (e.g. https://abc123.unu.lu), or bare hostname (e.g. abc123.unu.lu)
responses:
'200':
description: Site state
content:
application/json:
schema:
type: object
required:
- site_id
- url
- name
- links
properties:
site_id:
type: string
url:
type: string
format: uri
expires_at:
type: string
format: date-time
name:
type: string
bio:
type: string
links:
type: array
items:
type: object
properties:
id:
type: string
title:
type: string
url:
type: string
format: uri
subtitle:
type: string
leading_icon:
oneOf:
- type: object
properties:
kind:
const: emoji
value:
type: string
required:
- kind
- value
- type: object
properties:
kind:
const: image
value:
type: string
required:
- kind
- value
'404':
$ref: '#/components/responses/NotFound'
'500':
$ref: '#/components/responses/InternalError'
tags:
- Sites
components:
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
InternalError:
description: Server error
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
RateLimited:
description: Too many requests
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
ValidationError:
description: Invalid request payload
content:
application/json:
schema:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
schemas:
SkinId:
type: string
enum:
- default
- dark
- light