cronvoy
Get a key

Cronvoy API

v1 · August 13, 2026

Everything the dashboard does, your code can do: launch a browser run, put one on a schedule, set up a job-application agent, read back what it did. Point an AI assistant at it and it can operate Cronvoy on your behalf.

authquickstartrunsschedules agentsprofilesrecipeslimitserrors

Authentication

Create a key in the dashboard under Settings → API keys. It is shown once. Send it as a bearer token:

curl https://cronvoy.com/api/v1/status \
  -H "Authorization: Bearer cv_live_..."

A key acts as your account and nothing more. Every request is carried out as you against the same database rules your dashboard obeys, so a key can never read or touch another account's data. What a given key may do on your account is decided by the scopes you ticked when you made it:

ScopeGrants
runs:readRun history, transcripts, the live view
runs:writeLaunch, stop and continue runs
schedules:readList schedules
schedules:writeCreate, edit, pause and trigger schedules
schedules:deleteDelete schedules
agents:readAgents and their records
agents:writeCreate and configure agents, edit records
agents:deleteDelete agents and records
profiles:readProfiles, which sites they can sign into, document list
profiles:writeCreate profiles, upload documents

Two things the API will never do: return a saved password (they are encrypted in your browser and only the runner can open them), and accept one. Add logins in the dashboard. Keys also cannot mint or revoke other keys — that takes a signed-in session, so revoking a leaked key actually ends its access.

Quickstart

Launch a cloud run and watch it:

curl -X POST https://cronvoy.com/api/v1/runs \
  -H "Authorization: Bearer $CRONVOY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt":"Go to news.ycombinator.com and list the top 3 stories"}'

# { "queueId": "…", "target": "cloud", "status": "queued", … }

curl "https://cronvoy.com/api/v1/live" -H "Authorization: Bearer $CRONVOY_KEY"
# { "active": true, "status": "running", "action": "reading the page", "stepNo": 4, "log": [ … ] }

Runs

EndpointWhat it does
GET /api/v1/runsHistory, newest first. ?limit= (max 100), ?source=cloud|extension|scheduled.
POST /api/v1/runsLaunch. { prompt, target?, profileId?, agentId? }target is cloud (default, runs 24/7 on our servers) or extension (runs in your own Chrome, which must be open).
GET /api/v1/runs/{id}One run with its transcript and diagnostics. ?transcript=0 to skip the transcript.
POST /api/v1/runs/{id}/stopStop the live run. Takes effect at the next step checkpoint, usually a few seconds. {id} may be current.
POST /api/v1/runs/{id}/continue{ prompt } — a follow-up that reuses the profile session and the prior run's context, so it is still signed in and remembers what happened.
POST /api/v1/runs/{id}/respond{ input } — answer a run that paused for you (a 2FA code, an approval).
GET /api/v1/liveWhat the current run is doing. ?frame=1 adds a JPEG screenshot as base64 (large — leave it off when polling).

A run that pauses for you reports status: "waiting" with an awaiting object on /live. Answer it with /respond and it picks up where it left off.

Using curl for an action with no body? Send an empty object: curl -X POST … -d '{}'. A bare curl -X POST sends no Content-Length at all, and the web server's firewall answers that with an HTML 406 before it ever reaches the API. Ordinary HTTP clients (fetch, requests, axios) always send the header, so this only bites at the command line.

Schedules

A schedule is a prompt plus a cadence. Cloud schedules fire on our servers around the clock; extension schedules fire in your Chrome.

curl -X POST https://cronvoy.com/api/v1/schedules \
  -H "Authorization: Bearer $CRONVOY_KEY" -H "Content-Type: application/json" \
  -d '{
    "prompt": "Check my Loops dashboard for a draft campaign and tell me if one is ready",
    "every": "daily",
    "at": "06:30",
    "tz": "America/New_York",
    "target": "cloud"
  }'

every takes "hourly", "6h", "daily", "weekly", or a number of minutes (60 minimum). at is the local anchor time: an interval schedule is phase-locked to it, so two agents can be set an hour apart and alternate forever. For specific weekdays send days instead: {"days":["mon","wed","fri"],"at":"09:00"} — names or numbers, 0 (Sunday) to 6.

