The internet, readable by agents.

Manifest turns any URL into a structured map of what an AI agent can actually do on a page - every button, form, and input, with the fields it needs to fill and what's required before it can move on.

Product Hunt Badge

The problem

Give an AI agent a browser, and it can see a webpage. Give it a scraper, and it can read one. Neither tells it what it's allowed to do there.

Today, agents either take a screenshot and guess, or a developer hand-writes selectors that break the next time the page ships a redesign. There's no layer that tells an agent: here are the actions on this page, here's what each one needs, here's what's required.

That's the gap Manifest fills.

How it works

Manifest visits a URL, reads its accessibility tree the same way a screen reader would, and cross-references it against the page's actual DOM for the details accessibility trees miss — required fields, input types, placeholders, disabled states. That combined picture is translated into a clean JSON action manifest: every button, form, and input on the page, described in terms an agent can reason about and act on.

Manifest visits a URL, reads its accessibility tree the same way a screen reader would, and cross-references it against the page's actual DOM for the details accessibility trees miss — required fields, input types, placeholders, disabled states. That combined picture is translated into a clean JSON action manifest: every button, form, and input on the page, described in terms an agent can reason about and act on.

URL → accessibility tree → DOM extractors → structured JSON manifest

URL → accessibility tree → DOM extractors → structured JSON manifest

No screenshots. No hand-maintained selectors. No fragile CSS paths that snap the moment a page redesigns.

No screenshots. No hand-maintained selectors. No fragile CSS paths that snap the moment a page redesigns.

What you get back

POST /manifest

{ "url": "https://example.com" }

POST /manifest

{ "url": "https://example.com" }

{

"url": "https://example.com",

"current_page_state": "Contact page with a message form",

"actions": [

{

"id": "submit-form",

"label": "Submit",

"type": "button",

"description": "Submits the contact form",

"required": false

},

{

"id": "email-input",

"label": "Email",

"type": "input",

"description": "Email address field",

"required": true

}

],

"navigation": [

{ "label": "Home", "url": "/" }

]

}

{

"url": "https://example.com",

"current_page_state": "Contact page with a message form",

"actions": [

{

"id": "submit-form",

"label": "Submit",

"type": "button",

"description": "Submits the contact form",

"required": false

},

{

"id": "email-input",

"label": "Email",

"type": "input",

"description": "Email address field",

"required": true

}

],

"navigation": [

{ "label": "Home", "url": "/" }

]

}

cURL

Python

Node

curl -X POST https://manifest.omfang.io/manifest \

-H "X-API-Key: your-key-here" \

-H "Content-Type: application/json" \

-d '{"url": "https://omfang.io/contact"}'

Response:

{

"url": "https://omfang.io/contact",

"authenticated_user": null,

"current_page_state": "Contact page with a form for name, email, and message",

"actions": [

{

"id": "name-input",

"label": "Name",

"type": "input",

"description": "Text field for the sender's name",

"required": true

},

{

"id": "email-input",

"label": "Email",

"type": "input",

"description": "Email field for the sender's contact address",

"required": true

},

{

"id": "message-input",

"label": "Message",

"type": "textarea",

"description": "Free text field for the inquiry message",

"required": true

},

{

"id": "submit-contact-form",

"label": "Send message",

"type": "button",

"description": "Submits the contact form",

"required": false,

"requires": ["name-input", "email-input", "message-input"]

}

],

"navigation": [

{ "label": "Home", "url": "/" },

{ "label": "Manifest", "url": "/manifest" }

]

}

Why not just use a browser tool or a scraper?

Browser access

Browser access

Content extraction

Content extraction

Manifest

Manifest

Gives an agent a browser

Gives an agent a browser

Returns page content

Returns page content

Returns available actions

Returns available actions

Agent can reason about the output

Agent can reason about the output

Partially

Survives UI redesigns

Survives UI redesigns

Required fields & input types

Required fields & input types

Browser tools get an agent onto the page. Content tools get it the text. Manifest is the layer that tells it what to click, fill, and submit — and what's mandatory before it can move on.

Browser tools get an agent onto the page. Content tools get it the text. Manifest is the layer that tells it what to click, fill, and submit — and what's mandatory before it can move on.

Pricing

Pricing

Plan

Plan

Price

Price

Calls/ Month

Calls/ Month

Overage

Overage

Free

Free

$0

$0

50

50

Starter

Starter

$29/ mo

$29/ mo

1,000

1,000

$0.08/ call

$0.08/ call

Pro

Pro

$79/ mo

$79/ mo

5,000

5,000

$0.05/ call

$0.05/ call

Enterprice

Enterprice

Custom

Custom

Custom

Custom

Custom

Custom

Cached responses are always fast and never count against your rate limit the same way a cold request does — most agents hit the same handful of URLs repeatedly, so cache hits keep costs predictable.

