> ## Documentation Index
> Fetch the complete documentation index at: https://docs.photon.codes/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use Stable documentation by default. Honor an explicit Beta request or a URL under /docs/beta/. If the requested version conflicts with the installed CLI package or API origin, clarify the target before writing integration code.
> Pages under /docs/beta/ document Beta; other product pages document Stable. Keep the CLI package, commands, API origin, and credentials within the selected version. State the documentation version in your answer.
> For MCP search, always pass version: Stable or version: Beta. Unfiltered search mixes both versions. For filesystem reads, keep Beta queries under /beta/ and exclude /beta/ from Stable queries; discover paths before reading them.
> The public docs base is https://photon.codes/docs. Convert MCP page paths to public URLs under that base, preserving /beta/ when present. Read https://photon.codes/docs/skill.md for version selection and https://photon.codes/docs/llms.txt for the version indexes.

# Install an App

> Create a signed installation request, handle consent, and exchange the callback code for installation credentials.

Your backend performs the protocol steps. The customer clicks **Connect to Photon**, reviews access in Photon Dashboard, and returns to your product.

```mermaid theme={null}
sequenceDiagram
    actor User as Customer administrator
    participant App as Your App backend
    participant API as Photon API
    participant Dashboard as Photon Dashboard
    User->>App: Connect to Photon
    App->>App: Bind random state to signed-in customer session
    App->>API: POST /v1/installation-requests + App assertion
    API-->>App: requestId
    App-->>User: Redirect to Dashboard installation page
    User->>Dashboard: Select organization, approve permissions and projects
    Dashboard-->>User: Redirect to registered HTTPS callback with code and state
    User->>App: Callback
    App->>App: Validate session and state
    App->>API: POST /v1/installation-requests/redeem + new App assertion
    API-->>App: Installation ID and one-time client credentials
    App->>App: Persist credentials and customer association securely
    App-->>User: Connected
```

## 1. Create an installation request

Require a signed-in session in **your product** before starting. Generate a cryptographically random, expiring `state` and store it server-side with that session and customer account. Do not trust a customer ID supplied in an unverified callback.

Sign a new JWT with your registered App private key:

| Claim/header | Value |
| - | - |
| `alg`, `kid` | The registered key’s algorithm (`ES256` for generated keys and the local example; `RS256` for an imported RSA key) and its registered key ID |
| `iss`, `sub` | Your App ID |
| `aud` | The exact absolute URL of the endpoint being called |
| `iat`, `exp` | Current time and a short expiry; use two minutes, never more than five |
| `jti` | A new unique random ID for each assertion |

Use a maintained JWT library such as `jose`. Keep your server clock synchronized. Do not reuse assertions or sign the request using the webhook secret.

```http theme={null}
POST /v1/installation-requests
Content-Type: application/json

{
  "appId": "pho_app_REPLACE_ME",
  "clientAssertion": "SIGNED_JWT",
  "state": "RANDOM_SESSION_BOUND_STATE",
  "redirectUri": "https://helpdesk.example.com/photon/callback"
}
```

The response includes `requestId`, `appId`, `state`, and `redirectUri`. Check the echoed fields against the request, then redirect the browser to:

```text theme={null}
{PHOTON_DASHBOARD_ORIGIN}/organization/install?requestId={requestId}
```

See [Create installation request](https://docs.photon.codes/docs/beta/api-client/sdk/system/create-app-installation-request.md).

## 2. Let the customer approve access

Photon Dashboard handles Photon login and verifies that the user can manage App installations in the selected organization. This is separate from the login session in your own product.

The customer reviews the requested permissions and chooses **All projects** or **Only selected projects**. All selected projects share the same approved project permissions. All projects includes projects created later; selected projects does not automatically include future projects.

Customers can subsequently manage the installation under **Integrations → Apps**. See [Permissions and lifecycle](https://docs.photon.codes/docs/beta/apps/permissions.md).

## 3. Validate the callback and redeem the code

On the registered HTTPS callback:

1. Validate the browser session and compare `state` with the unexpired server-side value. Reject missing, mismatched, expired, or already-used state.
2. Consume the state once. Do not let two callback requests redeem concurrently.
3. Exchange `code` server-to-server with a **new assertion**, whose audience is the redemption endpoint.

```http theme={null}
POST /v1/installation-requests/redeem
Content-Type: application/json

{
  "appId": "pho_app_REPLACE_ME",
  "code": "CALLBACK_CODE",
  "clientAssertion": "NEW_SIGNED_JWT"
}
```

A first successful delivery returns:

```json theme={null}
{
  "status": "created",
  "installationId": "pho_ain_REPLACE_ME",
  "clientId": "client_REPLACE_ME",
  "clientSecret": "SECRET_RETURNED_ONLY_ONCE"
}
```

Persist the credentials securely **before** showing success. Associate the installation ID with your product's customer account, and resolve/verify its Photon organization using the installation's access token. Never send the client secret or access token to the browser.

### A successful HTTP response may not contain credentials

| Redemption status | Handling |
| - | - |
| `created` | Persist the returned credentials; complete setup. |
| `delivery_consumed` | The one-time delivery has already been consumed. Use your securely stored credentials if available. Do not assume a retry will reveal them again. |
| `recovery_required` | Stop setup and follow installation credential recovery with Photon. |
| `revocation_pending` | Do not activate the connection while revocation is pending. |
| `revoked` | Do not use the installation's credentials. |

If redemption times out, the operation may have succeeded even though you did not receive the secret. Do not loop until a new secret appears, replace another installation's credentials, or mark the connection successful. Preserve a non-secret diagnostic reference and initiate recovery. A browser refresh of an old callback cannot safely repeat installation.

See [Redeem installation credential](https://docs.photon.codes/docs/beta/api-client/sdk/system/redeem-app-installation-delivery.md).

## 4. Obtain an installation access token

Send the installation credentials to the **token endpoint configured for your Photon environment**. This is the environment's AuthKit OAuth token endpoint, not `/v1/installation-requests/redeem` and not the legacy Stable OAuth API. Obtain its exact URL from Photon when enabling App access; do not infer it from the Dashboard hostname or send credentials to a URL supplied by an end user.

```http theme={null}
POST {PHOTON_TOKEN_URL}
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=CLIENT_ID&client_secret=CLIENT_SECRET
```

Use a form encoder to construct the body. Use the response's `access_token` as a bearer token:

```http theme={null}
GET /v1/organizations/current
Authorization: Bearer ACCESS_TOKEN
```

This example requires `organization:read`. Cache tokens only until their expiry, keep them server-side, and handle authorization failures after an installation is paused, removed, or changes scope. Possessing an unexpired token does not override Photon authorization checks.

## Store data per installation

Your persistent model should associate:

* Your customer/account ID and the Photon installation ID.
* The verified Photon organization ID.
* Encrypted client credentials and connection status.
* Event deduplication records and processing checkpoints.

App-level PEM and webhook signing secret are configured once per App/environment. Per-installation client credentials belong in storage, not a new `.env` file or a separate server for every customer.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.