# Partner API (commerce)

Public contract for businesses. The full internal BFF index (identity proxy, settlement, etc.) lives in the repo under `docs/API.md` and is **not** a multi-tenant partner surface.

Base URL: your issued host (e.g. `https://app.example.com`).

## Auth

| Caller | Mechanism |
|--------|-----------|
| Mint intent / command | `Authorization: Bearer <secret>` or `x-pay-intent-secret` |
| Verify intent (GET) | None (token is the capability) |
| Access request form | None (rate-limited public form) |

Mint secrets are issued **per merchant after review** (`PAY_INTENT_MERCHANTS=id:secret:feeBps,…`). The platform `PAY_INTENT_SECRET` remains an admin key. Do not embed secrets in browsers or mobile apps.

Merchant credentials bind `merchantId` + contracted `feeBps` into the intent.

## Fees

Default commerce `feeBps` is **150** (1.5%), merchant-absorbed. See [FEES.md](./FEES.md).

On Vaulta / EOS (gh-v1), settlement is **two standard token `transfer`s** in one
signed tx (face amount split into merchant net + platform fee) using the send
token — typically `core.vaulta` / `A`. No custom fee contract. The fee leg goes
to the deploy’s commerce fee account.

Mint and GET both return the same precision-formatted `fee` quote (or `null`
for plain P2P at `feeBps: 0`). Amounts too small for a non-zero fee at token
precision are rejected at mint (`400`).

## `POST /api/v0/pay/intent`

Mint a non-custodial pay intent.

**Body**

| Field | Required | Notes |
|-------|----------|-------|
| `to` | yes | Merchant Antelope account |
| `amount` | yes | Decimal string |
| `memo` | no | Max 256 chars |
| `from` | no | Payer hint only |
| `symbol` / `contract` / `precision` | no | Defaults from deploy token config |
| `ttlSec` | no | 30–3600 (default 900) |
| `merchantId` | no | Bound into intent when issued |
| `feeBps` | no | Ignored from untrusted clients when merchant config exists; list default 150 |

**Response `201`**

```json
{
  "ok": true,
  "token": "…",
  "exp": 1710000000,
  "href": "https://…/app/send?intent=…",
  "fee": {
    "feeBps": 150,
    "feeAmount": "1.5000",
    "netToMerchant": "98.5000",
    "amount": "100.0000"
  },
  "notice": "Non-custodial. Open the link and sign in Sika — chat never holds keys."
}
```

## `GET /api/v0/pay/intent?token=`

Verify an intent without executing a transfer. No mint auth required (the token
is the capability). Response shape:

```json
{
  "payload": {
    "to": "hotel.acc",
    "amount": "100.00",
    "symbol": "A",
    "contract": "core.vaulta",
    "precision": 4,
    "memo": "Room 214",
    "exp": 1710000000,
    "merchantId": "hotel_accra",
    "feeBps": 150
  },
  "fee": {
    "feeBps": 150,
    "feeAmount": "1.5000",
    "netToMerchant": "98.5000",
    "amount": "100.0000"
  },
  "notice": "Review and sign in your wallet. Messaging apps never hold private keys."
}
```

## `POST /api/v0/pay/command`

Chat adapter: `{ "text": "/pay hotel.acc 100.00 Room 214" }` with the same mint auth. Returns `href` + `reply`. Merchant auth attaches contracted `feeBps` and the same precision-formatted `fee` quote as intent mint; platform/chat P2P stays at `fee: null`.

## Deep links

| Link | Use |
|------|-----|
| `/app/send?intent=<token>` | Customer confirms + signs |
| `/app/receive` | Merchant receive / QR |
| `/app/pay?…` | Claim-style pay links |
| `sika://app/send?intent=…` | Capacitor |

Legacy `/wallet/transfer?intent=` redirects to `/app/send`.

## `POST /api/v0/partners/access-request`

Public apply form. Rate-limited. Does **not** issue credentials — notifies the Sika team.

## Merchant portal (session)

After ops approval (`partner:approve`), partners sign in at `/partners/login` (magic link to the approved email). Session cookie gates:

| Method | Route | Notes |
|--------|-------|-------|
| POST | `/api/v0/partners/session/request` | Send magic link |
| POST | `/api/v0/partners/session/consume` | Exchange token → session cookie |
| POST | `/api/v0/partners/session/logout` | Clear session |
| GET | `/api/v0/partners/me` | Merchant profile + fee tier |
| GET/POST | `/api/v0/partners/keys` | List / rotate / revoke (secret shown once) |
| GET | `/api/v0/partners/intents` | Recent successful mints |
| POST | `/api/v0/partners/quote` | Face amount → platform + rail lines (`railFeeBps` is 0 until MoMo live) |

## Errors

Prefer `{ "message": "…" }` with HTTP 400 / 401 / 429. No stack traces or internal URLs.
