manifest-api
Stop guessing selectors. Manifest tells your agent what's clickable, fillable, and submittable on any page.
Python SDK for the Manifest API — extracts structured action manifests from web pages so AI agents know what they can do, not just what's on screen.
| Raw browser access | Content extraction | Manifest | |
|---|---|---|---|
| Gives agents a browser | ✓ | ✗ | ✓ |
| Returns page content | ✓ | ✓ | ✓ |
| Returns available actions | ✗ | ✗ | ✓ |
| Required fields, input types | ✗ | ✗ | ✓ |
| Survives UI redesigns | ✗ | ✓ | ✓ |
Install
pip install manifest-api
Quickstart
Sync
from manifest_api import ManifestClient
client = ManifestClient(api_key="your-key") # or set MANIFEST_API_KEY env var
manifest = client.get("https://example.com")
print(manifest.current_page_state)
print(manifest.actions)
# Convenience helpers
action = manifest.action("submit-form")
inputs = manifest.actions_of_type("input")
required = manifest.required_actions
# Cheap structural-change check (no LLM call, never cached)
fp = client.fingerprint("https://example.com")
print(fp.fingerprint)
Async
import asyncio
from manifest_api import AsyncManifestClient
async def main():
async with AsyncManifestClient(api_key="your-key") as client:
manifest = await client.get("https://example.com")
print(manifest.current_page_state)
asyncio.run(main())
LangChain
pip install "manifest-api[langchain]"
from manifest_api.integrations.langchain import create_manifest_tool
tool = create_manifest_tool() # reads MANIFEST_API_KEY from env
# or: create_manifest_tool(api_key="...")
# use with an agent, e.g.:
from langchain.agents import create_agent
agent = create_agent(model=..., tools=[tool])
create_manifest_tool wraps AsyncManifestClient as a get_manifest(url) StructuredTool — the API key is bound at creation time, so the LLM never sees it.
All methods
# Both ManifestClient and AsyncManifestClient expose:
manifest = client.get("https://example.com") # POST /manifest → Manifest
manifest = client.get("https://example.com", country="US") # POST /manifest → Manifest, Accept-Language: en-US,en;q=0.9
manifest = client.get("https://example.com", storage_state="storage_state.json") # authenticated fetch, see below
fp = client.fingerprint("https://example.com") # POST /fingerprint → Fingerprint
health = client.health() # GET /health → dict
valid = client.session_valid() # GET /session-status → bool
Manifest helpers
manifest.action("id") # → Action | None
manifest.actions_of_type("input") # → list[Action]
manifest.required_actions # → list[Action]
manifest.blocked_actions([...]) # → list[Action], see "Action dependencies" below
manifest.fingerprint # → str | None, same hash `fingerprint()` returns
Fingerprint
fingerprint(url) runs the same page-render + extraction pipeline as
get(url) but skips the LLM translation step, returning just a stable hash
of the page's interactive surface (Fingerprint.fingerprint). Useful for
cheaply polling whether a page's action surface has changed since your last
get() call, without paying for another manifest generation. Every Manifest
also carries this same hash on manifest.fingerprint, so you can compare it
against a later fingerprint() call directly.
fp = client.fingerprint("https://example.com")
print(fp.url, fp.fingerprint)
Geo/locale
manifest = client.get("https://example.com", country="US") # ISO 3166-1 alpha-2
print(manifest.requested_country, manifest.served_locale, manifest.locale_mismatch)
country sets the scan's Accept-Language header to steer server-side
geo/locale redirects (e.g. a store redirecting to a different country's
pricing). This is a first attempt, not a guarantee — some sites geo-route on
IP and ignore Accept-Language entirely, no proxy routing yet. Check
manifest.locale_mismatch: True means a country was requested and the
page actually captured (manifest.served_locale, best-effort from the URL)
didn't match — an honest flag, not corrected pricing/currency data.
Authenticated pages
If your agent already holds a logged-in session for the target site, pass its
Playwright storage_state so the scan perceives the authenticated view instead
of the logged-out wall:
# a dict, or a path to a storage_state JSON file
manifest = client.get("https://app.example.com/dashboard", storage_state="storage_state.json")
manifest = client.get("https://app.example.com/dashboard", storage_state={"cookies": [...], "origins": [...]})
Capture storage_state from your own browser automation once it's logged in:
context.storage_state(path="storage_state.json") # Playwright
What changes for an authenticated request:
manifest.cache_statusis"bypass"— authenticated responses are never read from or written to the shared cache, so one caller's logged-in manifest can't be served to another. (freshis implied; you don't need to pass it.)manifest.session_domainslists the domains/origins yourstorage_stateis scoped to (names only, never cookie values) — a quick check that you didn't hand over a broader session than the task needs.- A stale or invalid session raises
APIError(HTTP 503) with a message that says the supplied session failed — it never silently falls back to an anonymous fetch and hands you a logged-out manifest you can't distinguish from a real one.
The caller supplies an already-valid session — Manifest does no login automation, credential capture, 2FA/challenge handling, or session refresh, and stores no credential profiles.
Privacy note. An authenticated page's content — which may include PII — is
sent to Anthropic for the extraction step, the same path every get() call
uses. Manifest does not persist page content.
Security note. A supplied storage_state is held in memory for that one
request only. It is never written to disk, a database, a cache, or a queue; it
is scrubbed from request logs, error responses, and traces; and it is dropped
when the request's browser context closes.
Authenticated fetch is available in this Python SDK only. The JavaScript SDK has no
storageStateparameter yet.
Action dependencies
Some actions are disabled until others are completed (e.g. a submit button
gated on required fields). Action.requires lists the ids of actions that
must be completed first; blocked_actions() filters a manifest down to
actions not yet unblocked by a given set of completed ids:
completed = ["email-input"]
still_blocked = manifest.blocked_actions(completed) # actions still waiting on something
requires is inferred by the LLM translation step from DOM signals (disabled
attributes, aria-disabled, form field proximity) — it's best-effort, not a
guaranteed-accurate dependency graph, and won't capture custom JS validation
logic.
Action types
button · input · textarea · select · checkbox · radio · other
Locators
Each action may carry a locator with css, role, and name — enough to
find and act on the underlying element without guessing a selector yourself.
It's best-effort: locator is None if no element on the page plausibly
matched the action.
Prefer role/name over css where possible — they hold up better across
redesigns, since css can be a brittle positional fallback when the element
has no id or name attribute.
action = manifest.action("continue")
if action.locator and action.locator.css:
page.click(action.locator.css)
Error handling
from manifest_api import AuthenticationError, RateLimitError, APIError
try:
manifest = client.get("https://example.com")
except AuthenticationError:
print("Check your API key")
except RateLimitError:
print("Slow down — rate limit hit")
except APIError as e:
print(f"Server error {e.status_code}")
Docs
Metadata
Release files for manifest-api 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| manifest_api-0.4.0.tar.gz | 11.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| manifest_api-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 21.3 kB
Release files / manifest_api-0.4.0.tar.gz
| Download URL | manifest_api-0.4.0.tar.gz |
|---|---|
| Size | 11.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7dae3e1f4043d1ccb761082d81a9b810b3f3cbe4de73b9ca09e9f97e3c665eab
|
|
BLAKE2b-256 checksum How to use checksums |
5cee82d60f7ca46f9b53a9f4b02ad23200e7277b74eaa06ee0f3f2b9df184eba
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.1
|
Release files / manifest_api-0.4.0-py3-none-any.whl
| Download URL | manifest_api-0.4.0-py3-none-any.whl |
|---|---|
| Size | 10.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a76b64b0b1f4bc0fb2dff7ca6781eb588ae7fc9d7063e87946b4b5af4358bfaf
|
|
BLAKE2b-256 checksum How to use checksums |
ab15e62fafabdf4e4ac51ff55fda8d4768da9edae4b7265939cdeacb78a105f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.1
|