Cached responses are always fast and never count against your rate limit the same way a cold request does — most agents hit the same handful of URLs repeatedly, so cache hits keep costs predictable.

Contents

Getting started

Manifest API makes any webpage agent-readable. Pass a URL, get back a structured JSON manifest describing what the page contains and what actions are available.

Quickstart

  1. Get an API key from your manifest dashboard.

  1. Send a URL, get back a manifest.

cURL

Python

Node

curl -X POST https://manifest.omfang.io/manifest \

-H "X-API-Key: your-key-here" \

-H "Content-Type: application/json" \

-d '{"url": "https://omfang.io/contact"}'

Response:

{

"url": "https://omfang.io/contact",

"authenticated_user": null,

"current_page_state": "Contact page with a form for name, email, and message",

"actions": [

{

"id": "name-input",

"label": "Name",

"type": "input",

"description": "Text field for the sender's name",

"required": true

},

{

"id": "email-input",

"label": "Email",

"type": "input",

"description": "Email field for the sender's contact address",

"required": true

},

{

"id": "message-input",

"label": "Message",

"type": "textarea",

"description": "Free text field for the inquiry message",

"required": true

},

{

"id": "submit-contact-form",

"label": "Send message",

"type": "button",

"description": "Submits the contact form",

"required": false,

"requires": ["name-input", "email-input", "message-input"]

}

],

"navigation": [

{ "label": "Home", "url": "/" },

{ "label": "Manifest", "url": "/manifest" }

]

}

cURL

Python

Node

curl -X POST https://manifest.omfang.io/manifest \

-H "X-API-Key: your-key-here" \

-H "Content-Type: application/json" \

-d '{"url": "https://omfang.io/contact"}'

Response:

{

"url": "https://omfang.io/contact",

"authenticated_user": null,

"current_page_state": "Contact page with a form for name, email, and message",

"actions": [

{

"id": "name-input",

"label": "Name",

"type": "input",

"description": "Text field for the sender's name",

"required": true

},

{

"id": "email-input",

"label": "Email",

"type": "input",

"description": "Email field for the sender's contact address",

"required": true

},

{

"id": "message-input",

"label": "Message",

"type": "textarea",

"description": "Free text field for the inquiry message",

"required": true

},

{

"id": "submit-contact-form",

"label": "Send message",

"type": "button",

"description": "Submits the contact form",

"required": false,

"requires": ["name-input", "email-input", "message-input"]

}

],

"navigation": [

{ "label": "Home", "url": "/" },

{ "label": "Manifest", "url": "/manifest" }

]

}

Authentication

All requests require an API key passed in the X-API-Key header. Keys are issued from your dashboard and can be rotated or revoked at any time.

X-API-Key: your-key-here

Requests without a valid key return

401 Unauthorized.

Endpoints

POST/manifest

Returns a structured action manifest for any URL.

Request:

JSON


{

"url": "https://example.com"

}

Response:

JSON


{

"url": "string",

"authenticated_user": "string | null",

"current_page_state": "string",

"actions": [

{

"id": "string",

"label": "string",

"type": "button | input | textarea | select | checkbox | radio | other",

"description": "string",

"required": "boolean",

"requires": ["string"]

}

],

"navigation": [

{ "label": "label", "url": "string" }

]

}

GET/health

Returns the API health status. No authentication required.

JSON


{ "status": "ok" }

GET/session-status

Returns whether the current browser session is valid. No authentication required

JSON


{ "valid": true }

or

JSON


{ "valid": false, "message": "Session expired" }

Response fields:

Field

Type

Description

url

string

The url that was requested

authenticated_user

string or null

Detected logged-in user, if any

current_page_state

string

Brief description of the page

actions

array

Interactive elements on the page

navigation

array

Primary navigation links

Action fields:

Field

Type

Description

id

string

Unique identifier for the action

label

string

Human-readable label

type

string

button, input, textarea, select, checkbox, radio, other

description

string

What the action does

required

boolean

Whether the field is required

requires

array of strings | omitted

IDs of other actions on the page that must be completed before this one can be performed (e.g. a submit button requiring the input fields above it). Omitted when an action has no dependencies.

IDs of other actions on the page that must be completed before this one can be performed (e.g. a submit button requiring the input fields above it). Omitted when an action has no dependencies.

Rate limits

Plans

Calls/month

Per-minute limit

Free

50

10/min

Starter

1,000

10/min

Pro

5,000

10/min

Exceeding the per-minute limits returns

429 Too Many Requests

Error codes

Code

Meaning

401

Missing or invalid API key

429

Rate limit exceeded

503

Browser session unavailable — retry later

Caching

Manifests are cached for 6 hours by default. Repeated requests for the same URL within that window are served instantly from cache at no extra cost to your call limit.

omfang logo

Follow us on social media

Contact us

Learn more about omfang