EndpointWhat it does
GET /api/v1/schedulesAll of them, next-to-run first, with a plain-English cadence and each one's memory (doneCount, progressCursor).
POST /api/v1/schedulesCreate. Also accepts profileId and agentId.
PATCH /api/v1/schedules/{id}Edit anything, including {"enabled": false} to pause and true to resume. Changing the cadence recomputes the next run.
DELETE /api/v1/schedules/{id}Delete it. Its memory of what it already handled is kept, so a future schedule doesn't redo that work.
POST /api/v1/schedules/{id}/run-nowFire it immediately. The regular cadence is untouched — this is an extra run, not a replacement.

Agents

An agent is a preset with settings it carries into every run — the job-application agent knows your titles, salary, work authorization and EEO answers, and shares one record ledger across all your agents so it never applies to the same job twice.

# what you can create, and every setting it takes
curl https://cronvoy.com/api/v1/agent-templates -H "Authorization: Bearer $CRONVOY_KEY"

# create one
curl -X POST https://cronvoy.com/api/v1/agents \
  -H "Authorization: Bearer $CRONVOY_KEY" -H "Content-Type: application/json" \
  -d '{
    "templateId": "job-apply",
    "name": "Product Manager Job Agent",
    "settings": {
      "titles": ["Product Manager", "Senior Product Manager"],
      "locations": "Remote (US)",
      "yearsExperience": "8",
      "workAuth": "US citizen",
      "perRun": 3
    }
  }'

The response lists missingRequired if anything the forms will ask for is still blank. Then read what it did:

curl "https://cronvoy.com/api/v1/agents/{id}/records?status=applied&limit=20" \
  -H "Authorization: Bearer $CRONVOY_KEY"
EndpointWhat it does
GET /api/v1/agent-templatesTemplates and their full field list.
GET /api/v1/agentsYour agents. ?full=1 adds the exact instructions a run receives.
POST /api/v1/agents{ templateId, name?, settings? }
PATCH /api/v1/agents/{id}Merges settings — patch one field without clearing the rest.
GET /api/v1/agents/{id}/recordsThe ledger. ?status=, ?archived=1, ?limit=.
PATCH …/records/{recId}Correct a company or role, change a status, or archive a row.

Profiles

A profile is the identity a run wears: its own logins, its own browser session, its own documents. Use separate profiles to keep two accounts on the same site from mixing.

EndpointWhat it does
GET /api/v1/profilesAll profiles.
POST /api/v1/profiles{ name, context? }context is a persona note handed to every run using it.
POST …/{id}/duplicate{ name?, includeLogins?, includeSession?, includeDocuments? } — a second identity that starts out already signed in. Saved passwords and the browser session are copied as sealed ciphertext (the API cannot read them and never returns them). It is a snapshot: each profile saves its own session from the next run on. Don't run both at once — one account in two browsers simultaneously is what bot detection looks for.
GET /api/v1/profiles/{id}/connectionsWhich sites this profile can sign into, and which sessions are live. Usernames only, never passwords.
GET /api/v1/profiles/{id}/documentsUploaded files. ?data=1 on a single document returns its base64.
POST /api/v1/profiles/{id}/documents{ name, type, dataB64 }, up to 700 KB. A run attaches it by name.

Recipes

Set up a job agent and let it run every morning

POST /api/v1/profiles     { "name": "Job hunt" }                        → profileId
POST /api/v1/profiles/{profileId}/documents  { name: "resume.pdf", … }
POST /api/v1/agents       { "templateId": "job-apply", "settings": {…} } → agentId
POST /api/v1/schedules    { "prompt": "Apply to product roles that fit me",
                            "agentId": "…", "profileId": "…",
                            "every": "daily", "at": "08:00", "tz": "America/New_York" }

Pause everything

GET   /api/v1/schedules
PATCH /api/v1/schedules/{id}   { "enabled": false }     # for each

Did last night's run get anywhere?

GET /api/v1/runs?limit=1
GET /api/v1/runs/{id}?transcript=0     # status, summary, and a diagnostics block

Limits

Errors

Errors are JSON, with a stable error code to branch on and a message to read:

{ "error": "missing_scope", "message": "This API key does not have the `runs:write` scope.",
  "needed": "runs:write", "has": ["runs:read"] }
CodeMeaning
401 bad_keyUnknown, malformed or revoked key.
403 missing_scopeThe key wasn't granted that permission.
404 not_foundNo such run, schedule, agent or profile on this account.
409 still_running / pausedThe action doesn't apply in that state.
429 rate_limitedToo many requests; Retry-After says how long.
429 run_budget_dayThis key's run budget is spent. Resets 00:00 UTC.

Questions or something missing: hello@cronvoy.com.