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
- An API key from Settings → API Keys. Keys start with
vd_sk_. See API keys. - Credits on your account. See Credits and billing.
https://api.valendata.com. Send your key in either header:
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
CallGET /v1/skill-creations/{creation_id} every few seconds. status moves through these values:
- 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.
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 setwebhook_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_urlmust usehttporhttpsand point to a public host. Private, loopback, and internal addresses are refused with422. Usehttps.- Delivery times out after 15 seconds. Redirects are not followed.
- A network error or a
5xxresponse is retried up to two more times, a few seconds apart. A4xxresponse is not retried. - Return any
2xxstatus quickly, then do your work.
GET /v1/webhooks/secret:
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:
v1= followed by the hex HMAC-SHA256 of "<timestamp>.<raw body>", keyed with your secret. To verify a delivery:
- Compute the signature over the raw request body. Do not parse and re-serialize the JSON first.
- 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. - 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 adiagnosis. 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.severityiswarn,triage_kindispartial_fields, andsuggested_hint_exampleshas hints you can pass toimprove.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.severityisinfo,triage_kindisnot_shown, and there are no hints.
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
- A field that is only a new name for one it already has (say
starswhen the skill hasrating) is merged into the existing field. It is listed indetails.merged. - A field that is unclear comes back as a question in
details.ambiguousand 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
-
Plan. Valendata picks the fields to work on and how. In
fixmode, 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. Inadd_fieldsmode, it checks your request against the skill’s fields first, as described above. -
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. - Check. One short run (up to 5 rows) tests the new version.
-
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.
outcomesays 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, andoutcomenames the values it ignored.
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.
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:fetch_direct: one HTTP request, with no browser.fetch_browser: the same request, made from inside a browser.html_direct: the list page’s HTML, downloaded with no browser, through the skill’s own proxy, without your cookies.replay: the full recorded steps in a browser.
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:- Triage. It sorts the failure into a kind, such as
site_down,blocked,logged_out,page_not_loaded,element_changed, orextraction_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. Alogged_outfailure also sends you a notice to sign in again. - Code fixes first. Only
element_changedandextraction_driftfailures 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"). - 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. - 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.
Errors
Errors return JSON with adetail field.
A
422 for bad inputs lists every problem and the inputs the skill accepts:
429 because too many runs are in progress has a Retry-After header and a structured body:
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_resultsgoes up to 500.0returns 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_chargedandcost_usd. A request refused before it starts (401,403,404,422, or429) 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.

