Skip to main content
A skill is a website task you can call like an API. You describe the task once, for example “search for dentists in Austin and list them”. Valendata does it in a browser, records how, and publishes it as an endpoint with typed inputs and a fixed output shape. After that, every call runs the same task with your inputs and returns rows in the same shape. Skills run on your behalf, with your permission. A skill runs either in your own logged-in browser through the Valendata extension, or in a Valendata cloud browser. When a site changes, Valendata repairs the skill and saves the repair as a new version. You can see every version and roll back. See How Valendata keeps skills working.
You are responsible for having the right to access the sites and data your skills use. Follow each site’s terms of use and robots rules, and keep your request rates reasonable. See the Terms of Service.

Before you start

Every request in this guide goes to https://api.valendata.com. Send your key in either header:
The examples read the key from the VALENDATA_API_KEY environment variable.

1. Create a skill from a task

POST /v1/skills starts recording your task in a cloud browser. It answers at once with 202 Accepted and a creation_id. Recording usually takes a few minutes. Input and output names are converted to snake_case. For example, Phone number becomes phone_number. Send an Idempotency-Key header so a retried request does not start a second recording. The same key from the same account returns the creation that is already running, with idempotent_replay: true.
202 Accepted

Poll until it is ready

Call GET /v1/skill-creations/{creation_id} every few seconds. status moves through these values:
Limits on skill creation:
  • You can have up to 2 recordings in progress at once. A third returns 429.
  • You need at least 10 credits to start. Otherwise you get 402.
  • A recording that has not finished within 30 minutes is marked failed. Start it again.
  • Recordings run in a fresh cloud browser and cannot sign in to websites. For a task that needs your login, record it in the app with your own browser. See Recording a skill.

2. Inspect the skill

GET /v1/skills/{slug} returns the skill and its contract. A public skill needs no key. A private skill is visible only to people who may run it, and returns 404 to everyone else.
The fields you need most:
Excerpt

3. Run it and wait for rows (sync)

POST /v1/skills/{slug}/run runs the skill and answers when it finishes. Use it for quick skills and interactive use. Put the skill’s inputs at the top level of the JSON body. You can also pass them as query parameters. These keys are run settings, not inputs: Inputs are checked against input_schema before anything runs. A missing required input, a value of the wrong type, or an unknown key returns 422, and the response lists every problem.
200 OK
status is success, partial, or failed. Cost. A run that uses a cloud browser can answer before the browser’s time is billed (the browser is billed when it closes). While that is pending, cost_final is false: credits_charged is what has been billed so far and cost_estimate_credits adds an estimate of the browser time still to come, at the current browser rate. Read the run again with GET /v1/runs/{run_id} to get the final total, where cost_final is true and cost_estimate_credits equals credits_charged. A run that used no cloud browser has cost_final: true straight away. The other fields tell you how much to trust the rows: If your account already has as many runs in progress as it is allowed, a sync call does not wait. It returns 429 with a Retry-After header. Use an async run when you would rather queue.

4. Run it in the background (async)

POST /v1/skills/{slug}/runs starts a run and answers at once with 202 Accepted and a run_id. If your account is at its limit of runs in progress, the run waits in a queue instead of failing. A run that waits longer than about an hour fails, and you are not charged. Body fields: Send an Idempotency-Key header to make retries safe. The same key from the same account returns the run that already started, with idempotent_replay: true.
GET /v1/runs/{run_id} returns status (queued, running, success, partial, failed, or cancelled). When the run is finished, it also returns the result. The result has the same fields as a sync run, except that the rows are in rows (not data). It also includes run_id, skill_id, skill_slug, and created_at. You can read a run if you started it or if you own the skill.

Webhooks

If you set webhook_url, Valendata sends the finished run to it as a POST request with a JSON body. The body is the run result: run_id, status, rows, count, execution_time_ms, error, credits_charged, cost_usd, cost_final, cost_estimate_credits, and the trust and version fields described above.
  • webhook_url must use http or https and point to a public host. Private, loopback, and internal addresses are refused with 422. Use https.
  • Delivery times out after 15 seconds. Redirects are not followed.
  • A network error or a 5xx response is retried up to two more times, a few seconds apart. A 4xx response is not retried.
  • Return any 2xx status quickly, then do your work.
