# Integrating usevig

You are integrating usevig into a project. Follow this in order. Step 1 costs
money, so handle it according to whatever spending authority you have.

If you have not decided whether this fits, read https://usevig.com/evaluate.md first.

Ask the operator for two things and never invent either:
- the **payout address** their customers' money settles to
- which **chains and tokens** to accept

Also ask whether they were given a **coupon code**. If they have one, it replaces
the payment step entirely — see step 1.

## 1. Get a live account

Mainnet requires a plan. base-sepolia is free — skip to the note at the end of
this step if you only want to build against testnet first.

**If the operator has a coupon code**, pass it and you are done in one call: no
payment, no waiting, and the response contains the api_key directly.

    curl -X POST https://usevig.com/v1/signup \
      -H 'Content-Type: application/json' \
      -d '{"plan":"starter","payout_address":"0xTheirs","coupon":"VIG-XXXX-XXXX-XXXX"}'

    -> {"redeemed":true,"api_key":"vig_live_...","webhook_secret":"whsec_...",
        "granted":{"tier":"starter","period_days":30}}

Save both immediately; the key is shown once. Then skip to step 2. An account
activated this way is identical to a paid one. If the code is refused, do not
retry it in a loop — attempts are rate limited — report the error and ask the
operator to check it.

Otherwise, buy a plan:

    curl -X POST https://usevig.com/v1/signup \
      -H 'Content-Type: application/json' \
      -d '{"plan":"starter","payout_address":"0xTheirs","webhook_url":"https://theirs/hook"}'

Returns:

    {
      "object": "signup",
      "pending_merchant_id": "mch_...",
      "purchase_id": "pur_...",
      "checkout_id": "chk_...",
      "plan": {"tier":"starter","price_usd":49,"period_days":30,
               "included_transactions":11000},
      "status": "pending",
      "checkout_url": "https://usevig.com/pay/c/chk_...",
      "options": [ ... ],
      "status_url": "https://usevig.com/v1/signup/pur_...",
      "webhook_secret": "whsec_..."
    }

Pay the plan by sending the exact `amount` from any entry in `options` to that
entry's `address`, or open `checkout_url`. **This spends real money.**

Then poll `status_url` until `api_key` appears:

    curl https://usevig.com/v1/signup/pur_...

    -> {"paid": true, "plan_status": "active", "api_key": "vig_live_..."}

**Store that key immediately.** It is returned until it is first used, then
never again. Keep it out of version control.

*Free path:* `POST /v1/merchants {"network":"base-sepolia","payout_address":"0x..."}`
returns a `vig_test_` key instantly, no payment. Identical code path.

*With a coupon:* add `"coupon":"CODE"` and there is nothing to pay — the account
activates immediately and the api_key is in the response. **A coupon carries its
own tier and period, and those win: `plan` is ignored when a coupon is present.**
Report back what `granted` says, not what you asked for.

## 1b. Change what you accept, later

Accepted chains and tokens are an account setting, not something fixed at
creation. Change them any time — no new account, no new key:

    curl -X PATCH https://usevig.com/v1/me \
      -H "Authorization: Bearer $VIG_KEY" -H 'Content-Type: application/json' \
      -d '{"accepted":[{"network":"base","token":"USDC"},
                       {"network":"polygon","token":"USDT"}]}'

That becomes the default for checkouts you create without an explicit accept.
Updates are partial: omitted fields are untouched. You can also change
payout_address, webhook_url, finality and name the same way.

If you change payout_address, it applies to checkouts created afterwards.
Funds at an address quoted before the change still collect to the old payout
address. Do not tell the operator that the funds moved. They did not.

## 2. Create a charge

    curl -X POST https://usevig.com/v1/checkouts \
      -H "Authorization: Bearer $VIG_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"amount_usd":25,"accept":[{"network":"base","token":"USDC"}],"finality":"confirmed"}'

Returns exactly this shape — do not write defensive code for alternatives:

    {
      "id": "chk_...",
      "object": "checkout",
      "status": "pending",
      "amount_usd": 25,
      "resource": null,
      "finality": "confirmed",
      "reached": null,
      "finality_detail": {"level","label","approx_seconds","source","reversal_risk"},
      "checkout_url": "https://usevig.com/pay/c/chk_...",
      "expires_at": <unix>,
      "created_at": <unix>,
      "paid_at": null,
      "options": [ ... ],
      "instructions": "...",
      "custody": "..."
    }

Each entry in `options`:

    {
      "network": "base",
      "chain_id": 8453,
      "token": "USDC",
      "token_address": "0x...",
      "decimals": 6,
      "amount": "25000000",
      "address": "0x...",
      "rate_usd": 1,
      "quote_expires_at": <unix>
    }

Send `address` and `amount` to a machine payer; send `checkout_url` to a human.

**One checkout can offer several chains and tokens.** List every pair you accept
and the payer picks; each option carries its own address and amount:

    "accept": [{"network":"base","token":"USDC"},
               {"network":"base","token":"USDT"},
               {"network":"polygon","token":"USDT"}]

Omit `accept` to offer every token on the account's own network. Any paid plan
reaches every mainnet; a free testnet key is limited to base-sepolia.

## 3. Handle the webhooks

Every event type you can receive:

    checkout.instant     checkout.confirmed    checkout.safe
    checkout.finalized   checkout.paid         checkout.underpaid
    checkout.expired     checkout.reorged
    plan.activated       plan.expiring         plan.expired

The first four are the finality ladder: they fire as a payment gets deeper,
whichever level you chose, so your UI can show progress. Treat unknown types as
ignorable rather than an error — more may be added.

