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 "Content-Type: application/json" \

-H "X-API-Key: YOUR_API_KEY" \

-d '{

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

"country": "US"

}'

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" }

],

"requested_country": "US",

"served_locale": "en-US",

"locale_mismatch": false,

"fingerprint": "a3f5c9e1...",

"cache_status": "hit",

"captured_at": "2026-07-26T14:32:07Z",

"expires_at": "2026-07-26T20:32:07Z"

}

country (optional) — ISO 3166-1 alpha-2 code (e.g. "US", "DE"). Sets the scan's Accept-Language header, and for some sites, an additional locale cookie — best-effort steering of server-side geo/locale redirects, not a guarantee. Check locale_mismatch in the response either way.

Geo/locale honesty, not correction. Some sites redirect based on IP, headers, or a stored locale — Manifest doesn't attempt to override this universally or convert prices. country improves the odds of landing on the right locale for sites we've mapped, but for others it has no effect (and rarely, can even shift which wrong locale you get). locale_mismatch is the field to trust: if it's true, treat prices, availability, and copy on that page as locale-specific, not the one you asked for.

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" }

],

"fingerprint": "string",

"cache_status": "hit | miss",

"captured_at": "ISO-8601 timestamp",

"expires_at": "ISO-8601 timestamp"

}

POST /fingerprint

GET /fingerprint?url=

Returns a stable hash of a page's interactive surface — no LLM call, never cached. Use it to cheaply check whether a page's actions have changed since your last /manifest call, before paying for a new manifest generation.

Request:

bash


curl -X POST https://manifest.omfang.io/fingerprint \ -H "X-API-Key: your-key" \ -H "Content-Type: application/json" \ -d '{"url": "https://example.com"}'

Or as a GET:

bash


curl "https://manifest.omfang.io/fingerprint?url=https://example.com" \ -H "X-API-Key: your-key"

Response:

JSON


{ "url": "https://example.com", "fingerprint": "a3f5c9e1..." }

fingerprint is a SHA-256 hex digest computed from the page's interactive elements (role, name, input type). It's stable across unrelated layout shifts and DOM reordering — an ad or news ticker inserted earlier in the page won't change it — but it changes the moment a button, field, or link is added, removed, or renamed.

Every /manifest response also includes this same field, so you can compare a manifest you already fetched against a later /fingerprint call without re-running the full pipeline:

JSON


{ "url": "https://example.com", "actions": [ ... ], "fingerprint": "a3f5c9e1..." }

When to use it

If you're polling a page you've already generated a manifest for — waiting for a form to appear, checking whether a checkout flow changed — call /fingerprint on a short interval instead of /manifest. Only re-fetch the full manifest when the hash changes. This skips the LLM translation step entirely, so it's far cheaper per call than /manifest.


Limits

/fingerprint has its own monthly quota, separate from /manifest — exhausting one never blocks the other.

Plan

Plan

Manifest calls/ Month

Manifest calls/ Month

Fingerprint calls/ Month

Fingerprint calls/ Month

Free

Free

50

50

250

250

Starter

Starter

1,000

1,000

5,000

5,000

Pro

Pro

5,000

5,000

25,000

25,000

Rate limits (requests/minute) are shared with /manifest per plan.

SDK


python


fp = client.fingerprint(

"https://example.com"

)

print(fp.url, fp.fingerprint)

# or compare against an existing manifest


manifest =client.get(

"https://example.com"

)

if manifest.fingerprint != client.fingerprint(

"https://example.com"

).fingerprint:

manifest =client.get(

"https://example.com", fresh=True)

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

fingerprint

string

SHA-256 hash of the page's interactive surface — see /fingerprint

cache_status

"hit" | "miss"

Whether this response came from cache or a live scan

requested_country

string or null

The country param you sent, echoed back. null if not provided.

served_locale

string or null

Best-effort detected locale of the page actually captured (e.g. "en-DE"). null if no locale signal was found.

locale_mismatch

boolean

true if you requested a country and the page served a different locale — the site geo-routed elsewhere despite the request. false if they matched, or if no country was requested.

captured_at

ISO-8601 timestamp

When the page was actually scanned

expires_at

ISO-8601 timestamp

When this cached manifest expires

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 arrays of strings | omitted

Groups of action ids that must be satisfied before this one, ANDed together — any one id within a group satisfies that group (OR). A plain required field is a one-item group. Example: [["size-5","size-6"], ["color-red","color-blue"]] means one size AND one color. Omitted when an action has no dependencies.

Groups of action ids that must be satisfied before this one, ANDed together — any one id within a group satisfies that group (OR). A plain required field is a one-item group. Example: [["size-5","size-6"], ["color-red","color-blue"]] means one size AND one color. 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

Cache & Freshness

Manifest caches page extractions for 6 hours to keep repeated requests fast and cheap. Every response tells you exactly how fresh that data is:

Field

Type

Description

cache_status

"hit" | "miss"

Whether this response came from cache or a live scan

captured_at

ISO-8601 timestamp

When the page was actually scanned

expires_at

ISO-8601 timestamp

When this cached manifest expires (captured_at + 6h)

By default, requests may return a cached manifest up to 6 hours old. If your workflow involves forms, pricing, inventory, or any destructive action, pass fresh=true to bypass the cache and force a live scan — this counts as one billable API call.

JSON


// Request

POST /manifest

{ "url": "https://example.com/checkout", "fresh": true }


// Response

{

"cache_status": "miss",

"captured_at": "2026-07-26T14:32:07Z", "expires_at": "2026-07-26T20:32:07Z", "current_page_state": { ... }, "actions": [ ... ]

}

Rule of thumb: use the default cache for read-heavy or exploratory calls. Use fresh=true whenever the agent is about to submit a form, complete a purchase, or take any action where a stale DOM could cause a bad outcome.

omfang logo

Follow us on social media

Contact us

Learn more about omfang

omfang logo

Follow us on social media

Contact us

Learn more about omfang

omfang logo

Follow us on social media

Contact us

Learn more about omfang