Each delivery is signed. Get your signing secret once with GET /v1/webhooks/secret:
Reading it with an API key needs the webhooks:read scope. If the secret leaks, rotate it with POST /v1/webhooks/secret/rotate (scope webhooks:write) or from Settings → API Keys. For 24 hours after a rotation, deliveries are signed with both the new and the old secret. Every delivery has these headers:
The signature is v1= followed by the hex HMAC-SHA256 of "<timestamp>.<raw body>", keyed with your secret. To verify a delivery:
  1. Compute the signature over the raw request body. Do not parse and re-serialize the JSON first.
  2. Split the header on commas (for 24 hours after a rotation it holds v1=<new>,v1=<old>) and compare each value using a constant-time comparison. Accept the delivery if any one matches.
  3. Reject deliveries whose timestamp is more than 5 minutes away from now. This stops replays.

5. Improve a skill

You can change a skill the way you would coach a person: tell it, in plain English, what you want. One request, POST /v1/skills/{slug}/improve, does three jobs. Pick one with mode:

Read the diagnosis

A run that failed, that came back with a field on fewer than half the rows, that left a field blank on a few rows (see Fields blank on a few rows), or whose rows look wrong, carries a diagnosis. It is on the sync result, on GET /v1/runs/{run_id}, on a failed GET /v1/skill-creations/{creation_id}, and on the MCP tool result.

Fields blank on a few rows

A field most rows filled (half or more) but a few left blank is checked against the page the run saw. There are two cases:
  • filled on 3/5 — on the card but not read on 2: the value is on those rows’ cards and the skill missed it. severity is warn, triage_kind is partial_fields, and suggested_hint_examples has hints you can pass to improve.
  • filled on 3/5 — not shown on 2 rows' cards: those cards show no value, for example a sponsored result with no accessibility icon. Nothing is wrong. severity is info, triage_kind is not_shown, and there are no hints.
A field every row filled is never listed. A field blank on a few rows where the page cannot show why is not raised on its own.

Start an improvement

POST /v1/skills/{slug}/improve answers at once with 202 Accepted and an improvement_id. Only the skill’s owner or an editor of its workspace can improve it. Drafts are allowed, so you can fix a skill that failed its first check. Send an Idempotency-Key header to make retries safe: the same key returns the same improvement.

Fix a missing field

Add new fields

Before it learns anything, Valendata compares your request with the fields the skill already has:
  • A field that is only a new name for one it already has (say stars when the skill has rating) is merged into the existing field. It is listed in details.merged.
  • A field that is unclear comes back as a question in details.ambiguous and is not learned. Answer the question in a new request.
  • Only fields that are really new are learned. They are listed in details.new_fields.

Re-learn the detail steps

Poll until it ends

Python

What happens

  1. Plan. Valendata picks the fields to work on and how. In fix mode, if the skill returns rows but misses fields, it relearns where those fields live; if it returns no rows at all, an AI agent redoes the steps, guided by your hint. In add_fields mode, it checks your request against the skill’s fields first, as described above.
  2. Learn. A cloud browser opens the site and relearns the skill: the page itself, the per-result details page or panel, and any “show more” step. When the value is only part of an element’s text, for example the 99.7% in “99.7% positive (14.8K)”, the skill learns to keep just that part, even when that text sits in a different place on each card. The change is saved as a new recipe version, tagged with the improvement’s id and mode, so nothing is lost.
  3. Check. One short run (up to 5 rows) tests the new version.
  4. Keep the better one. The new version stays live only if it is better:
    • fix: the target fields fill on more rows, and no other field broke.
    • add_fields: the new fields fill in the check run.
    • relearn_details: the detail fields fill at least as well as before.
    If not, the attempt is saved as a candidate version (not live), the old version stays live, and outcome says why. A draft skill whose check passes goes live. “Fill” means a real value: placeholder text, a label (such as “Buy It Now” in a feedback column), a value of the wrong shape for the field, or one word repeated on most rows does not count, and outcome names the values it ignored.
Poll GET /v1/skill-improvements/{improvement_id}. status moves through queued, working, validating, then done or failed.

See what has changed

