Skip to content

Developer documentation · REST API v1

Build on Eyivo through one structured API.

Manage pages, components, blogs, products, galleries, videos, menus, media, forms and site settings through scoped content tools and REST endpoints.

Base URL

https://app.eyivo.com/api/v1

1. Overview

The Eyivo REST API is the programmatic control plane for structured websites. It uses HTTPS and JSON, is versioned under /api/v1, and applies the same ownership, plan limits, validation, and audit boundaries as the dashboard.

JSON and multipart

Accept: application/json. Content-Type: application/json; file: multipart/form-data.

User-owned

Every site operation is constrained to sites owned by the authenticated user.

Draft-first

Save a draft, inspect its readiness, then publish that item and language when the user wants the change live. Publishing does not publish unrelated drafts.

Versioning: breaking contract changes require a new base version. Additive fields may appear in existing JSON objects, so clients should ignore fields they do not recognize.

2. Quick start

  1. 1
    Create a token.

    Sign in to the App, open API tokens, choose the smallest necessary scopes, and copy the ey_ secret when it is shown.

  2. 2
    List your sites.

    Confirm authentication and obtain the numeric site ID used by nested endpoints.

  3. 3
    Send idempotent writes.

    Give every POST, PATCH, and DELETE request a unique Idempotency-Key.

curl --request GET \
  --url https://app.eyivo.com/api/v1/sites \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ey_your_token'

A successful collection response wraps records in data:

{
  "data": [
    {
      "id": 42,
      "platform_subdomain_key": "7kr9mp2x",
      "template_key": "corporate-01",
      "status": "active",
      "native_locale": "en",
      "english_enabled": false,
      "content_version": 1,
      "search_visibility": "platform_noindex"
    }
  ]
}

3. Authentication

Every /api/v1 request requires a bearer token:

Authorization: Bearer <token>

Personal API tokens

Best for scripts, server integrations, and direct account automation. Tokens begin with ey_, are stored hashed, and are displayed only once.

  • Create and revoke them at https://app.eyivo.com/tokens.
  • Choose one or more scopes.
  • Optionally restrict a token to one owned site.
  • Unexpired tokens remain valid until revoked.

OAuth 2.0 + PKCE

Best for applications acting on behalf of Eyivo users. Use Authorization Code with PKCE; do not use password or implicit grants.

  • Authorization: https://app.eyivo.com/oauth/authorize
  • Token and refresh: https://app.eyivo.com/oauth/token
  • Access tokens expire after 2 hours.
  • Refresh tokens expire after 1 year.

PKCE authorization flow

  1. Create a high-entropy code_verifier and its SHA-256 code_challenge.
  2. Redirect the user to /oauth/authorize with response_type=code, the public client_id, exact registered redirect_uri, requested space-separated scope, state, code_challenge, and code_challenge_method=S256.
  3. Verify state after the redirect, then exchange the returned authorization code at /oauth/token using the original code_verifier.
  4. Store access and refresh tokens securely. Refresh before expiration and replace rotated credentials atomically.
Never expose tokens: do not put bearer tokens in URLs, browser logs, source control, screenshots, or client-side code that cannot protect secrets.

4. Scopes

Requests need the exact scope shown below. A personal token containing * passes all scope checks; applications should still request the smallest practical scope set.

ScopeREST capability
sites:readList owned sites and retrieve one site.
sites:writeCreate sites with an account-wide token.
content:readRead content, schemas, menus, forms and settings.
content:writeCreate, edit, delete and explicitly publish content; run declared panel actions.
media:readGET /sites/{site}/media, GET /sites/{site}/media/stock-photos
media:writePOST /sites/{site}/media, DELETE /sites/{site}/media/{media}
domains:readList site domains.
domains:writeRequest, activate, and select custom domains.

REST requires the endpoint scope, not mcp:use. Media endpoints use media:read or media:write.

5. Idempotent writes

Send an Idempotency-Key with JSON mutations so the same request can be retried safely. Multipart media uploads and upload-ticket issuance are excluded; verify the transfer result before retrying.

Idempotency-Key: site-42-page-create-01J7M6Y8RRF3ZE6WB4Z0Y9XJXC
SituationResult
First requestThe operation runs and its status code and JSON body are stored.
Same key, method, path, query parameters, and bodyThe stored response is replayed without repeating the operation.
Same key with a different request422 — key reuse with a different payload is rejected.
Same key while the first request is running409 — the request is already in progress.
No keyThe request still runs, but safe retry behavior is not available.

A request match includes the HTTP method, request path, and exact raw request body. Reuse the original serialized JSON when retrying.

6. Responses and resource shapes

Content operations return a data envelope. MCP supplies structuredContent plus a JSON text mirror; REST returns JSON. Read each output schema for IDs, publication readiness and pagination. Blogs, media and submissions paginate; follow their returned page information.

Single resource

