Access
Accounts are enterprise only. Avouch is available to AI builder platforms by agreement. Nobody can make themselves a key: POST /v1/accounts answers 403 enterprise_only unless the owner calls it.
To get access, leave a work email in the form on the home page. We reply to every platform personally, agree the terms (including the seat price), and issue your key.
Founders building an app can join the waitlist on the home page. Avouch opens through platforms first.
Try it: the sandbox
Run the whole flow on Avouch's real code, now, with no key. An agent lists an app, Avouch checks it, example sellers are matched, one promotes it, a buyer clicks, the app's checkout reports a signed sale, and Avouch issues the receipt, signed with its real key. Then your browser checks the signature itself, against the published key.
curl -X POST https://<avouch>/v1/sandbox/run -H "Content-Type: application/json" \
-d '{"app_name": "Portrait Pro", "url": "https://portrait.pro",
"tagline": "Studio portraits from one selfie.", "commission": 35}'
→ {"ok": true, "sandbox": true, "livemode": false, "ms": 41,
"steps": [{"say": "An agent lists the app", "call": "POST /v1/listings", "status": 201, …}, …],
"receipt": {"token": "scr_test_…", "signed": "{…\"livemode\":false…}", "signature": "…", "kid": "sc-…"},
"public_key_url": "/.well-known/avouch-receipts.json"}
What keeps it honest
- It shares nothing with the live service. Each run has its own database, made from the live schema with no rows, and deleted after an hour. A sandbox app, seller, click or sale never reaches the marketplace,
find_products, a real seller's matches, payouts, usage or billing. - Nothing is fetched. In the sandbox the site check is simulated, and the step says so. A real listing fetches the site.
- The sellers are fictional examples, each marked
"example": true, and the buyer is simulated. - The receipt is real and says it's a sandbox. It's signed with Avouch's real Ed25519 key, with
"livemode": falseinside the signed text, under ascr_test_token, so it can never pass for a real sale. - Body fields are all optional:
app_name,url,tagline,commission(5 to 70). 20 runs per connection per hour.
Test keys
Under an agreement, your account can have a test key, avk_test_…, beside your live key. Every call made with it runs in your own sandbox: nothing is real and nothing is paid, and each answer says "livemode": false (and the header Avouch-Livemode: false). Report test sales to POST /v1/conversions with the usual signature and Authorization: Bearer avk_test_…, so we know which sandbox it belongs to: a test sale, end to end has every step. A test listing's signing secret comes back when it is made, and from GET /v1/listings/<slug>/manage. Your sandbox's receipt webhooks are delivered (livemode: false), POST /v1/webhooks/test sends one on demand, and Stripe test events go to the sandbox's own endpoint. Signed in to the console with the test key, you see the sandbox. Test data is kept for 30 days after its last use.
A test sale, end to end
With your test key you can run the whole money path yourself, against your own sandbox: make a test listing, get a click from an example seller, send a signed sale, read its receipt and verify it. Nothing is real and nobody is paid; every answer says "livemode": false.
1. A test listing, and its signing secret
curl -X POST https://<avouch>/v1/listings -H "Authorization: Bearer avk_test_…" -H "Avouch-Seat: user-4821" \
-H "Content-Type: application/json" \
-d '{"url": "https://portrait.pro", "name": "Portrait Pro", "tagline": "Studio portraits from one selfie.",
"about": "Portrait Pro turns one selfie into studio headshots. US$29 once.", "commission": 35}'
→ 201 {"ok": true, "created": true, "slug": "portrait-pro", "seat": …, "livemode": false,
"signing_secret": "scsec_…", "signing_secret_note": "Test mode: … shown again at GET /v1/listings/portrait-pro/manage", …}
A test listing has no manage page, so its signing_secret comes back once, here. GET /v1/listings/<slug>/manage with the test key (and the seat) shows it again, with the exact request to send. Keep it on your server. A live key gets 403 test_mode_only there: a live listing's secret is only on its owner's manage page.
2. A click from an example seller
curl -X POST https://<avouch>/v1/listings/portrait-pro/test_click -H "Authorization: Bearer avk_test_…" -H "Avouch-Seat: user-4821"
→ 201 {"ok": true, "click": "ck_…", "seller": {"name": "… (example)", "example": true, …}}
Your sandbox has no real sellers. Without a click, a sale is recorded as unattributed and has no receipt.
3. The signed sale
Sign the exact bytes you send. Python:
import hashlib, hmac, json, time, urllib.request
SECRET = "scsec_…" # the listing's signing secret
body = json.dumps({"product": "portrait-pro", "order_id": "A-1001", "amount": "29.00", "currency": "usd",
"click": "ck_…", "new_customer": True, "charge_id": "pi_test_1"}, separators=(",", ":")).encode()
t = str(int(time.time()))
sig = hmac.new(SECRET.encode(), t.encode() + b"." + body, hashlib.sha256).hexdigest()
req = urllib.request.Request("https://<avouch>/v1/conversions", data=body, method="POST", headers={
"Content-Type": "application/json", "Sharecut-Signature": "t=%s,v1=%s" % (t, sig),
"Authorization": "Bearer avk_test_…"}) # test mode only: the test key says which sandbox
print(urllib.request.urlopen(req).read().decode())
Or with curl and openssl:
SECRET='scsec_…'
BODY='{"product":"portrait-pro","order_id":"A-1002","amount":"29.00","currency":"usd","click":"ck_…","new_customer":true}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST https://<avouch>/v1/conversions -H "Content-Type: application/json" \
-H "Sharecut-Signature: t=$T,v1=$SIG" -H "Authorization: Bearer avk_test_…" --data-raw "$BODY"
→ {"ok": true, "status": "held", "ledger_status": "held", "receipt": "scr_test_a2_14_…", "test": true, "livemode": false, …}
4. The receipt, and checking it
curl https://<avouch>/v1/receipts/scr_test_a2_14_…
python3 verify_receipt.py --issuer https://<avouch> https://<avouch> scr_test_a2_14_…
→ VERIFIED. Signed facts: … status held … livemode False
SANDBOX RECEIPT (livemode false): genuinely signed, but an example from a sandbox. It is not a real sale.
A sandbox receipt lives on the Avouch that made it, so name that host as the issuer (locally, --issuer http://127.0.0.1:8830 http://127.0.0.1:8830 …). Then try a refund (step 5 of Reporting sales), a webhook test delivery (Webhooks), and the console with your test key: its test-mode view shows these listings, sales and receipts.
Stripe in test mode: GET /v1/listings/<slug>/manage gives the sandbox's own Stripe test endpoint, /v1/stripe/test/<sandbox>/<slug>. Add it in your Stripe test dashboard and send its signing secret with POST /v1/listings/<slug>/manage {"stripe_whsec": "whsec_…"} and the test key. Live events are refused there.
API keys
Your key starts with avk_. It is shown once when it is issued and stored only as a hash, so keep it somewhere safe. Send it on every call:
Authorization: Bearer avk_…
A missing or wrong key gets 401 unauthorized. A key only ever sees the listings it made. A test key starts with avk_test_ and only ever sees its own sandbox (the sandbox).
Reissue or revoke a key
- From the console (
/console, signed in with the key): Reissue this key. The new key is shown once on the page. The old key stops at once, every connection made with it (Claude, other OAuth apps) ends, and console sessions made with the old key end. Signed in with the test key, it reissues the test key. - By the owner, for an account:
POST /admin/accounts/<id>/keyissues a new live key (the old one and its connections end), andPOST /admin/accounts/<id>/key/revokestops it with no new key until one is reissued.POST /admin/accounts/<id>/test-keydoes the same for the test key. Write to us if your key leaked and you can't reach the console.
Listings
POST /v1/listings
curl -X POST https://<avouch>/v1/listings \
-H "Authorization: Bearer avk_…" -H "Avouch-Seat: user-4821" -H "Content-Type: application/json" \
-d '{"url": "https://portrait.pro", "name": "Portrait Pro",
"tagline": "Studio portraits from one selfie.",
"about": "Portrait Pro turns one selfie into studio headshots. US$29 once.",
"commission": 35, "face_commission": 45}'
→ 201 {"ok": true, "created": true, "slug": "portrait-pro", "name": "Portrait Pro", "url": "https://portrait.pro",
"status": "live", "seat": "user-4821", "commission_pct": 35, "sellers": 0, "clicks_30d": 0,
"sales": {"held": 0, "unconfirmed": 0, "cleared": 0, "paid": 0, "refunded": 0, "refused": 0,
"returning customer": 0, "unattributed": 0},
"owed_to_sellers_cents": 0, "page": "https://<avouch>/p/portrait-pro",
"check": {"ready_for_sellers": true, "sales_report_themselves": false, "fixes": ["Connect the checkout…"]},
"livemode": true}
| Field | Meaning |
|---|---|
url | The app's public https address. It must answer. An http:// address is listed as https://, and the answer says so in url_note. |
name, tagline, about | What it is, in the maker's words (at least 2, 10 and 40 characters). |
commission | The % of each sale the app maker pays, 5 to 70. Default 30, the same for this call, the MCP tool launch_with_people, a launch session and the website form. Paid only on real sales to new customers. |
face_commission | Optional. A higher % for a post with the seller's own face, checked by a person: at least commission, at most 90. Default: commission + 10. |
In this order: bad fields come back first as 400 invalid with fields naming each one. Then the address: listing the same address again from the same seat returns the existing listing (created: false); from another seat of the same account it returns that listing too (created: false, yours: false; it stays that seat's, with its numbers); an address another account listed gets 409 already. The address is compared by its host only: lowercase, without www., a trailing dot, the port, path, query or fragment, so https://yourapp.com/?ref=x and https://YourApp.com.:443/ are the same listing. Then the site is fetched (400 unreachable if it doesn't answer).
A failed listing, for example:
→ 400 {"ok": false, "error": "invalid", "detail": "Some fields need fixing: about, commission.",
"fields": {"about": "A few sentences: what it is, who it is for, what it costs.",
"commission": "A share between 5 and 70 percent."}, "livemode": true}
Limits: 500 listings a day per key, counted per seat for a platform (set by AVOUCH_LISTINGS_PER_KEY_DAY). The website form's per-connection limit does not apply to keyed calls.
Reading them
GET /v1/listings (the seat's, newest first), GET /v1/listings/<slug> (sellers promoting it, clicks in 30 days, sales by status, money owed to sellers), GET /v1/listings/<slug>/receipts (signed receipts, newest first: each with token, status, amount_cents, currency, day, id, and signed, signature and kid exactly as GET /v1/receipts/<token> gives them).
Pages: both lists return at most limit items (default 100; a whole number from 1 to 200, anything else is a 400 naming it) with has_more and next. Pass ?after=<next> for the next page; next is null on the last one. after is an id from a previous page (a whole number up to 9223372036854775807).
GET /v1/listings?limit=2
→ {"ok": true, "seat": "user-4821", "listings": [{"id": 41, "slug": "third-app", …}, {"id": 38, "slug": "portrait-pro", …}],
"has_more": true, "next": 38, "livemode": true}
GET /v1/listings?limit=2&after=38
→ {"ok": true, "seat": "user-4821", "listings": [{"id": 12, …}], "has_more": false, "next": null, "livemode": true}
Sale statuses
One set of words everywhere: a receipt's status, the receipts list, a listing's sales counts, webhooks, the answers of /v1/conversions and /v1/refunds, the MCP tools and the console. Where the ledger has its own word, answers also carry it as ledger_status.
| Status | Means |
|---|---|
held | Reported and confirmed by the business, inside the refund window. A refund now reverses it. |
unconfirmed | Reported by the pixel, not yet confirmed by the business with a signed call. |
cleared | Past the refund window: it stuck, and the seller is owed their share. |
paid | Cleared, and the seller has been paid. |
refunded | Refunded (or charged back) inside the window: nothing is owed. |
refused | The rules refused it, such as a seller buying through their own link. Nothing is owed. |
returning customer | The business said the buyer wasn't new, and the listing pays for new customers only. Nothing is owed. |
unattributed | No click, code or ref matched a seller of the listing. Recorded, nothing is owed, and it has no receipt. |
The check
Every app is checked automatically before a single seller sees it. The launch response includes the result, and GET /v1/listings/<slug>/check runs it again after a fix.
| Check | Passes when |
|---|---|
| The site is live | The address answers on https. |
| A refund policy we can find | The page, or a /refund or /terms page, says how refunds work. |
| Not a refused kind of product | Nothing in the name, tagline or description is gambling, adult content, weapons, drugs, crypto or get-rich schemes, payday loans, miracle health claims or MLM. |
| Sales report themselves | The checkout is connected (below). Needed to be paid automatically. |
Each failed check comes with a fix in plain words (always one string, for one check), so an agent can fix it; a listing's check.fixes is the list of what's left. matchable (or ready_for_sellers) means the first three pass; passed means all four do; ok only means the call worked. An app that fails one of the first three isn't shown to sellers, isn't listed by find_products or /api/products, and its public page says it isn't open to sellers yet.
Reporting sales
From Stripe (nothing to build)
In the app's own Stripe dashboard, add one webhook endpoint, https://<avouch>/v1/stripe/<slug>, for checkout.session.completed, charge.refunded and charge.dispute.created, and paste its signing secret (whsec_…) on the listing's manage page. Every event is checked with Stripe's own signature. Pass the click id our link adds (?sc_click=) as the session's client_reference_id or metadata.sc_click. In test mode the endpoint is the sandbox's own (A test sale).
From your server: POST /v1/conversions
body = {"product": "portrait-pro", "order_id": "A-1001", "amount": "29.00",
"click": "<sc_click>", "new_customer": true, "charge_id": "pi_…"}
t = unix seconds
sig = hex HMAC-SHA256(listing secret, t + "." + raw body)
header Sharecut-Signature: t=<t>,v1=<sig>
Sign the exact raw bytes you send: serialise the body once, sign those bytes, send those bytes. Runnable Python and curl versions are in A test sale; for a live listing leave out the Authorization header. The listing's secret is on its manage page. The time must be within 5 minutes. amount is a string with at most two decimals (or amount_cents, a whole number). An order id is counted once, so retries are safe (duplicate: true). Attribution: the click id, then the seller's code, then their ref; with none, the sale is unattributed and has no receipt. In test mode, whether a sale is a test comes from your test key, never from the body. (The header keeps the name Avouch had when it was built.)
Refunds: POST /v1/refunds
body = {"product": "portrait-pro", "order_id": "A-1001"} # signed the same way; add "chargeback": true for a dispute
→ inside the refund window: {"ok": true, "status": "refunded", "ledger_status": "reversed"}
→ after the sale cleared: {"ok": true, "status": "cleared", "ledger_status": "payable", "kept": true,
"detail": "This sale had cleared, so the affiliate keeps the commission."}
→ a chargeback after payout: {"ok": true, "status": "charged_back", "detail": "Charged back after payout: it comes off the affiliate's next payouts."}
→ again: {"ok": true, "status": "refunded", "duplicate": true} unknown order: 404
Cleared means kept. A refund inside the refund window reverses the sale and nothing is owed. After the window, the sale has cleared: a plain refund is the business's own, and the seller keeps the commission (the answer says kept: true). A chargeback (fraud) is different: before payout it is reversed; after payout the commission is taken off the seller's next payouts.
Webhooks
POST /v1/webhooks {"url": "https://…"} with your key returns a secret, once. Only public https addresses are accepted, and a delivery never follows a redirect (a 3xx counts as a failed delivery). An app connected through OAuth can't set it: only the account's own key can.
| Call | What it does |
|---|---|
GET /v1/webhooks | The address that is set (never the secret) and its deliveries: delivered, waiting, failed, and the last error. |
POST /v1/webhooks again | 409 already_set, naming the address. To replace it and get a new secret send {"url": …, "rotate": true}: the old secret stops at once. |
DELETE /v1/webhooks | Removes it. Events still waiting are dropped. |
POST /v1/webhooks/test | Sends one signed webhook.test event now and says whether it arrived. With a sale on the account, it carries that sale's receipt.created body (with its signed receipt) as sample, so you can test your verifier. Works with a test key (livemode: false). |
Events: receipt.created (a sale arrived), receipt.updated (it cleared, was paid or was refunded) and listing.created (a listing was made, by key, MCP or the button: {"type", "product", "seat", "via", "launch_session", "listing", "livemode", "at"}). Each receipt delivery is a POST of {"type", "id", "receipt", "status", "ledger_status", "why", "product", "at", "livemode", "signed_receipt"} (signed_receipt is {signed, signature, kid}, the same as GET /v1/receipts/<token>) with:
Avouch-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + body)>
Check it before trusting the body. Failed deliveries are retried with backoff (30 s, 2 min, 8 min…) up to 6 times. A webhook set with your test key gets your sandbox's receipt events (livemode: false); listing.created is sent for live listings only.
Seats and usage
The app maker pays only the commission, only when something sells. A platform pays per active seat, at the price in its agreement.
Your agent acts for each of your users by sending your key plus that user's id on your side:
Authorization: Bearer avk_…
Avouch-Seat: user-4821
- Listings are owned by the seat that made them, and seats can't see each other's. Reads with a seat return
"seat". - No
Avouch-Seat(orAvouch-Seat: account) means your platform's own seat,"account". It is billed like any other seat, so a listing made without the header still counts. A read without the header reads that seat and says so:"seat": "account", and aseat_notewith how many listings your users' seats hold. A listing that belongs to a user's seat is a404that names the seat it looked in. - Launch sessions need a real seat (
400 seat_requiredwithout one). An OAuth connection's seat is fixed when it is allowed. - A seat is active in a month when it made a listing (by key, MCP or the button) or a deal that month.
GET /v1/platform/usage?month=2026-10
→ {"ok": true, "month": "2026-10", "active_seats": 2, "listings": 2, "sales": 0, "seat_price_cents": 900, "bill_cents": 1800, "livemode": true}
No key is 401, a month that isn't YYYY-MM is 400, and a direct (non-platform) account is 403 not_a_platform. With the test key it reports your sandbox.
Receipts
Every sale a seller brought gets a receipt: which seller, which post, whether a person checked it was their own face, whether the business confirmed it, whether it was a new customer, and the merchant's own charge id. It never holds the customer's details. A sale no seller brought (unattributed) is recorded but has no receipt. Its status is one of the sale statuses.
GET /v1/receipts/<token> returns the receipt with signed (the exact JSON text), signature (Ed25519, base64url) and kid. The public keys are at /.well-known/avouch-receipts.json. Verify the signature over the bytes of signed and read facts only from signed. Only "livemode": true is a real sale; a sandbox receipt says false, inside the signed text. A person can open /receipt/<token> and press Verify.
curl -O https://<avouch>/docs/verify_receipt.py
python3 verify_receipt.py scr_12_… # keys and receipt from https://avouch-production-02c1.up.railway.app
python3 verify_receipt.py --issuer https://<avouch> https://<avouch> scr_12_… # another issuer you trust
python3 verify_receipt.py --issuer https://<avouch> https://<avouch> scr_test_a2_14_… # a sandbox receipt: name the host that made it
Pin the issuer. Take the keys from the issuer you trust, never from the host that handed you the receipt: a look-alike host can serve its own key and a forged receipt that checks out against it. verify_receipt.py fetches the keys from https://avouch-production-02c1.up.railway.app (or --issuer <origin>) and refuses a receipt fetched from any other host. A sandbox receipt (scr_test_) lives on the Avouch you tested against, so pass that host with --issuer.
verify_receipt.py needs only Python and cryptography, nothing from Avouch. The format is open: the Outcome Receipt spec.
"provider": "sharecut", Avouch's provider id from its earlier name. It stays, because it is inside the signed text.MCP tools
Any agent that speaks MCP can use Avouch. Hosted: connect to https://<avouch-mcp>/mcp with your key as the connection's Authorization: Bearer header and, for a platform, Avouch-Seat (without it the tools act for your platform's own seat, account). Local:
claude mcp add avouch -e AVOUCH_URL=https://<avouch> -e AVOUCH_API_KEY=avk_… -e AVOUCH_SEAT=user-4821 -- python3 sharecut_mcp.py
| Tool | What it does |
|---|---|
launch_with_people | List an app (url, name, tagline, about, commission, default 30 as in REST, face_commission). Returns the listing and its check. Listing the same app again returns it. |
my_products | Your listings with sellers, clicks, sales by status and money owed. |
product_status | One listing's numbers, its sales counted under the sale statuses. |
recent_receipts | A listing's receipts, newest first. |
verify_receipt | Fetches a receipt and verifies its signature locally with the published key. |
find_products | Apps open to sellers, by words. Apps that failed the check are left out. |
Every tool returns its answer twice: as JSON text, and as the same object in structuredContent. A failure ("ok": false: a field to fix, not_found, an unreachable Avouch) also sets isError: true, so an agent that checks isError never reads a failure as a success. A wrong key or token gets HTTP 401 with WWW-Authenticate, which says when the key itself isn't active.
Running the hosted server yourself: set AVOUCH_MCP_RESOURCE to the MCP server's public URL exactly as users add it (for example https://<avouch-mcp>/mcp), with the same value on the Avouch app. OAuth tokens are issued only for that resource, so without it every sign-in ends in invalid_target. On Avouch's own hosted server it is already set. The hosted connection answers GET /healthz with the code version it is running, its resource, and stale: true if it should be restarted.
Connect from Claude (OAuth)
In Claude, add https://<avouch-mcp>/mcp as a connector. find_products and verify_receipt work straight away. The first time Claude uses a tool that acts on your account, it shows a Connect button: Avouch's own page opens, you paste your Avouch API key there, and choose Allow. Claude never sees the key; it gets a token that does what the key does, for one hour at a time, renewed while the connection lasts.
- A test key (
avk_test_) makes a sandbox connection: everything it does sayslivemode: false, and nothing real happens. - A platform account can name the seat (its own id for the user) on that page. The connection then acts for that seat only; with none, it acts for the platform's own seat,
account. - To end a connection, reissue the key that made it (API keys: the console's Reissue, or the owner's route): every connection made with the old key stops at once. One connection alone ends with
POST /oauth/revokefrom its client.
Any MCP client that follows the MCP authorization spec works the same way. Discovery starts at /.well-known/oauth-protected-resource on the MCP server, which names Avouch's authorization server; its metadata is at /.well-known/oauth-authorization-server. Avouch supports client ID metadata documents and dynamic registration, PKCE with S256 only, resource indicators, and rotating refresh tokens. Keys as a Bearer header keep working.
Launch sessions API
From your server, with your key:
| Call | What it does |
|---|---|
POST /v1/launch_sessions | Key + Avouch-Seat. Optional JSON object {app_name, url, tagline, about, commission, face_commission} (anything else, a list included, is 400). Returns {id, client_secret, expires_at, mode}. 30 an hour per seat. |
POST /v1/platform/origins | {"origins": [...]}, a JSON list, replaces the list (up to 20). GET reads it. Per key mode. |
From the page, with the client secret in the Avouch-Client-Secret header (or client_secret in a JSON body). Never in the address: it isn't accepted there.
| Call | What it does |
|---|---|
GET /v1/launch_sessions/<id>/state | The app (prefill and what the check read), the commission (default 30), the terms on a US$100 sale in cents (you_pay_cents, to_the_seller_cents, to_avouch_cents, you_keep_cents), status open or complete, and next. |
POST /v1/launch_sessions/<id>/check | {url}. The four checks with their fixes, matchable, passed, and connect: the Stripe webhook address and its events, and the signed /v1/conversions call. Before the launch the addresses use the listing's expected name (final: false); after it, the check runs on the listing itself. 12 per session. |
POST /v1/launch_sessions/<id>/launch | {commission, face_commission?}, and url, name, tagline, about to set or fix a field (a url here is used as if it had been sent to /check). Returns the POST /v1/listings answer plus check_full, matched_sellers (sellers it is matched to now), seller_cap (how many it may be shown at this stage), state (both, in words: testing: matched to 3 of up to 5 sellers), stage, manage and how_sales_report. |
A launch whose fields need fixing is answered before the session is used, so it doesn't spend a try:
POST /v1/launch_sessions/lcs_…/launch {"commission": 30, "url": "https://blank-test.example.app"}
→ 400 {"ok": false, "error": "invalid", "session": "open", "tries_left": 5,
"fields": {"name": "Give the product its name.", "tagline": "One line on what it does, at least ten characters.",
"about": "A few sentences: what it is, who it is for, what it costs."},
"detail": "Fix these and launch again (this didn't use a try): about, name, tagline.", "livemode": true}
| Answer | Means |
|---|---|
400 invalid | A field to fix (fields), the session still open. Doesn't use a try. |
401 unauthorized | No secret, or the wrong one. 10 wrong secrets close the session (410 locked). |
403 origin_not_allowed | The page's origin isn't registered for this key's mode. |
409 used | The session already launched. A launch that got as far as fetching the site and failed (400 unreachable, "session": "open") can be tried again, up to 5 tries (409 too_many_tries after). |
410 expired | 15 minutes passed. Make a new session. |
429 slow_down | Too many sessions, checks or calls. Wait and try again. |
Every answer carries livemode; a test session's also sends Avouch-Livemode: false. In test mode the check is simulated (nothing is fetched), there is no manage page (your server reads the test listing's signing secret from GET /v1/listings/<slug>/manage; the page never gets a secret), the Stripe address is the sandbox's own test endpoint, and listing.created isn't delivered.