GET /v1/skills/{slug}/improvements?limit=20 returns the skill’s change history, newest first (limit 1–50, default 5). It holds every fix, added field and re-learn by anyone who can edit the skill (its owner or a workspace editor), whether started from the API, MCP, Igris, the web app, or the workflow Brain chat. source and started_by_name say where and who. Past Refine and Re-learn changes from before are included too, with imported: true and result: "applied". Limits on improvements: one improvement at a time for your account, one at a time for each skill (409 while one runs), up to 6 an hour (429), and at least 10 credits to start (402). Every mode runs a cloud browser, the AI model, and the check run; all of it is charged to you and reported in credits_charged.

Versions

Every change to a skill’s recipe is saved as a numbered version: the first recording, each repair, each edit, and each restore. Each version records the reason it was made and the run that caused it. To run a particular version for one call, without pinning, pass version: in the query or body of a sync run, in the body of an async run, or as the version argument of a skill_<slug> MCP tool. Without it, a run uses the pinned version if there is one, otherwise the live version.
While a skill is pinned, automatic repairs do not replace the version it runs. They are saved as candidate versions for you to review, diff, and restore if you want them.

Health

GET /v1/skills/{slug}/health shows how a skill has been doing. A public skill’s health needs no key. A private skill’s health is visible only to people who may run it.

Fast path

Many websites load their data from their own backend after the page opens. When a skill runs in the cloud browser, Valendata watches for a request that clearly carries the rows the skill returns. If it finds one, it tries calling that request directly, alongside normal runs. If the results keep matching, that request becomes the skill’s fast path, and is saved as a new version. Some websites send the rows inside the page itself instead. For these, Valendata checks whether the list page can be downloaded without a browser and read the same way. The page address must follow the skill’s inputs, and the rows must match. If they keep matching, the page download becomes the fast path. After that, a run tries the fast path first:
  1. fetch_direct: one HTTP request, with no browser.
  2. fetch_browser: the same request, made from inside a browser.
  3. html_direct: the list page’s HTML, downloaded with no browser, through the skill’s own proxy, without your cookies.
  4. replay: the full recorded steps in a browser.
If the fast path stops working, the run moves to the next step within the same call, so you still get rows. A fast path that keeps failing is paused, and runs use the browser until it is proven again. tier_used on each result tells you which step answered.

Self-healing

Websites change. When a run fails, Valendata works out why before it tries to fix anything:
  1. Triage. It sorts the failure into a kind, such as site_down, blocked, logged_out, page_not_loaded, element_changed, or extraction_drift. Failures caused by the site, access, or sign-in (site_down, blocked, logged_out, page_not_loaded) are not “repaired”. The run fails honestly and tells you why. A logged_out failure also sends you a notice to sign in again.
  2. Code fixes first. Only element_changed and extraction_drift failures are repaired. If a recorded element changed, Valendata first tries to find it again by its saved fingerprint. This is a quick, cheap fix that does not use an AI model (heal_kind: "fp").
  3. Verified AI repair. If that does not work, an AI agent redoes the step (heal_kind: "ai"). The result is accepted only if it is proven on the rows it produces. The rows must exist, must not be empty, must match the output contract, and must not be clearly worse than the skill’s recent good runs. AI repairs are limited per skill per day.
  4. New version. An accepted repair is saved as a new recipe version. You can see it in versions, diff it, and restore an earlier version at any time.
Read more in How Valendata keeps skills working.

Errors

Errors return JSON with a detail field. A 422 for bad inputs lists every problem and the inputs the skill accepts:
A 429 because too many runs are in progress has a Retry-After header and a structured body:
A 429 from the per-key rate limit has Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers.

Limits and billing

  • Request rate per API key: 30 requests a minute on the Free plan, and 120 a minute on paid plans. The limit resets every minute.
  • Runs in progress: each account has a limit on how many runs can be in progress at once. Sync calls over the limit get 429. Async runs queue.
  • Rows per call: max_results goes up to 500. 0 returns the whole list.
  • Skill creation: up to 2 recordings in progress at once, and at least 10 credits to start.
  • Improvements: one in progress per account and per skill, up to 6 an hour, and at least 10 credits to start.
  • Billing: runs use credits for browser time, AI model use, and rows returned, as listed in Credits and billing. Every result reports credits_charged and cost_usd. A request refused before it starts (401, 403, 404, 422, or 429) does not run anything.

Next steps

Connect your AI assistant

Use your skills as tools in Claude, ChatGPT, Meta Muse, or Cursor.

How skills keep working

Versions, triage, repairs, and the fast path.

API reference

Every endpoint and field.

Credits and billing

What each run costs.