For AI agents

Machine-readable integration guide — same content as public AGENTS.md.

AGENTS.md
# PayProof AGENTS.md

Agent integration guide for the PayProof verification & developer billing platform.

## Product rules

1. PayProof **verifies** Ethiopian bank transfer receipts. It does **not** collect settlement for developer products.
2. End-customers pay **the developer's bank accounts**. PayProof confirms the receipt and maintains subscription/payment status.
3. Platform usage (app + API verification) is **credit-based**. One billable verification = 1 credit (after free allowance).

## Canonical merchant flow

```
Create app → Add receiving accounts → Set product price → Create checkout
→ Customer pays with reference_code → Verify receipt → Checkout auto-completes
→ Webhook subscription.activated | payment.verified
```

Dashboard path: **Apps → [app] → Pricing → Checkouts**.

Public customer pay page: `/pay/{checkout_public_id}`  
Public API: `GET /api/public/gateway/checkouts/:public_id/`  
Public verify (multipart): `POST /api/public/gateway/checkouts/:public_id/verify/`  
Live updates: `wss://{host}/ws/gateway/checkouts/:public_id`

## Base URL

```
https://verifyitet.duckdns.org
```

Local: `NEXT_PUBLIC_API_URL` (synced by `./start_dev`).

## Auth

### Dashboard (Telegram OIDC)

Same as Android. Obtain `id_token` via Telegram OIDC SDK, then:

```
POST /api/auth/telegram-login/
{ "id_token": "<OIDC JWT>" }
```

JWT on `Authorization: Bearer` for `/api/platform/*` and `/api/subscription/*`.

### Phone OTP

```
POST /api/auth/send-otp/   { "phone": "09xxxxxxxx" }
→ channel: "telegram" (6-digit DM) | "sms" (4-digit)
POST /api/auth/verify-otp/ { "phone", "code" }
```

### API key (server)

```
Authorization: Bearer pk_live_...
```

Only on `/api/v1/*`. Create on each app (Dashboard → Apps → [app] → API keys).

## Credits

```
GET  /api/v1/credits/
GET  /api/subscription/credits/
POST /api/subscription/credit-topups/   { "amount_etb": "150" }
```

Credits minted = `floor(paid_etb / etb_per_verification)`. Pay **PayProof platform** accounts from the top-up intent.

## Apps & pricing

```
POST /api/platform/apps/       { "name", "description?", "logo_url?", "brand_color?", "checkout_style?"}
POST /api/platform/products/   {
  "app_id", "name",
  "product_type": "subscription" | "one_time",
  "pricing_model": "fixed" | "dynamic",   # dynamic only for one_time
  "interval": "week" | "month" | "year" | "day" | "custom",
  "interval_count?": 1,
  "trial_days?": 0,
  "amount_etb": "499"   # null/omit for dynamic
}
GET  /api/platform/products/?app_id=
GET/PUT /api/platform/apps/:id/settings/   # window, expiry, partial, overpayment, auto_manual_review, default_success_url, default_cancel_url
POST /api/platform/api-keys/   { "name", "app_id" }  # app-scoped preferred
```

Product must belong to the app. Inactive app/product cannot create checkouts.

## Gateway checkouts

```
POST /api/v1/checkouts/  (or /api/platform/checkouts/)
{
  "app_id", "product_id",
  "amount_etb?": "999.50",   # required for pricing_model=dynamic
  "customer_ref?": "user_42",
  "idempotency_key?": "order_42",
  "expires_in_hours?": 48,
  "success_url?": "https://yoursite.com/orders/42/thanks",
  "cancel_url?": "https://yoursite.com/cart"
}

→ {
  "checkout": {
    "id" | "public_id",
    "reference_code": "PP-{company}-{hex}",
    "expected_amount_etb",
    "amount_paid_etb",
    "status": "pending",
    "expires_at",
    "success_url?",
    "cancel_url?"
  },
  "payment_accounts": [...],
  "pay_url": "/pay/cs_..."
}
```

**Redirects:** Hosted `/pay/:id` auto-redirects to `success_url` after verification (brief success UI, then navigate). Query params appended: `checkout_id`, `reference_code`, `status=verified`, and `customer_ref` when set. App defaults: `PUT /api/platform/apps/:id/settings/` with `default_success_url` / `default_cancel_url`. Per-checkout URLs override app defaults.

**Customer instructions:** transfer exact `expected_amount_etb` to a listed account; put `reference_code` in the transfer remark/reason.

Hosted page upload (no auth, rate-limited by IP+checkout):

```
POST /api/public/gateway/checkouts/:public_id/verify/
Content-Type: multipart/form-data
fields: images[] | images | qr_payload?

→ { "verification_id", "status": "PENDING", "websocket": "/ws/gateway/checkouts/:public_id" }
```

Also available authenticated: `POST /api/v1/checkouts/:public_id/verify/` and `POST /api/platform/checkouts/:public_id/verify/`.

Statuses: `pending | partially_paid | verified | failed | expired | canceled | manual_review`.

Polling: `GET /api/public/gateway/checkouts/:public_id/status/`  
WS: `wss://{host}/ws/gateway/checkouts/:public_id` — `{type:"checkout.updated", data:{...}}`.

Manual review:

```
POST /api/public/gateway/checkouts/:public_id/manual-review/
POST /api/platform/checkouts/:public_id/manual-review/approve/  { "note?", "amount_etb?" }
POST /api/platform/checkouts/:public_id/manual-review/reject/   { "reason?" }
```

Subscription status:

```
GET /api/v1/subscriptions/:id/status/
→ { "id", "status", "ok", "interval", "current_period_end", "customer_ref" }
```

## Verification API

```
POST /api/v1/verifications/
Authorization: Bearer pk_live_...
Content-Type: multipart/form-data
fields: images[] , qr_payload?

→ { "id", "status": "PENDING", "websocket": "/ws/verify/{schema}/" }

GET /api/v1/verifications/:id/
```

WebSocket: `wss://{host}/ws/verify/{schema_name}/` — type `verification_update`.  
Gateway checkout WS: `wss://{host}/ws/gateway/checkouts/:public_id`.

Billable terminal statuses: `VERIFIED`, `FAILED`, `REJECTED` (1 credit each, including gateway uploads).

## Webhooks

```
POST /api/platform/webhooks/
{
  "app_id",
  "url",
  "events": [
    "checkout.created", "checkout.verified", "checkout.partially_paid",
    "checkout.failed", "checkout.expired",
    "checkout.manual_review_requested", "checkout.manual_review_approved",
    "checkout.manual_review_rejected",
    "subscription.activated", "subscription.ended", "subscription.renewed",
    "payment.verified", "payment.failed",
    "verification.verified", "verification.failed"
  ],
  "enabled": true
}
```

Deliveries: `GET /api/platform/webhooks/:id/deliveries/` · retry `POST …/deliveries/:delivery_id/retry/`.

Header: `PayProof-Signature: t=<unix>,v1=<hex>`  
Verify: `HMAC-SHA256(secret, "#{t}.#{raw_body}")`.

## Accounts

```
GET  /api/platform/accounts/
POST /api/platform/accounts/          { bank_name, account_number, account_name }
POST /api/platform/accounts/import/   { "ids": "all" }  # list existing verification accounts
```

## Do not

- Expect PayProof to settle developer product funds.
- Complete checkouts without a real verified payment in production workflows — rely on reference-matched verification.
- Put API keys in client-side apps.