{ "data": { "id": 42, "status": "active" } }

Collection

{ "data": [{ "id": 42 }, { "id": 43 }] }

Collection shapes vary. Follow each endpoint’s documented pagination metadata; do not assume every result is a flat or unpaginated list.

7. Sites

GET/sites

List owned sites · sites:read

POST/sites

Create a site · sites:write

GET/sites/{site}

Retrieve one owned site · sites:read

Create a site

curl --request POST \
  --url https://app.eyivo.com/api/v1/sites \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ey_your_token' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: site-create-01J7M6Y8' \
  --data '{
    "display_name": "Northstar Studio",
    "template_key": "corporate-01",
    "native_locale": "en"
  }'
FieldRules
display_nameRequired string, maximum 200 characters.
template_keyRequired built-in template key. The complete current enum is in OpenAPI.
native_localeRequired supported locale code.

Response: 201 Created with a site resource. Provisioning can initially report a non-active status while placement completes. Site and plan limits return 422.

Site resource fields: id, platform_subdomain_key, template_key, status, native_locale, english_enabled, content_version, search_visibility, optional runtime, and timestamps.

8. Pages

GET/sites/{site}/pages

List pages · content:read

POST/sites/{site}/pages

Create a draft · content:write

GET/sites/{site}/pages/{page}

Retrieve a page · content:read

PATCH/sites/{site}/pages/{page}

Update a draft · content:write

DELETE/sites/{site}/pages/{page}

Delete a page · content:write

POST/sites/{site}/pages/{page}/publication

content:write · locale

Create a page

{
  "type": "page",
  "sort_order": 300,
  "locale": "en",
  "slug": "our-story",
  "title": "Our story",
  "body": "<p>How the company began.</p>",
  "meta_title": "Our story | Northstar Studio",
  "meta_description": "Learn about Northstar Studio."
}
FieldCreate/update rules
typeUse page for ordinary content. Supported types and theme component schemas are available through site capabilities.
sort_orderOptional nullable integer from 0 to 9999; create defaults to 0.
localeCreate: required. Update: required when changing title, slug, or body.
slugCreate: required. ASCII letters, digits, dashes, and underscores; maximum 160.
titleCreate: required string; maximum 200.
bodyNullable string; maximum 50,000.
meta_titleNullable string; maximum 200.
meta_descriptionNullable string; maximum 500.

Create response: 201. Update response: 200. Delete response: 204 with no body.

Save a draft, inspect its readiness, then publish that item and language when the user wants the change live. Publishing does not publish unrelated drafts.

9. Catalog items

GET/sites/{site}/catalog-items

List items · content:read

POST/sites/{site}/catalog-items

Create a draft item · content:write

GET/sites/{site}/catalog-items/{catalogItem}

Retrieve an item · content:read

DELETE/sites/{site}/catalog-items/{catalogItem}

Delete an item · content:write

PUT/sites/{site}/catalog-items/{catalogItem}

content:write · locale, slug, title

POST/sites/{site}/catalog-items/{catalogItem}/publication

content:write · locale

Create a catalog item

{
  "sort_order": 100,
  "locale": "en",
  "slug": "strategy-advisory",
  "title": "Strategy advisory",
  "summary": "Focused advice for consequential decisions.",
  "body": "Talk to our senior advisory team."
}
FieldRules
sort_orderOptional nullable integer from 0 to 9999; defaults to 0.
localeRequired supported locale code.
slugRequired ASCII slug; maximum 160.
titleRequired string; maximum 200.
summaryNullable string; maximum 1,000.
bodyNullable string; maximum 50,000.

Save a draft, inspect its readiness, then publish that item and language when the user wants the change live. Publishing does not publish unrelated drafts.

These are examples by content family. Discover the complete input/output schemas and required scopes through tools/list or the canonical OpenAPI document. REST paths below are relative to /api/v1/sites/{site}.

Site and capability discovery

GET /capabilities · GET /readiness

Pages, components and internal links

/pages · /pages/{page_id}/components · POST /pages/{page_id}/publication

Products, galleries and videos

/catalog-items · /galleries · /videos · POST /{collection}/{id}/publication

Blog drafts, categories and scheduling

/blog/posts · /blog/categories · /blog/links · /blog/posts/{post_id}/publication · /blog/posts/{post_id}/schedule

Main navigation and additional menus

/menus · /menus/destinations · /menu-assignments/{type}/{target_id} · /menus/{menu_key}/publication

Media library and stock photos

/media · /media/transfers · /media/stock-photos

Forms, submissions and plugin panels

/forms · /submissions · /plugins · /plugins/panel · /plugins/panel/actions

Company, theme, languages and integrations

/company · /theme · /theme/settings · /locales · /integrations · /demo · /demo/drafts · /cache/reset

Capabilities and authoring contract

