Pricing and limits
Flat quotes of €7, €19 or €49 held on your euro wallet and kept only on a verified fix; three free fixes; top-ups on 402; per-user caps and timeouts.
Resolve prices the outcome, not the tokens — no fix, no fee. Triage quotes one of three flat tiers; you pay that amount if — and only if — the job ends resolved with an attestation. Declines, failures and infrastructure errors are always €0, and your first 3 verified fixes are free.
Tiers
| Tier | Price | Typical shape |
|---|---|---|
t7 | €7 | Small, well-scoped bug: single file, clear repro. |
t19 | €19 | Moderate: multi-file, needs investigation. |
t49 | €49 | Hard but plausible: subtle, cross-cutting. |
free | €0 | Your first 3 verified fixes, whatever their difficulty. |
Prices are in euro, the same €7 / €19 / €49 shown on codai.ro/pricing. Paid jobs are held on your prepaid codai wallet, which is kept in EUR. No exchange rate is applied anywhere in the pipeline.
Money fields on the wire are integers in micros and keep their historical _micro_usd names for API stability: quote_micro_usd: 19000000 is a 19.00 quote, which holds €19 on your wallet.
The free tier
3 verified fixes per user, across all your keys. It is claimed atomically when a quotable job arrives: the job is created already accepted with tier: "free" and quote_micro_usd: 0, and free_tier_remaining tells you what is left afterwards. A free job that ends failed or error returns its slot. Once the three are used, quotable jobs come back as quoted and wait for your accept.
Free jobs run a single fix candidate (greedy); paid jobs may fan out up to four — see How it works.
How you pay: a wallet hold
Accept places a hold
POST /v1/resolve/jobs/{id}/accept debits the quote from your wallet in the same transaction that moves the job to accepted. If the debit cannot be made, the status change is rolled back too — you never have a running job without the funds held, nor funds held for a job that did not start. Free-tier jobs skip this step entirely.
Verified fix: the hold becomes the charge
When the job resolves, the worker writes the attestation row first and only then records the charge against the hold. The debit simply stays on your wallet — there is no second payment, no card charge and no separate invoice line for Resolve. No attestation, no charge: the order is enforced in code.
Anything else: a full refund
A job that ends failed (no verified fix within budget), error (infrastructure, including a worker lost and swept by the watchdog) returns the whole held amount to your wallet as a refund entry. Refunds are idempotent per job, so a retried settlement never pays you twice or charges you twice.
Not enough balance: 402 and a top-up link
If your wallet cannot cover the quote, accept answers 402 and the job stays quoted — nothing is debited:
{
"error": "insufficient_funds",
"balance": 5000000,
"required": 19000000,
"currency": "micro_eur",
"topup_url": "https://pay.codai.ro/checkout?topup=14"
}balance and required are EUR micros. topup_url opens the pay.codai.ro checkout for the shortfall rounded up to whole euros, clamped to the checkout bounds of €10 minimum and €1000 maximum. Top up, then call /accept again on the same job id. The GitHub Action posts this link on the issue for you.
For a t7 job the 402 also carries "bundle_url": "https://pay.codai.ro/checkout?bundle=resolve10" — buying the fix bundle is an alternative to topping up.
Intro price
Your first paid fix costs €1. On your first paid accept — any of t7, t19 or t49 — Resolve holds €1 instead of the list price. While you are eligible, the 201 quote says so:
{ "status": "quoted", "tier": "t19", "quote_micro_usd": 19000000,
"intro_price_micro_eur": 1000000, "list_price_micro_eur": 19000000 }- The intro is used up only by a verified fix. If that job ends
failedorerror, the €1 is refunded like any hold and the intro is offered again on your next paid accept. - You are eligible while you have no verified paid job and no other intro job in flight (
accepted/running) — so two parallel accepts cannot both get €1. - The accept response reports
"paid_with": "intro"and"held_micro_eur": 1000000.
Fix bundle
€49 buys 10 fix credits — about €4.90 per t7 fix instead of €7. Buy it at pay.codai.ro/checkout?bundle=resolve10.
- Credits pay only for
t7jobs.t19andt49always use the intro price or your wallet. - A credit is taken at accept instead of a wallet hold (
"paid_with": "credit","held_micro_eur": 0), returned on failure or error, and consumed only by a verified fix. GET /v1/resolve/balancereturnsfix_credits,intro_availableandwallet_micro_eur.
Which one pays: precedence
At accept Resolve picks the first that applies: free tier > intro price > fix credit (t7 only) > wallet. paid_with on the accept response (and on GET /v1/resolve/jobs/{id} once accepted) tells you which was used. One exception protects you from a needless 402: if the €1 intro hold does not fit in your wallet but a credit is usable on that t7 job, the credit is used instead.
You can see the upstream model cost per job (not what you pay — what Resolve spent) as cost_micro_usd on GET /v1/resolve/jobs/{id}; it is the sum of gateway usage under the job's session ids.
We never spend more than 2× your quote on model calls for one job (a fixed $2 ceiling for free jobs). That ceiling is ours to absorb — it never changes what you pay.
Limits
All limits are per user, counted across every key you own.
| Limit | Value | What you see when you hit it |
|---|---|---|
| Submissions | 6 per minute | 429 rate limited: too many resolve submissions, retry in a minute |
Jobs in flight (accepted / running) | 2 | 429 too many resolve jobs in flight, wait for one to finish |
| Jobs per day | 10 | 429 daily resolve job cap reached, retry tomorrow |
| Free verified fixes | 3, lifetime (a failed or errored free job does not use one) | Quotes come back quoted instead of accepted |
| Wallet balance at accept | ≥ the quote, in EUR | 402 insufficient_funds with topup_url; the job stays quoted |
| Issue text | 50 000 characters (the Action truncates) | — |
Caps are checked before triage, so a rejected submission costs neither a slot nor a triage call. The day resets at 00:00 UTC; every 429 body is the flat { "error": "<message>" } shown above, with no Retry-After header.
Timeouts
| Budget | Value | Effect |
|---|---|---|
| Whole job | 30 minutes | Past it the job ends failed (no verified fix within budget) — €0. |
| One sandbox run (setup + tests) | 15 minutes | A slow suite ends the candidate; the job may try the next one. |
| Agent turns per candidate | 4 | Edits beyond that are not attempted. |
| Fix candidates | 1 (free) / 4 (paid) | First verified pass wins; the rest are skipped. |
| Orphan watchdog | job budget + 15 minutes | A job whose worker died is marked error (watchdog: worker lost — not billed, safe to resubmit) and a free slot is returned. |
| GitHub Action polling | 25 minutes | The Action stops waiting and comments the last status; the job itself continues to its own budget. |
Triage runs inline in your POST and is bounded by the gateway's request timeout; a slow triage returns declined with triage unavailable, please retry rather than hanging.
Supported repositories
- Public
https://github.com/<owner>/<repo>only. Private repos, other hosts and monorepo sub-paths are rejected at submission with a400. - Python with pytest, or JavaScript/TypeScript with vitest, jest or
node:test(detected frompackage.json). - The repo's suite must run from a clean clone plus its committed dependency manifest. Tests that need secrets, running services or system packages beyond the runner image usually end in
declinedorerror.
Last updated:
How it works
The Resolve job lifecycle from triage to attestation, what the public proof document actually proves, and what the sandboxed runner will and will not touch.
API reference
How to read the reference — base URLs, authentication, the error envelope, SSE conventions and where changes are announced.