**Switch on `type`, never on `data.status`.** `data` is the checkout's CURRENT
state, so a `checkout.instant` delivered in the same tick as payment already
reads `"status": "paid"`. Matching on `data.status` fulfils at instant finality
no matter which level you requested — taking reversal risk you opted out of.

Each delivery carries:

    X-Vig-Signature: t=<unix>,v1=<hmac-sha256 hex>
    X-Vig-Event-Id: <id>
    X-Vig-Delivery-Attempt: <n>

The signed message is `<timestamp>.<raw body>`, keyed with the `webhook_secret`
from signup. Compare in constant time and reject a timestamp older than
300s. Deliveries retry, so make handling idempotent on `X-Vig-Event-Id`.

**Fulfil on `type === "checkout.paid"` only.** `checkout.underpaid` means the payer sent
less than the quoted amount and must not release the goods; the shortfall is in
the payload. Anything under 1% short of the quote counts as underpaid.

## 4. Choose finality deliberately

- `instant` — In a block, about 2s on base.
- `confirmed` — Confirmed, about 12s on base.
- `safe` — Safe, about 58s on base.
- `finalized` — Final, about 1160s on base.

Lower levels are faster and carry more reversal risk. `confirmed` is a
reasonable default for digital goods; use `finalized` before shipping anything
physical or expensive. Set it per merchant or per checkout.

`checkout.reorged` can still fire after `checkout.paid` if a reorg unwinds the
payment. **If what you granted is stateful and permanent — credits, storage,
entitlements — an unhandled reorg is permanently free product.** Reverse exactly
what you granted, keyed on the checkout id, and make the reversal idempotent so
two deliveries cannot double-subtract.

## 5. Read decimals from the API, never hardcode

Most supported tokens use 6 decimals. DAI uses 18. Assuming 6 for DAI
understates a charge by a factor of 10^12.

Price in `amount_usd` and let the API compute `amount`. If you must compute it
yourself, read `decimals` from the option or from `GET /v1/config`.

## 6. Collect your funds

Payments accumulate at per-checkout addresses. Sweeping them to the payout
address is a transaction the operator signs and broadcasts, paying that gas.
usevig never calls it.

    curl "https://usevig.com/v1/collect/quote?network=base" -H "Authorization: Bearer $VIG_KEY"

    {
      "object": "collect_quote",
      "network": "base",
      "chain_id": 8453,
      "unswept": [ ... ],
      "total_usd": 37.5,
      "address_count": 3,
      "gas_estimate": {"gas_units": <n>, "note": "..."},
      "transaction": {"to":"0x...","data":"0x...","value":"0x0","gas":"...","chain_id":8453},
      "how_to_broadcast": "...",
      "custody": "..."
    }

`transaction` is **unsigned**. Sign and broadcast it with any wallet or
`eth_sendTransaction`. Several checkouts collect in one transaction, so this
costs one signature rather than one per payment. `transaction` is `null` when
there is nothing to collect.

## Operational facts that will otherwise cost you time

- **A quote expires in 15 minutes; the checkout itself in 60 minutes.** Create
  the checkout when the payer is ready, not far in advance.
- **Overpayment marks the checkout paid** and the excess is collectable. The
  payer is not refunded automatically.
- **The receiving address is a contract that does not exist yet.** That is
  normal — token balances are ledger entries, so funds arrive at an address
  with no code, and the contract is deployed by the collect transaction.
- **Right token, wrong chain is unrecoverable.** The address is derived per
  chain. Show the payer the network as prominently as the amount.
- **A collected address disappears from the next quote automatically.** Since
  you broadcast the sweep yourself, usevig confirms collection from the
  on-chain balance rather than from having seen your transaction.
- **Collection keeps working when a plan lapses.** An unpaid invoice never
  blocks access to money that is already yours.
- **Plans run 30 days with a 3-day grace period.** Renew with
  `POST /v1/plan/renew`; renewing early extends from the current expiry and
  does not rotate the api_key.
- **base-sepolia USDC faucets are captcha-gated.** For testing, TUSD at
  `0x35DA4Ea8430aD45c48702C1fA3dc7237aB83b66d` has an open `mint(address,uint256)`
  anyone can call.

## Errors

Every 4xx body has this shape:

    {"error": "unsupported_token",
     "fix": "NOPE is not supported on base-sepolia; supported: USDC, TUSD"}

`fix` is written to be actionable. **Surface it verbatim** rather than
paraphrasing it into something vaguer.

Ones you will actually hit:

- `402 plan_required` — mainnet without an active plan. Body carries the signup URL.
- `403 network_not_permitted` — a `vig_test_` key asking for a mainnet charge.
- `400 unsupported_token` — that token is not configured on that chain.
- `400 no_rate_available` — the token has no USD quote and cannot be priced.

## Verification checklist

Prove it end to end before reporting success:

1. `GET /v1/me` returns your tier and `plan_status: "active"`.
2. `POST /v1/checkouts` returns a `checkout_url` and at least one option.
3. Sending the exact `amount` to that option's `address` flips the checkout to
   `paid` — verify against `GET /v1/checkouts/<id>`, not against your own logs.
4. Your webhook endpoint received `checkout.paid` and the signature verified.
5. An underpayment does **not** fulfil the order.
6. `GET /v1/collect/quote` returns an unsigned transaction that, once
   broadcast, moves the funds to the payout address.
7. The api_key is stored outside version control.

Do steps 3-6 on base-sepolia first if you want to prove the wiring without
spending real money. Point the same code at a mainnet network afterwards —
only the network name changes.

Full reference: https://usevig.com/llms.txt and https://usevig.com/openapi.json