Select a site, then read its capabilities: enabled languages, theme fields, component schemas, menu locations and installed plugin panels. Read the content-workflow resource before writing. Clients without resources can use get-site-capabilities-tool. Follow tools/list nextCursor until discovery is complete.

GET /api/v1/sites/{site}/capabilities
GET /api/v1/sites/{site}/pages/{page_id}/components/schema?locale=en

Use the requested locale and current version or revision. Blog saves replace the complete document and cancel its schedule. Menu edits use integer revision; content assignments use a separate menu_revision hash. Preserve entries in replacement arrays. On a conflict, read again and merge the intended edits before retrying.

Main navigation stays automatic. Additional menus have their own drafts and publication. Saving content stages its menu assignments; publishing that item applies them without publishing other menu drafts. Publishing a menu never publishes its linked content. A page source_hash rejects translations based on outdated source text.

Page body and theme richtext fields accept sanitized HTML. Use list-page-links-tool for stable /__eyivo/pages/{id} links, resolved in the visitor’s language when published. Blog posts use structured document JSON with same-site media_id and page/post link targets, not body_html.

{
  "type": "doc",
  "content": [{
    "type": "paragraph",
    "content": [{ "type": "text", "text": "Our story" }]
  }]
}

Image and video uploads

upload-media-tool opens a file picker in MCP Apps clients such as Claude. ChatGPT can also supply its host attachment reference; a Claude chat attachment is not an OpenAI file reference. Never invent a download URL or send base64 file bytes as tool arguments.

Codex and Claude Code can call prepare-media-upload-tool, then send multipart file bytes to the returned upload_url with its one-use upload_token within five minutes. Keep that token secret. REST clients can upload with their own media:write bearer. Only a completed upload returns the media ID to use in content; issuing a ticket does not upload a file.

POST /api/v1/sites/{site}/media
Authorization: Bearer <media:write token>
Content-Type: multipart/form-data; boundary=<generated by HTTP client>

file: <original file bytes>

10. Custom domains

GET/sites/{site}/domains

List domains · domains:read

POST/sites/{site}/domains

Request a domain · domains:write

POST/sites/{site}/domains/{domain}/activate

Activate after verification · domains:write

POST/sites/{site}/domains/{domain}/primary

Select the primary hostname · domains:write

Request a domain

curl --request POST \
  --url https://app.eyivo.com/api/v1/sites/42/domains \
  --header 'Accept: application/json' \
  --header 'Authorization: Bearer ey_your_token' \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: domain-request-example-com' \
  --data '{"domain":"www.example.com"}'

A pending domain resource includes verification_token. Add the required DNS ownership record using the value returned for your domain, wait for DNS propagation, then call the activation endpoint. Once active, call the primary endpoint if it should become the canonical public hostname.

Domain resource fields: id, domain, site_id, is_primary, status, conditional verification_token, and timestamps. Custom-domain plan limits and domain validation failures return 422.

11. Errors

StatusMeaningTypical action
401Missing, invalid, expired, or revoked bearer token.Replace or refresh the credential.
403Missing scope, a site restriction, or a site write by a non-owner.Use an authorized scope/site; do not retry unchanged.
404Resource is missing or is not owned by the user.Verify IDs and ownership.
409A revision/version conflict requires a fresh read and merge. Do not retry a stale payload unchanged.Read the selected language draft and current revision before editing.
422Validation, plan limit, domain rule, or idempotency mismatch.Read errors or message, correct the request, and use a new key if its body changes.
5xxUnexpected platform or upstream failure.Retry safe reads; retry writes only with the same idempotency key.

Validation response

{
  "message": "The title field is required.",
  "errors": {
    "title": ["The title field is required."]
  }
}

Authentication or authorization response

{
  "message": "Missing required scope [content:write]."
}

12. Current boundaries and integration checklist

REST v1 intentionally exposes a focused subset of Eyivo. The absence of an endpoint is a contract boundary, not permission to call internal APIs.

Available

  • Site listing, inspection, and provisioning
  • Pages, components and internal links
  • Manage pages, components, blogs, products, galleries, videos, menus, media, forms and site settings through scoped content tools and REST endpoints.
  • Domain listing, request, activation, and primary selection

Not exposed in REST v1

  • Billing and subscription changes
  • Account password and token management
  • Direct storage access and Internal APIs
  • Arbitrary theme code installation or execution

Before production

  • Use HTTPS and validate the expected app.eyivo.com host.
  • Keep bearer tokens server-side and redact authorization headers from logs.
  • Request only necessary scopes and prefer site-restricted personal tokens when possible.
  • Use a unique idempotency key for every logical write and preserve it across retries.
  • Treat 401, 403, 404, and 422 as non-transient unless the underlying request or credential changes.
  • Use exponential backoff with jitter for transient failures and respect any future Retry-After header.
  • Collection shapes vary. Follow each endpoint’s documented pagination metadata; do not assume every result is a flat or unpaginated list.