# Partner Apps

Engineers at a partner company integrating with the Monta API on behalf of Monta operators.

A **partner app** is a listing in the Monta marketplace that operators can install. Installing it
grants your app scoped access to that operator's data, authenticated with OAuth 2.0.

Use a partner app when you are building an integration that multiple Monta operators will adopt.
For a single-operator integration you manage yourself, the direct API consumer credentials are
simpler.

## Lifecycle

| Stage | What happens |
| --- | --- |
| 1. Submit | You register the app with its name, slug, scopes, and redirect URIs. |
| 2. Review | Monta reviews the submission. |
| 3. Approval | The app is published, an OAuth client is minted, and a credentials email is sent. |
| 4. Install | An operator installs the app and consents to its scopes. |
| 5. Authorize | You run the OAuth flow to obtain tokens for that operator. |

## Receiving your credentials

On approval, Monta emails the address on your publisher organization. The email contains a
**single-use link** that reveals your `oauthClientId` and `oauthClientSecret`.

* The link may be opened **once**. Store the secret in a secret manager the moment you see it.
* The client secret is never returned by any API, and never re-sent by email.
* If the link is consumed or lost, contact Monta support. Re-issuing mints a **new** OAuth client;
  the previous credentials stop working and any operator who authorized against them must
  re-authorize.

## Redirect URIs

Register every callback URL your app will use, one per environment.

* `https` only, except loopback (`localhost`, `127.0.0.1`, `[::1]`) for local development.
* Matched exactly: `https://example.com` and `https://example.com/` are different URIs.
* No wildcards, no URL fragments, no private or internal IP addresses.
* Up to 10 per app, no duplicates.

Get these right at submission. See **Going live** for the full authorization flow.

## Scopes

An app requests scopes at registration, in `resource:permission` form — for example
`teams:read`, `charge-points:read`. The operator sees them at install and consents to them as a set.

`offline_access` is requested per authorization, not at registration, and is what returns a
refresh token.

An access token is limited to the intersection of the app's registered scopes, the operator's
consent, and the scopes named in the authorize request.

## Webhooks

Partner apps involve two separate webhook mechanisms with two separate secrets. They are easy to
confuse.

### App lifecycle webhooks

Monta notifies your app when an operator installs it (and on other lifecycle events).

* Configure a **webhook URL** and a **webhook secret** on the app itself.
* The secret is **not generated for you**. You supply it, minimum **16 characters**, and it must be
  set whenever a webhook URL is set — a URL without a secret is rejected.
* If your app has no webhook URL, the secret is `null` and no lifecycle callbacks are delivered.
  This is expected, not an error.
* Payloads are signed with HMAC-SHA256 using that secret. Verify the signature before acting.

### Event webhooks

Delivery of charge, team, and other domain events for an installed operator.

* Configured per install, with its own URL and secret.
* If you do not supply a secret, one is generated for you (16–191 characters).
* These are the webhooks described under **Webhooks** in this documentation.

Setting one does not configure the other.

## Checklist before going live

* [ ] Redirect URIs registered for every environment, exact strings confirmed.
* [ ] Client secret stored in a secret manager, never in source control.
* [ ] Token exchange happens within 60 seconds of receiving the code.
* [ ] Refresh tokens persisted and rotated — the new one replaces the old on every refresh.
* [ ] `Monta-Version` header pinned on every API request.
* [ ] Webhook secret set if you configured a webhook URL, and signatures verified.
* [ ] `state` sent on authorize and validated on callback.
