# Monime API integration rules

These rules apply to any code in this project that calls the Monime API,
receives Monime webhooks or builds a Dash Dot. Docs: https://docs.monime.io
(add `.md` to any page URL for Markdown; the index is
https://docs.monime.io/llms.txt, and every Dash Dots page is in
https://docs.monime.io/llms-dots.txt). OpenAPI specification:
https://docs.monime.io/apis/openapi.json

## Requests

- Base URL: `https://api.monime.io`. Paths start with `/v1/`, for example
  `POST /v1/checkout-sessions`.
- Send these headers on every request:
  - `Authorization: Bearer <access token>`
  - `Monime-Space-Id: <space id>` (IDs start with `spc-`)
  - `Content-Type: application/json` when there is a body
  - `Monime-Version: caph.2025-08-23` to pin the API release
- Read the token and Space ID from configuration or a secrets manager. Never
  hard-code them, commit them, log them or send them to a browser or mobile
  app: call Monime only from a server.
- Use a test token (it starts with `mon_test_`) in development, CI and
  staging. Live tokens start with `mon_` without `test_`. Test and live are
  fully separate: objects, webhooks and limits do not cross over.
- Successful responses are `{ "success": true, "messages": [], "result": ... }`.
  Errors are `{ "success": false, "error": { "code", "reason", "message",
  "details" } }`. Branch on `error.reason`, not on `message`.
- Log the `Monime-Request-Id` response header with every failure.

## Money

- Amounts are `{ "currency": "SLE", "value": <integer> }` and `value` is in
  minor units: SLE 250.00 is `{ "currency": "SLE", "value": 25000 }`. Never
  use floating point for money. Supported currencies: `SLE`, `USD`.
- Timestamps are RFC 3339 in UTC, such as `2025-08-24T06:00:00Z`.

## Creating things safely

- Send an `Idempotency-Key` header on every POST that creates something or
  moves money (checkout sessions, payouts, payment codes, internal transfers,
  financial accounts, webhooks, receipt redemptions, USSD OTPs).
- Use a UUID v4, or a UUID v5 derived from your own reference (such as
  `payout:order-123`) so a retry after a crash reuses the same key. Store the
  key with your record before calling Monime. Never put personal data in it.
- Retry with the same key and the identical method, URL and body. Keys are
  remembered for 24 hours; a replayed response has `Monime-Cache: irc`.
- A `409` with reason `idempotency_key_in_use` means the key was reused for a
  different request: fix the code that chose the key, don't retry.
- If you don't know whether a create succeeded, GET the object before trying
  again.

## Retries and rate limits

- Retry network errors and `5xx` responses with exponential backoff and
  jitter. Don't retry other `4xx` errors without changing the request.
- On `429`, wait the number of seconds in `Retry-After`, then retry. The
  `Monime-Rate-Limit` header says which limit was hit (`token-limit`,
  `space-limit` or `endpoint-limit`). A throttled request had no effect.
- Use webhooks to learn about changes instead of polling.

## Lists

- Lists are paginated with `limit` (1 to 50) and `after`. Pass the response's
  `pagination.next` as `after`, and stop when `next` is missing or null.
  Cursors are opaque: never build or edit them.

## Metadata

- `metadata` is a flat map of up to 64 string values of 100 characters or
  less. Keys use letters, digits, `_` and `-`, and can't start with `_`, `-`
  or `monime`. Don't store secrets or personal data in it.

## Webhooks

- Verify every delivery before acting on it, using the raw request body
  exactly as received (not re-serialised JSON):
  - The `Monime-Signature` header looks like `t=<unix seconds>,v1=<signature>`.
  - For HMAC webhooks, the signature is base64 of
    HMAC-SHA256(webhook secret, `<t>` + `_` + raw body). The separator is an
    underscore, and the output is base64, not hex.
  - Compare signatures in constant time.
  - Reject deliveries where `t` is more than 300 seconds old or more than 60
    seconds in the future.
  - On failure, respond `401` without processing or explaining why.
- The webhook secret is not the API token. Keep it in configuration.
- Store the event, respond `2xx` within 5 seconds, then process it in the
  background. Anything else is retried.
- Deliveries can repeat and arrive out of order: deduplicate on `event.id`
  (it starts with `wke-`) for at least 24 hours, and read the object's current
  state rather than assuming the order of events.
- Webhook URLs must be public HTTPS. For local development, expose your server
  through a tunnel.
- Don't poll to find out when a checkout is paid: handle
  `checkout_session.completed`.

## Dash Dots

A dot is a mini app that runs inside Dash, built with the Dot framework and
the Monime CLI (`monime dot dev`, `build`, `publish`). Check the
components and calls you use in the reference:
https://docs.monime.io/docs/products/dash-dots/reference

- `dot.json` has `package` (a reverse domain name that never changes once
  published), `name`, `version` (semver, raised every release), and the `id`
  `monime dot register` writes. Nothing else but `$schema`, `permissions`,
  `network` and `dash.minApiVersion` (the Dash API version the dot needs,
  `1.0` by default).
- A dot belongs to a Space, not to the developer. Register, publish and
  release it in the business's Space (`monime space use`, or `--space`),
  which the developer must be a member of.
- List every host the dot calls in `network.hosts`; other requests fail as
  `denied`. List pages in `dot.ts` with `defineDot({ pages })`; the first is
  the entry page.
- Each page is `view.dml`, `state.ts` (a default export of JSON-only data)
  and `controller.ts` (`export default definePage({ ... })`).
- A `view.dml` has exactly one root `<View>`, and loose text only inside
  `<Text>`, `<Button>` and similar. Use only the documented components and
  props; there are no stylesheets and no `class`.
- `{{ }}` bindings read state with operators and `?:` only: they can't call
  functions or change values. Compute values in mutations.
- Event attributes (`onTap="choose"`) name a mutation or action in the
  page's controller. Mutations are pure: `(state, event) => changes`.
  Actions may be async; only actions get `this.navigation`.
- In pages, call Dash through `this.dash`, never the global `dash`. Every
  call can fail with a `DashError` (`unsupported`, `denied`, `cancelled`,
  `timeout`, `invalid`, `missing`, `failed`): check it with
  `isDashError(error, 'denied')` from `@monimesl/dot-runtime`, and carry on.
- Dash API 1.0 is stable. Before a call newer than the dot's
  `minApiVersion`, check `this.dash.supports('feature.name')`.
- Navigation options are strings, and at most 16 pages are open at once.
- A dot's code and `.env` are public. Keep secrets and prices on your server,
  and check every request there.
- `AmountInput` counts minor units unless `unit="major"`: 150 is SLE 1.50.
- Style with props, not CSS: `View` takes `background`, `border`, `radius`,
  `width`, `height` and `aspect`; `Text` takes `tone="onPrimary"` on a
  `primary` background. Name the dot's own colors in `theme.colors.custom`
  and use them by name. A page sets its own colors with `configs.colors`
  or `this.setColors()`.
- `dash.media.pickPhoto`, `dash.media.scanCode`, `dash.media.pickFile`
  and `dash.contacts.pickPhone` need the `photoLibrary` or `camera`,
  `camera`, `files` and `contacts` permissions. Wrap pickers in
  `unlessCancelled` (from `@monimesl/dot-runtime`): closing one isn't an
  error. Show `errorMessage(error)` for anything else, and `save` a picked
  file to keep it.
- Type handlers with `ValueEvent`, `CheckedEvent`, `IndexEvent` and
  `DataEvent<{ ... }>`. Use `this.navigation.pushForResult` and
  `backWithResult` to get a choice back from a page, and an `onRefresh`
  hook for pull to refresh. Let a share link open a page with
  `definePage({ share: { options: [...] } })`, and check those options in
  `onLoad`: anyone can write a link.
- `dash.account.signIn({ mode: 'simple' })` names the person, but a server
  can't verify its token. Don't treat it as proof for moving money.
- `statics/` holds only PNG, JPEG, GIF or WebP images of 1 MB or less; no
  SVG.
- Give every `<For>` a `key`. Test pages with `testPage` from
  `@monimesl/dot-runtime/testing`, faking Dash calls with its `host` option.
