diff --git a/packages/documentation/astro.config.mjs b/packages/documentation/astro.config.mjs
index 1095a3d14a..f2299475a2 100644
--- a/packages/documentation/astro.config.mjs
+++ b/packages/documentation/astro.config.mjs
@@ -7,6 +7,7 @@ import starlightLinksValidator from 'starlight-links-validator'
import starlightFullViewMode from 'starlight-fullview-mode'
import { rehypeHeadingIds } from '@astrojs/markdown-remark'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
+import starlightVersions from 'starlight-versions'
// https://astro.build/config
export default defineConfig({
@@ -112,6 +113,10 @@ export default defineConfig({
},
link: '/overview/concepts/account-servicing-entity'
},
+ {
+ label: 'Multi-tenancy',
+ link: '/overview/concepts/multi-tenancy'
+ },
{
label: 'Accounting',
translations: {
@@ -184,6 +189,10 @@ export default defineConfig({
label: 'Overview and checklist',
link: '/integration/requirements/overview'
},
+ {
+ label: 'Tenants',
+ link: '/integration/requirements/tenants'
+ },
{
label: 'Assets',
link: '/integration/requirements/assets'
@@ -358,12 +367,18 @@ export default defineConfig({
}
],
plugins: [
+ starlightVersions({
+ current: {
+ label: 'v2-beta'
+ },
+ versions: [{ slug: 'v1-beta' }]
+ }),
starlightLinksValidator({
exclude: [
- '/apis/graphql/auth',
- '/apis/graphql/backend',
- '/apis/graphql/auth/*',
- '/apis/graphql/backend/*'
+ '**/apis/graphql/auth',
+ '**/apis/graphql/backend',
+ '**/apis/graphql/auth/*',
+ '**/apis/graphql/backend/*'
],
errorOnLocalLinks: false,
errorOnFallbackPages: false,
diff --git a/packages/documentation/package.json b/packages/documentation/package.json
index f8372a3583..70268aa9e4 100644
--- a/packages/documentation/package.json
+++ b/packages/documentation/package.json
@@ -5,6 +5,7 @@
"scripts": {
"start": "astro dev",
"build:docs": "astro build",
+ "build:docs:graphql": "spectaql config-auth.yml && spectaql config-backend.yml",
"preview": "astro preview",
"astro": "astro"
},
@@ -19,6 +20,7 @@
"remark-math": "^6.0.0",
"spectaql": "^3.0.4",
"starlight-fullview-mode": "^0.2.3",
- "starlight-links-validator": "^0.17.0"
+ "starlight-links-validator": "^0.17.0",
+ "starlight-versions": "^0.5.5"
}
}
diff --git a/packages/documentation/public/img/admin-guide/assets.png b/packages/documentation/public/img/admin-guide/assets.png
index acb170d3bd..355266651a 100644
Binary files a/packages/documentation/public/img/admin-guide/assets.png and b/packages/documentation/public/img/admin-guide/assets.png differ
diff --git a/packages/documentation/public/img/admin-guide/create-asset.png b/packages/documentation/public/img/admin-guide/create-asset.png
index 8af0d987f6..10cc3ef94b 100644
Binary files a/packages/documentation/public/img/admin-guide/create-asset.png and b/packages/documentation/public/img/admin-guide/create-asset.png differ
diff --git a/packages/documentation/public/img/admin-guide/create-peer.png b/packages/documentation/public/img/admin-guide/create-peer.png
index 86cfd2bdfd..25ba1e74bf 100644
Binary files a/packages/documentation/public/img/admin-guide/create-peer.png and b/packages/documentation/public/img/admin-guide/create-peer.png differ
diff --git a/packages/documentation/public/img/admin-guide/create-tenant.png b/packages/documentation/public/img/admin-guide/create-tenant.png
new file mode 100644
index 0000000000..d3006fcef2
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/create-tenant.png differ
diff --git a/packages/documentation/public/img/admin-guide/create-wallet-address.png b/packages/documentation/public/img/admin-guide/create-wallet-address.png
index a7c50167b0..950b25a569 100644
Binary files a/packages/documentation/public/img/admin-guide/create-wallet-address.png and b/packages/documentation/public/img/admin-guide/create-wallet-address.png differ
diff --git a/packages/documentation/public/img/admin-guide/delete-asset.png b/packages/documentation/public/img/admin-guide/delete-asset.png
new file mode 100644
index 0000000000..c029c45518
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/delete-asset.png differ
diff --git a/packages/documentation/public/img/admin-guide/edit-asset.png b/packages/documentation/public/img/admin-guide/edit-asset.png
index 1a4e5c15f1..2e625d7f4b 100644
Binary files a/packages/documentation/public/img/admin-guide/edit-asset.png and b/packages/documentation/public/img/admin-guide/edit-asset.png differ
diff --git a/packages/documentation/public/img/admin-guide/edit-peer.png b/packages/documentation/public/img/admin-guide/edit-peer.png
index fdd9f99fa7..00dca55569 100644
Binary files a/packages/documentation/public/img/admin-guide/edit-peer.png and b/packages/documentation/public/img/admin-guide/edit-peer.png differ
diff --git a/packages/documentation/public/img/admin-guide/edit-tenant.png b/packages/documentation/public/img/admin-guide/edit-tenant.png
new file mode 100644
index 0000000000..64bdf20fd8
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/edit-tenant.png differ
diff --git a/packages/documentation/public/img/admin-guide/edit-wallet-address.png b/packages/documentation/public/img/admin-guide/edit-wallet-address.png
index 28a2062215..6be9c2d787 100644
Binary files a/packages/documentation/public/img/admin-guide/edit-wallet-address.png and b/packages/documentation/public/img/admin-guide/edit-wallet-address.png differ
diff --git a/packages/documentation/public/img/admin-guide/home-post.png b/packages/documentation/public/img/admin-guide/home-post.png
new file mode 100644
index 0000000000..b56b501a2f
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/home-post.png differ
diff --git a/packages/documentation/public/img/admin-guide/home-pre.png b/packages/documentation/public/img/admin-guide/home-pre.png
new file mode 100644
index 0000000000..6ee84cb1b6
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/home-pre.png differ
diff --git a/packages/documentation/public/img/admin-guide/payments.png b/packages/documentation/public/img/admin-guide/payments.png
index 9d371be460..5fe7799e7e 100644
Binary files a/packages/documentation/public/img/admin-guide/payments.png and b/packages/documentation/public/img/admin-guide/payments.png differ
diff --git a/packages/documentation/public/img/admin-guide/peers.png b/packages/documentation/public/img/admin-guide/peers.png
index a448f299c3..956d67e099 100644
Binary files a/packages/documentation/public/img/admin-guide/peers.png and b/packages/documentation/public/img/admin-guide/peers.png differ
diff --git a/packages/documentation/public/img/admin-guide/tenants.png b/packages/documentation/public/img/admin-guide/tenants.png
new file mode 100644
index 0000000000..53a2aba8a5
Binary files /dev/null and b/packages/documentation/public/img/admin-guide/tenants.png differ
diff --git a/packages/documentation/public/img/admin-guide/wallet-addresses.png b/packages/documentation/public/img/admin-guide/wallet-addresses.png
index 6d90169eea..5da5d1facd 100644
Binary files a/packages/documentation/public/img/admin-guide/wallet-addresses.png and b/packages/documentation/public/img/admin-guide/wallet-addresses.png differ
diff --git a/packages/documentation/public/img/admin-guide/webhooks.png b/packages/documentation/public/img/admin-guide/webhooks.png
index b45a576ddd..6be1262739 100644
Binary files a/packages/documentation/public/img/admin-guide/webhooks.png and b/packages/documentation/public/img/admin-guide/webhooks.png differ
diff --git a/packages/documentation/public/img/v1-beta/concepts-interledger.png b/packages/documentation/public/img/v1-beta/concepts-interledger.png
new file mode 100644
index 0000000000..db71949067
Binary files /dev/null and b/packages/documentation/public/img/v1-beta/concepts-interledger.png differ
diff --git a/packages/documentation/public/img/v1-beta/concepts-telemetry-architecture.png b/packages/documentation/public/img/v1-beta/concepts-telemetry-architecture.png
new file mode 100644
index 0000000000..57958d8865
Binary files /dev/null and b/packages/documentation/public/img/v1-beta/concepts-telemetry-architecture.png differ
diff --git a/packages/documentation/public/img/v1-beta/localenv-databases.png b/packages/documentation/public/img/v1-beta/localenv-databases.png
new file mode 100644
index 0000000000..d9a7ea29cc
Binary files /dev/null and b/packages/documentation/public/img/v1-beta/localenv-databases.png differ
diff --git a/packages/documentation/public/img/v1-beta/rafiki-architecture.png b/packages/documentation/public/img/v1-beta/rafiki-architecture.png
new file mode 100644
index 0000000000..691ded0bbb
Binary files /dev/null and b/packages/documentation/public/img/v1-beta/rafiki-architecture.png differ
diff --git a/packages/documentation/src/components/Header.astro b/packages/documentation/src/components/Header.astro
index d351408c42..1186e839b5 100644
--- a/packages/documentation/src/components/Header.astro
+++ b/packages/documentation/src/components/Header.astro
@@ -1,19 +1,21 @@
---
import { getRelativeLocaleUrl } from 'astro:i18n';
-import Search from "@astrojs/starlight/components/Search.astro";
import ThemeSelect from "@astrojs/starlight/components/ThemeSelect.astro";
import LanguageSelect from '@astrojs/starlight/components/LanguageSelect.astro';
import SocialIcons from "@astrojs/starlight/components/SocialIcons.astro";
import RafikiLogo from "../components/RafikiLogo.astro";
+import VersionSelect from 'starlight-versions/components/VersionSelect.astro'
+import VersionSearch from 'starlight-versions/components/VersionSearch.astro'
---
-
+
+
diff --git a/packages/documentation/src/content.config.ts b/packages/documentation/src/content.config.ts
index 76002a672d..820968dce3 100644
--- a/packages/documentation/src/content.config.ts
+++ b/packages/documentation/src/content.config.ts
@@ -1,8 +1,10 @@
import { defineCollection } from 'astro:content'
import { docsLoader } from '@astrojs/starlight/loaders'
import { docsSchema, i18nSchema } from '@astrojs/starlight/schema'
+import { docsVersionsLoader } from 'starlight-versions/loader'
export const collections = {
docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }),
- i18n: defineCollection({ type: 'data', schema: i18nSchema() })
+ i18n: defineCollection({ type: 'data', schema: i18nSchema() }),
+ versions: defineCollection({ loader: docsVersionsLoader() })
}
diff --git a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx
index a7d4498ff8..3bd5659741 100644
--- a/packages/documentation/src/content/docs/admin/admin-user-guide.mdx
+++ b/packages/documentation/src/content/docs/admin/admin-user-guide.mdx
@@ -6,7 +6,17 @@ import { LinkOut } from '@interledger/docs-design-system'
import { LargeImg } from '@interledger/docs-design-system'
import KratosWarn from '/src/partials/kratos-warning.mdx'
-The Rafiki Admin application provides tools to manage peers, assets, wallet addresses, webhooks, payments, and account settings. It functions as an interface to the Rafiki [backend service](/integration/deployment/services/backend-service/) and all actions performed, such as fetching data or executing commands, are passed to the Rafiki `backend` service. The purpose of this document is to help you navigate and use the Rafiki Admin application effectively.
+The Rafiki Admin application provides tools to manage:
+
+- Tenants
+- Assets
+- Peers
+- Wallet addresses
+- Webhook events
+- Payments
+- Account settings
+
+It functions as an interface to the [Rafiki Admin APIs](/apis/graphql/admin-api-overview) and all actions performed, such as fetching data or executing commands, are passed to the Rafiki `backend` service. The purpose of this document is to help you navigate and use the Rafiki Admin application effectively.
## Getting started
@@ -15,6 +25,26 @@ The Rafiki Admin application provides tools to manage peers, assets, wallet addr
- Familiarity with general Rafiki concepts. The [Rafiki overview](/overview/overview) is a great place to start.
- Running the Rafiki `frontend` package. See [Frontend service](/integration/deployment/services/frontend-service) for more information.
+### API credential configuration
+
+To access your data and manage resources within your tenant, you must configure your API credentials. Enter your tenant ID and API secret and select **Save Credentials**.
+
+The Rafiki Admin application HMAC‑signs requests (HMAC SHA‑256) to the Backend Admin API and includes a `tenant-id` header. Requests are scoped to the tenant whose credentials you provide.
+
+
+
+After successfully configuring your API credentials, a message will appear indicating that the credentials have been configured, and the left navigation menu will be populated with the available options.
+
+
+
+The **Clear Credentials** button allows you to clear your stored API credentials. You must clear your credentials in order to switch tenants. Requests will then be scoped to the new tenant.
+
## Identity and user management
Rafiki Admin relies on the Ory Kratos identity and user management solution to handle authentication (login) and user management (account creation and password recovery).
@@ -82,6 +112,10 @@ Ory Kratos provides frontend components (such as forms and buttons) for identity
## Navigation
+:::note
+The screenshots in this guide may not perfectly reflect the appearance of the Rafiki Admin UI in all environments. Specifically, the left navigation menu may differ slightly depending on whether authentication via Kratos is enabled.
+:::
+
After logging in, you’ll be greeted by the main landing page with a left-hand navigation menu. This menu provides access to the main functionality needed to manage your Rafiki instance.
-### Create peer
+### Create tenant
-To create a new peer, select **Create Peer** from the main Peers page.
+To create a new tenant, select **Add tenant** from the main Tenants page.
-Fill out the following fields to configure and create your peer:
+Fill out the following fields to configure and create your new tenant:
-| Section | Field | Description |
-| ------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |
-| General Information | Name | A public name for the peer to identify it on your system. |
-| | Static ILP Address | The peer’s ILP address, obtained from the peer. |
-| | Max Packet Amount | The agreed-upon maximum number of packets a payment is split into. |
-| HTTP Information | Incoming Auth Tokens | A comma-separated list of tokens accepted by your Rafiki instance from a peer for incoming requests. |
-| | Outgoing Auth Token | A single token used by your Rafiki instance for all outgoing requests to authenticate itself with the peer. |
-| | Outgoing Endpoint | The URL of the peer’s server where your Rafiki instance sends outgoing requests. |
-| Asset Information | Asset | The asset used for transactions with this peer. |
+| Section | Field | Description |
+| ----------------------------- | ------------------ | ---------------------------------------------------------------- |
+| General Information | Public Name | A public name for the tenant. |
+| | Email | The tenant's email address. |
+| Sensitive Information | API Secret | The API secret for the tenant. Treat as sensitive information. |
+| Identity Provider Information | Consent URL | The URL for the tenant's identity provider consent endpoint. |
+| | Secret | The secret for the tenant's identity provider. |
+| Tenant Settings | Exchange Rates Url | The URL for the tenant's exchange rates service. |
+| | Webhook Url | The URL for the tenant's webhook endpoint. |
+| | Webhook Timeout | The timeout for the tenant's webhook requests. |
+| | Webhook Max Retry | The maximum number of retries for the tenant's webhook requests. |
+| | Wallet Address Url | The URL for the tenant's wallet address service. |
+| | ILP Address | The tenant's Interledger Protocol (ILP) address. |
-After completing these fields, select **Create** to add the new peer.
+After completing these fields, select **Create** to add the new tenant.
-### Edit peer
+### Edit tenant
-To edit an existing peer, select any peer entry from the table on the main Peers page. This opens the Edit Peer page where you can view and change peer settings.
+To edit an existing tenant, select any tenant entry from the table on the main Tenants page. This opens the Edit Tenant page where you can view and change tenant settings.
-While the Edit Peer page shares fields with the Create Peer page, it also includes fields and actions specific to managing an existing peer:
+The Edit Tenant page includes the following sections:
-| Section | Field/Action | Description |
-| --------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
-| General Information | Peer ID | A unique identifier assigned by Rafiki when the peer was created. This cannot be changed. |
-| Asset Information | View Asset | For more information about an asset, select **View asset**. |
-| Liquidity Information | Amount | Current amount of peer liquidity available. |
-| | Deposit Liquidity | To increase the amount of liquidity available, select **Deposit liquidity**. |
-| | Withdraw Liquidity | To reduce the amount of liquidity available, select **Withdraw liquidity**. |
+| Section | Field | Description |
+| ----------------------------- | ----------- | ------------------------------------------------------------------------------------------- |
+| General Information | Tenant ID | A unique identifier assigned by Rafiki when the tenant was created. This cannot be changed. |
+| | Public Name | A public name for the tenant. |
+| | Email | The tenant's email address. |
+| Identity Provider Information | Consent URL | The URL for the tenant's identity provider consent endpoint. |
+| | Secret | The secret for the tenant's identity provider. |
+| Sensitive Information | API Secret | A unique identifier assigned by Rafiki when the tenant was created. This cannot be changed. |
-After editing any of the preceding fields in the General Information or HTTP Information sections, select **Save** to commit those changes.
+You can modify the public name, email, consent URL, and secret for a tenant. The tenant ID and API secret are read-only.
-#### Delete peer
+After editing any of the preceding fields, select **Save** to commit those changes.
-The final section of the Peers page is the irreversible action of deleting a peer. Select **Delete peer** to make this change.
+#### Delete tenant
-
+As an operator, you may need to delete tenants from your Rafiki instance. The option to delete a tenant is only visible when viewing a non-operator tenant. An operator cannot delete themselves.
-Confirm the deletion by typing "delete peer" into the text field and selecting **Delete this peer**.
+To perform this irreversible action, select **Delete tenant**. Confirm the deletion by typing "delete tenant" into the text field and selecting **Delete this tenant**.
## Assets
The Assets page allows you to manage assets in your Rafiki instance, including viewing, editing, and creating assets.
-On this page, all configured assets appear in a table where you can view the asset ID, the asset code, the scale, and the withdrawal threshold.
+On this page, all configured assets appear in a table where you can view the asset ID, the asset code, the scale, and the withdrawal threshold. The list of assets shows only those for the current tenant, as determined by your API credentials.
+
+:::note
+Operators can create, edit, and delete assets for any tenant. Tenants can only view and manage their own assets.
+:::
+
+Confirm the deletion by typing "delete asset" into the text field and selecting **Delete this asset**.
+
+:::note[Prerequisites for deleting an asset]
+You can only delete an asset if it’s not in use. Before deletion, ensure:
+
+- No wallet addresses are associated with the asset
+- No peers reference the asset
+- No payments exist that reference the asset
+
+If the asset is still referenced, the backend prevents deletion and returns an error.
+:::
+
+## Peers
+
+The Peers page allows you to manage peering relationships in your Rafiki instance, including viewing, creating, editing, and deleting peers.
+
+On this page, all configured peers appear in a table where you can view the peer name, its ILP address, asset details including the asset type and scale, and the outgoing HTTP endpoint. The list of peers shows only those for the current tenant, as determined by your API credentials.
+
+:::note
+Operators can create, edit, and delete peers for any tenant. Tenants can only view and manage their own peers.
+:::
+
+
+
+### Create peer
+
+To create a new peer, select **Create Peer** from the main Peers page.
+
+
+
+Fill out the following fields to configure and create your peer:
+
+| Section | Field | Description |
+| ------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |
+| General Information | Name | A public name for the peer to identify it on your system. |
+| | Static ILP Address | The peer’s ILP address, obtained from the peer. |
+| | Max Packet Amount | The agreed-upon maximum number of packets a payment is split into. |
+| HTTP Information | Incoming Auth Tokens | A comma-separated list of tokens accepted by your Rafiki instance from a peer for incoming requests. |
+| | Outgoing Auth Token | A single token used by your Rafiki instance for all outgoing requests to authenticate itself with the peer. |
+| | Outgoing Endpoint | The URL of the peer’s server where your Rafiki instance sends outgoing requests. |
+| Asset Information | Tenant | The tenant whose asset will be used for transactions with this peer. |
+
+If you are an operator, you must select a tenant when creating a new peer. The asset selection dropdown is dynamically populated based on the selected tenant. If you don't have any tenants, the tenant ID will default to your own.
+
+After completing these fields, select **Create** to add the new peer.
+
+### Edit peer
+
+To edit an existing peer, select any peer entry from the table on the main Peers page. This opens the Edit Peer page where you can view and change peer settings.
+
+
+
+While the Edit Peer page shares fields with the Create Peer page, it also includes fields and actions specific to managing an existing peer:
+
+| Section | Field/Action | Description |
+| --------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
+| General Information | Peer ID | A unique identifier assigned by Rafiki when the peer was created. This cannot be changed. |
+| Asset Information | View Asset | For more information about an asset, select **View asset**. |
+| Liquidity Information | Amount | Current amount of peer liquidity available. |
+| | Deposit Liquidity | To increase the amount of liquidity available, select **Deposit liquidity**. |
+| | Withdraw Liquidity | To reduce the amount of liquidity available, select **Withdraw liquidity**. |
+
+After editing any of the preceding fields in the General Information or HTTP Information sections, select **Save** to commit those changes.
+
+#### Delete peer
+
+The final section of the Edit Peer page is the irreversible action of deleting a peer. Select **Delete peer** to make this change.
+
+
+
+Confirm the deletion by typing "delete peer" into the text field and selecting **Delete this peer**.
+
## Wallet addresses
The Wallet Addresses page allows you to manage the wallet addresses associated with your Rafiki instance, including viewing, editing, and creating wallet addresses.
-On this page, all configured wallet addresses appear in the table where you can view the address URL, the public name, and the wallet status.
+On this page, all configured wallet addresses appear in the table where you can view the address URL, the public name, and the wallet status. The list of wallet addresses shows only those for the current tenant, as determined by your API credentials.
+
+:::note
+Operators can create wallet addresses for any tenant. Tenants can only create their own wallet address.
+:::
idempotence as being the property of “certain operations in…computer science whereby [the operations] can be applied multiple times without changing the result beyond the initial application.” “An operation can be repeated or retried as often as necessary without causing unintended effects. With non-idempotent operations, the algorithm may have to keep track of whether the operation was already performed.”
-Several mutations in the Admin APIs utilize an idempotency key to allow for safely retrying requests without performing operations multiple times. The key should be unique, typically a V4 UUID.
+Several mutations in the Admin APIs utilize an idempotency key to allow for safely retrying requests without performing operations multiple times. The key must be unique (for example, a UUID v4).
-For the Admin APIs, whenever a mutation with an `idempotencyKey` is called, the request payload and the request response are saved under that key. Any subsequent requests made with the same idempotency key will return the original response and status of the request, regardless of whether the request was successful. Keys are cached for a default of 24 hours. The default can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS backend` environment flag.
+For the Admin APIs, whenever a mutation with an `idempotencyKey` is called, the request payload and the request response are saved under that key. Any subsequent requests made with the same idempotency key will return the original response and status of the request, regardless of whether the request was successful. Keys are cached for a default of 24 hours. The default can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS` environment flag.
-Additionally, in the chance that a request is made while still concurrently processing the first request under the same `idempotencyKey`, the APIs will return an error. This provides further safeguards from potential errors in the system. The timing to prevent processing concurrent requests is `2` seconds by default. The default can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` environment flag.
+If a request is made while the first request under the same `idempotencyKey`, is still processing, the APIs return an error to prevent concurrent duplicates. The concurrency lock duration is `2` seconds by default and can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` environment flag.
diff --git a/packages/documentation/src/content/docs/es/index.mdx b/packages/documentation/src/content/docs/es/index.mdx
index 3987d779ad..c6f87d7fb4 100644
--- a/packages/documentation/src/content/docs/es/index.mdx
+++ b/packages/documentation/src/content/docs/es/index.mdx
@@ -6,7 +6,7 @@ hero:
tagline: Rafiki is open source software that provides an efficient solution for an account servicing entity (ASE) to enable Interledger functionality on its users' accounts.
actions:
- text: Read Rafiki docs
- link: /overview/overview
+ link: /es/overview/overview
icon: open-book
variant: primary
attrs:
diff --git a/packages/documentation/src/content/docs/es/v1-beta/index.mdx b/packages/documentation/src/content/docs/es/v1-beta/index.mdx
new file mode 100644
index 0000000000..ba7c176f15
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/index.mdx
@@ -0,0 +1,46 @@
+---
+title: Hello from Rafiki
+description: Rafiki is open source software that provides an efficient solution
+ for an Account Servicing Entity to enable Interledger functionality on its
+ users' accounts.
+template: splash
+hero:
+ tagline:
+ Rafiki is open source software that provides an efficient solution for
+ an account servicing entity (ASE) to enable Interledger functionality on its
+ users' accounts.
+ actions:
+ - text: Read Rafiki docs
+ link: /es/v1-beta/overview/overview
+ icon: open-book
+ variant: primary
+ attrs:
+ data-umami-event: Landing page - Rafiki docs
+slug: es/v1-beta
+---
+
+import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'
+
+
+
+
+ Test Rafiki by running two mock ASEs that automatically peer with one
+ another.
+
+
+
+
+ Review the requirements for deploying Rafiki to a production environment.
+
+
+
+
+ Discover what's in our Backend GraphQL schema.
+
+
+
+
+ Discover what's in our Auth GraphQL schema.
+
+
+
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/account-servicing-entity.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/account-servicing-entity.mdx
new file mode 100644
index 0000000000..6c0d28f4b8
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/account-servicing-entity.mdx
@@ -0,0 +1,38 @@
+---
+title: Servicio de Cuentas de Entidad (ASE)
+slug: es/v1-beta/overview/concepts/account-servicing-entity
+---
+
+An account servicing entity (ASE) is a regulated entity that provides and maintains payment accounts for its customers. Examples of ASEs include banks, digital wallet providers, and mobile money providers.
+
+As regulated entities, ASEs are subject to the laws, rules, and regulations of their jurisdictions. As such, Rafiki should **not** be used in production environments by non-regulated entities.
+
+## Responsibilities and obligations
+
+A few examples of an ASE's responsibilities and obligations include:
+
+- Regulatory compliance
+- Account provisioning and maintenance
+- Transaction handling
+- Ledger management
+- Authentication and consent
+
+### Regulatory compliance
+
+ASEs must onboard account holders in compliance with regulatory requirements, such as performing Know Your Customer (KYC) checks, anti-money laundering (AML) processes, and sanctions screening.
+
+### Account provisioning and maintenance
+
+ASEs manage the creation, upkeep, and security of payment accounts. This includes providing channels for account holders (individuals or businesses) to interact with their accounts via mobile apps, websites, and other interfaces.
+
+### Transaction handling
+
+ASEs handle deposits and withdrawals through various external payment methods such as bank transfers, credit cards, and other payment services.
+
+### Ledger management
+
+ASEs maintain a ledger of account balances and transaction histories for their account holders.
+
+### Authentication and consent
+
+In the context of Open Payments, ASEs are responsible for authenticating resource owners (for example, account holders) and obtaining their consent when clients, such as mobile apps, request access to a resource (for example, an account).
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/accounting.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/accounting.mdx
new file mode 100644
index 0000000000..8e0287207e
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/accounting.mdx
@@ -0,0 +1,1322 @@
+---
+title: Transacciones en Rafiki
+tableOfContents:
+ maxHeadingLevel: 3
+slug: es/v1-beta/overview/concepts/accounting
+---
+
+import {
+ LinkOut,
+ Mermaid,
+ StylishHeader
+} from '@interledger/docs-design-system'
+import { Steps } from '@astrojs/starlight/components'
+
+Rafiki uses double-entry accounting to record financial transactions. In this method of bookkeeping, a transaction recorded to one account results in an equal and opposite entry to another account. For example, a \$50 credit to one account results in a \$50 debit from another account.
+
+Transactions in Rafiki represent Interledger packet interactions, denominated in a given [asset](#assets). Packet interactions can be successful, fail, or be rejected. Rafiki's accounting layer processes the interactions and converts the activities into financial records, which are then written to your [accounting database](#accounting-databases).
+
+Accounts within Rafiki are your internal [liquidity](#liquidity-accounts) and [settlement](#settlement-accounts) accounts used to fund payments, not the accounts that you service for your customers. This distinction is crucial for understanding how Rafiki handles transactions and settlements.
+
+## Assets
+
+An asset represents a transferrable item of value. Although the Interledger Protocol (ILP) supports the transfer of any asset deemed to have value, assets are generally denominated in a currency. For example fiat currencies, central bank digital currencies, and branded currencies (such as merchant reward points).
+
+Part of Rafiki's [integration requirements](/es/v1-beta/integration/requirements/assets) include adding one or more assets that you support.
+
+An asset is made up of the following properties.
+
+
+
+| Property | Type | Description | Example |
+| ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
+| `value` | BigInt | A numerical amount | `10000` |
+| `assetCode` | String | A code representing the asset. An ISO 4217 currency code should be used whenever possible. | `"USD"` |
+| `assetScale` | Integer | Difference in order of magnitude between the standard unit and a fractional unit | `2` |
+
+
+
+To convert an asset’s value into an amount that’s easier to interpret, apply the following formula.
+
+$\frac{value}{10^{assetScale}}$ = _currencyAmount_
+
+Using the example data from the preceding table, the formula looks like this:
+
+$\frac{10000}{10^2} =$100.00 USD
+
+## Accounts
+
+Rafiki uses a combination of liquidity and settlement accounts to track the amounts available to fund transactions. Rafiki does not physically hold funds in each account. Instead, it uses double-entry accounting to record the transactions. The actual settlement of amounts owed, in which funds are physically exchanged, occurs outside of both Rafiki and the Interledger Protocol.
+
+### Liquidity accounts
+
+Liquidity accounts track deposits, withdrawals, and transfers that occur during the course of a transaction. Rafiki provides liquidity accounts for assets, peers, and payments.
+
+Liquidity accounts hold either a zero or a positive balance. Rafiki ensures that the total debits to a liquidity account will not exceed the account's total credits.
+
+
+
+| Account type | What the account represents | Number of accounts |
+| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------ |
+| [Asset liquidity](#asset-liquidity-accounts) | The value, denominated in a given asset, that Rafiki has available to support cross-currency transactions | One per asset |
+| [Peer liquidity](#peer-liquidity-accounts) | The credit line, denominated in the asset of your peering relationship, that you extend to a peer | One per peer |
+| [Incoming payment liquidity](#incoming-payment-liquidity-accounts) | The value received from a completed incoming payment | One per incoming payment |
+| [Outgoing payment liquidity](#outgoing-payment-liquidity-accounts) | The value that Rafiki will attempt to send in an outgoing payment | One per outgoing payment |
+| [Wallet address liquidity](#wallet-address-liquidity-accounts) | The value that a wallet address received via SPSP | One per wallet address |
+
+
+
+#### Asset liquidity accounts
+
+Asset liquidity ensures Rafiki has enough liquidity, denominated in a given asset, to handle cross-currency (foreign exchange) transactions.
+
+An asset liquidity account represents the value that Rafiki has available for sending or forwarding ILP packets. You have one asset liquidity account for each asset you transact in.
+
+Whenever an outgoing payment/incoming payment is in a different asset than the peering relationship, the liquidity of asset accounts change depending on the FX direction. Any transaction that would result in a negative balance will fail.
+
+:::note
+If you and your peer transact in the same asset (there's no currency conversion) and you both provide your customers only with wallet addresses denominated in that asset, then there will be no movement into/from the corresponding asset's liquidity account.
+
+For example, you and your peer transact in USD and only provide your customers with USD wallet addresses. One of your customers sends $10 to your peer's customer. There's no movement from your USD asset liquidity account because there was no currency conversion. There is, however, an [outgoing payment liquidity account](/es/v1-beta/overview/concepts/accounting#outgoing-payment-liquidity-accounts) created to support the transaction.
+:::
+
+You can add a liquidity threshold for each asset liquidity account via the [`updateAsset`](https://rafiki.dev/apis/graphql/backend/mutations/#updateasset) mutation's `liquidityThreshold` input argument.
+
+When a threshold is entered, the [`asset.liquidity_low`](/es/v1-beta/integration/requirements/webhook-events#asset-liquidity-low) webhook event notifies you if an asset account's liquidity drops below the threshold.
+
+You should define and adjust asset liquidity based on your liquidity risk profile. You can deposit or withdraw asset liquidity as needed through [Rafiki Admin](/es/v1-beta/admin/admin-user-guide#edit-asset) or by using the [Backend Admin API](/es/v1-beta/admin/liquidity/asset-liquidity#manage-asset-liquidity-using-the-backend-admin-api).
+
+
+ Asset liquidity example - cross-currency transactions
+ Your Rafiki instance is configured for two assets: EUR and USD.
+
+- Rafiki holds an asset liquidity account for both EUR and USD.
+- You’ve set the asset scale of both currencies to 0.
+- Your starting EUR liquidity is 10 and your USD liquidity is 50.
+
+**Cross-currency transaction #1:**
+
+
+ 1. Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €10. 10 EUR move from the peer's liquidity account on your Rafiki instance to your EUR asset liquidity account. Your EUR liquidity increases to 20 (10 + 10).
+
+ 2. The EUR-to-USD exchange rate is applied, with €10 equating to \$12 USD. Since your starting USD liquidity is 50, your USD asset liquidity account can cover the transfer of \$12 USD to an incoming payment liquidity account. Your USD liquidity decreases to 38 (50 - 12).
+
+
+
+**Cross-currency transaction #2:**
+
+
+ 1. Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €50. Your EUR liquidity increases to 70 (20 + 50).
+
+ 2. The current EUR-to-USD exchange rate is applied, with €50 equating to \$55 USD. The transaction fails. Your USD liquidity account is 38, so you don't have enough liquidity to cover the transaction.
+
+ 3. Your EUR liquidity reduces back to 20 (70 - 50).
+
+
+
+
+#### Peer liquidity accounts
+
+Peer liquidity is the credit line you've extended to a peer. A peer liquidity account represents the amount of the line of credit that the peer still has available to them. You have one liquidity account for each peer and the account is denominated in the asset you both agreed to transact in.
+
+The amount of credit that you extend to a peer, the asset that you transact in, and the mechanism you use to settle are just a few items that should be defined in your respective peering agreements.
+
+:::note
+A peering agreement is a legal contract between the parties involved in a peering relationship. It defines terms such as the assets involved and other operational details. It is not configured or managed within Rafiki but is necessary for establishing the terms under which assets are exchanged.
+:::
+
+If a peer’s liquidity is insufficient (for example, they’ve used up their allotted credit line), payments will not be processed. Your peer should settle with you so that you can reset their liquidity.
+
+You can add a liquidity threshold for each peer liquidity account via the [`updatePeer`](https://rafiki.dev/apis/graphql/backend/mutations/#updatepeer) mutation's `liquidityThreshold` input argument.
+
+When a threshold is entered, the [`peer.liquidity_low`](/es/v1-beta/integration/requirements/webhook-events#peer-liquidity-low) webhook event notifies you if a peer's liquidity drops below the threshold.
+
+You should define and adjust each peer's liquidity based on your liquidity risk profile. You can deposit or withdraw peer liquidity as needed through [Rafiki Admin](/es/v1-beta/admin/admin-user-guide#edit-peer) or by using the [Backend Admin API](/es/v1-beta/admin/liquidity/peer-liquidity#manage-peer-liquidity-using-the-backend-admin-api).
+
+
+ Peer liquidity example
+ You and Cloud Nine Wallet are peers. You’ve agreed to extend Cloud Nine Wallet
+ a line of credit worth \$100.00 USD. This means Cloud Nine Wallet has \$100.00
+ in their peer liquidity account on your Rafiki instance. Your Rafiki instance can
+ receive packets that total up to \$100.00 from Cloud Nine Wallet. When the \$100.00
+ is used up, Cloud Nine Wallet settles with you by sending \$100.00 via the shared
+ settlement mechanism outlined in your peering agreement. When you receive the funds,
+ you reset their liquidity in Rafiki.
+
+
+#### Payment liquidity accounts
+
+Payment liquidity is the amount that's available because of an incoming or outgoing payment. Rafiki has three types of payment liquidity accounts.
+
+
+
+| Payment type | Purpose |
+| ---------------------------------------------------- | ------------------------------------------------------- |
+| [Incoming](#incoming-payment-liquidity-accounts) | For incoming payments created via the Open Payments API |
+| [Outgoing](#outgoing-payment-liquidity-accounts) | For outgoing payments created via the Open Payments API |
+| [Wallet address](#wallet-address-liquidity-accounts) | For payments sent via SPSP |
+
+
+
+##### Incoming payment liquidity accounts
+
+An incoming payment liquidity account represents the value received for an incoming payment. Incoming payments are created via Open Payments. When the first packet for an incoming payment is received, a corresponding liquidity account is automatically created. You will have one liquidity account per incoming payment.
+
+You are notified of created, completed, and expired incoming payments by listening for the appropriate [webhook events](/es/v1-beta/integration/requirements/webhook-events/#incoming-payments). Since Rafiki doesn't hold funds, anything you receive must be withdrawn and then credited to the recipient's account on your ledger.
+
+The liquidity account isn’t used again after the payment completes, but its record remains in your accounting database. When a new incoming payment occurs, a new liquidity account is created.
+
+##### Outgoing payment liquidity accounts
+
+An outgoing payment liquidity account represents the value available to send in an outgoing payment. When an outgoing payment is created via Open Payments, a corresponding liquidity account is automatically created. You will have one liquidity account per outgoing payment.
+
+You are notified of created, completed, and failed outgoing payments by listening for the appropriate [webhook events](/es/v1-beta/integration/requirements/webhook-events/#outgoing-payments). Liquidity must be deposited into the outgoing payment account before the payment can be processed.
+
+You may occasionally have excess liquidity, such as when an outgoing payment only partially completes and a portion of the send-amount remains. Since Rafiki doesn’t hold funds, any excess liquidity that remains after an outgoing payment completes must be withdrawn from the outgoing payment liquidity account. How you choose to handle the excess is up to you. You could, for example, refund the excess to the sender or take the amount as a fee.
+
+The account isn’t used again after the payment completes, but its record remains in your accounting database. When a new outgoing payment is created, a new liquidity account is created.
+
+##### Wallet address liquidity accounts
+
+A wallet address liquidity account contains the value received to a wallet address via [SPSP](/es/v1-beta/overview/concepts/interledger#simple-payment-setup-protocol-spsp). When an incoming payment is created, a corresponding liquidity account is automatically created. You will have one account per wallet address.
+
+Since Rafiki doesn’t hold funds, you must withdraw the liquidity when the payment completes and credit the funds to the recipient’s account on your ledger. You are notified to withdraw liquidity by listening for the appropriate [webhook event](/es/v1-beta/integration/requirements/webhook-events#wallet-addresses).
+
+Unlike the incoming and outgoing payment liquidity accounts, the same wallet address liquidity account will be used for future incoming SPSP payments.
+
+### Settlement accounts
+
+A settlement account represents the total funds, denominated in a single asset, that you have deposited into Rafiki. You have one settlement account for each asset you transact in.
+
+Settlement accounts hold either a zero or a negative balance. A negative balance on a settlement account means you've deposited more funds into Rafiki than you've withdrawn. The closer a settlement account's balance is to 0, the more likely it is you need to settle with your peer for the amount owed and then deposit the amount back into Rafiki.
+
+Rafiki ensures that the total credits to a settlement account do not exceed its total debits.
+
+
+ Settlement account example
+ You deposit \$10,000 into a peer's liquidity account, meaning you've extended a credit line of \$10,000 to your peer.
+
+Your peer liquidity account balance is \$10,000 and your USD settlement account balance is now -\$10,000.
+
+An incoming payment from your peer for \$100 is created, meaning your peer is using \$100 of their line of credit. Since Rafiki doesn't hold funds, you must withdraw the liquidity and credit the amount to the recipient's account on your ledger.
+
+Now, your peer liquidity account's balance is \$9,900 and your USD settlement account's balance is -\$9,900.
+
+
+
+## Accounting databases
+
+### TigerBeetle
+
+TigerBeetle is a high-performance distributed financial accounting database used by Rafiki’s [backend service](/es/v1-beta/integration/deployment/services/backend-service) to store account balance data. Both liquidity and settlement accounts in Rafiki correspond to TigerBeetle credit and debit accounts, respectively.
+
+TigerBeetle only holds balance data without any additional ILP packet metadata. For detailed information on TigerBeetle, including its consensus mechanism and its limitations, visit the official TigerBeetle documentation and blog. For more information about TigerBeetle in a production Rafiki environment, see [Running Rafiki in production](/es/v1-beta/integration/deployment/helm-k8s).
+
+### Postgres
+
+You can choose to use a separate Postgres database for accounting instead of using TigerBeetle. However, TigerBeetle is recommended due to its speed, efficiency, and dedicated design for handling double-entry/double-ledger accounting.
+
+## Transfers
+
+As with the accounts described above, Rafiki performs double-entry accounting for transfers, where increasing the total debits of one account increases the total credits of another account by the same amount, and vice versa.
+
+Transfers can complete in either a single phase or in two phases.
+
+### Single-phase transfer
+
+A single-phase transfer posts funds to accounts immediately when the transfer is created.
+
+**Example of successful single-phase incoming payment**
+
+>ASE: Fires webhook event when incoming payment completes
+ ASE->>R: Withdraws payment amount from incoming payment liquidity account
+ ASE->>ASE: Credits the recipient's account by the payment amount
+
+`}
+/>
+
+### Two-phase transfer
+
+A two-phase transfer moves funds in two stages.
+
+1. Reserve funds (`pending`)
+2. Resolve funds (`post`, `void`, or `expire`)
+
+**Example of successful two-phase incoming payment**
+
+>ASE: Fires webhook event when incoming payment completes
+ ASE->>Rafiki: Withdraws payment amount from incoming payment liquidity account (reserve funds pending)
+ ASE->>ASE: Credits the recipient's account by the payment amount
+ ASE->>Rafiki: Resolve funds (post)
+ Rafiki->>Rafiki: Two-phase transfer complete
+ `}
+/>
+
+The name two-phase transfer is a reference to the two-phase commit protocol for distributed transactions.
+
+You can [post and commit](/es/v1-beta/admin/liquidity/two-phase-transfers#post-and-commit-a-successful-transfer) a successful two-phase transfer and [void and roll back](/es/v1-beta/admin/liquidity/two-phase-transfers#void-and-roll-back-an-unsuccessful-transfer) an unsuccessful two-phase transfer by using the Backend Admin API.
+
+### Intra-Rafiki transfer examples
+
+Remember that a settlement account will always have a zero or negative balance and a liquidity account will always have a zero or positive balance.
+
+- [Deposits](#deposits)
+- [Withdrawals](#withdrawals)
+- [Payments in the same asset](#payments-in-the-same-asset)
+- [Cross currency payments](#cross-currency-payments)
+
+#### Deposits
+
+A deposit is the act of debiting the settlement account and crediting the liquidity account.
+
+**Example:** Depositing `100 USD` asset liquidity
+
+| Debit Account | Credit Account |
+| ------------- | --------------- |
+| Settlement | Asset liquidity |
+
+
+
+
+
+#### Payments in the same asset
+
+**Example:** Sender consented to a payment of `14 USD` but the quote promised to deliver `15 USD`. The send amount is less than the receive amount.
+
+| Debit Account | Credit Account |
+| ---------------- | ---------------- |
+| Outgoing payment | Incoming payment |
+| Asset liquidity | Incoming payment |
+
+
+
+
USD outgoing payment liquidity acct
+
USD asset liquidity acct
+
USD incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
14
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
1
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
15
+
+
+
+
+
+
+
+
+
+**Example:** Sender consented to a payment of `15 USD` but the quote promised to deliver `14 USD`. The send amount is more than the receive amount.
+
+| Debit Account | Credit Account |
+| ---------------- | ---------------- |
+| Outgoing payment | Incoming payment |
+| Outgoing payment | Asset liquidity |
+
+
+
+
+
+### Interledger transfer examples
+
+In these examples, the sender and receiver do not have wallet addresses at the same Rafiki instance.
+
+Remember that a settlement account will always have a zero or negative balance and a liquidity account will always have a zero or positive balance.
+
+- [Sending connector - same asset](#sending-connector---same-asset)
+- [Sending connector - cross currency](#sending-connector---cross-currency)
+- [Receiving connector - same asset](#receiving-connector---same-asset)
+- [Receiving connector - cross currency](#receiving-connector---cross-currency)
+- [Connector - same asset](#connector---same-asset)
+- [Connector - cross currency](#connector---cross-currency)
+
+#### Sending connector - same asset
+
+**Example:** Sender creates an outgoing payment for `100 USD` to an incoming payment in the same asset at a peer's Rafiki instance
+
+| Debit Account | Credit Account |
+| ---------------- | -------------- |
+| Outgoing payment | Peer liquidity |
+
+
+
+
USD outgoing payment liquidity acct
+
USD peer liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+#### Sending connector - cross currency
+
+**Example:** Sender creates an outgoing payment for `100 USD` to an incoming payment at a peer's Rafiki instance. The peering relationship is in EUR, so the payment is converted on the sending side.
+
+| Debit Account | Credit Account | Asset |
+| ---------------- | --------------- | ----- |
+| Outgoing payment | Asset liquidity | `USD` |
+| Asset Liquidity | Peer Liquidity | `EUR` |
+
+
+
+
USD outgoing payment liquidity acct
+
USD asset liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+{' '}
+
+
+
EUR asset liquidity acct
+
EUR peer liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
90
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
90
+
+
+
+
+
+
+
+
+
+#### Receiving connector - same asset
+
+**Example:** An incoming payment receives `100 USD` from an outgoing payment in the same asset at a peer's Rafiki instance.
+
+| Debit Account | Credit Account |
+| -------------- | ---------------- |
+| Peer liquidity | Incoming payment |
+
+
+
+
USD peer liquidity acct
+
USD incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+#### Receiving connector - cross currency
+
+**Example:** A Rafiki instance receives `10 USD` from a peer (peering relationship in USD) to be deposited in an incoming payment liquidity account denominated in EUR. The payment is converted to EUR and deposited.
+
+| Debit Account | Credit Account | Asset |
+| --------------- | ---------------- | ----- |
+| Peer liquidity | Asset liquidity | `USD` |
+| Asset liquidity | Incoming payment | `EUR` |
+
+
+
+
USD peer liquidity acct
+
USD asset liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
10
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
10
+
+
+
+
+
+
+{' '}
+
+
+
EUR asset liquidity acct
+
EUR incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
9
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
9
+
+
+
+
+
+
+
+
+
+#### Connector - same asset
+
+**Example:** Rafiki forwards `10 USD` from peer A to peer B.
+
+| Debit Account | Credit Account |
+| -------------- | -------------- |
+| Peer liquidity | Peer liquidity |
+
+
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/clearing-settlement.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/clearing-settlement.mdx
new file mode 100644
index 0000000000..230a5d6cb2
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/clearing-settlement.mdx
@@ -0,0 +1,30 @@
+---
+title: Compensación y liquidación
+slug: es/v1-beta/overview/concepts/clearing-settlement
+---
+
+## Clearing
+
+When a payment is made over traditional banking rails, the money doesn’t move instantly. First, there are checks to confirm that the money exists and can be transferred. Clearing networks are responsible for exchanging messages between ASEs to facilitate these checks. This process is called clearing. When a payment successfully clears, it means the payer’s ASE has an obligation to the payee’s ASE.
+
+The [Interledger Protocol (ILP)](/v1-beta/overview/concepts/interledger) is not a traditional clearing network, but does function in a similar way.
+
+- ASEs that implement the protocol must become [peers](/v1-beta/integration/requirements/peers) to transact with one another. This is comparable to traditional banking, where ASEs must use the same clearing network. An ASE can't use Interledger to transact with another ASE unless they have both implemented the protocol and have peered with one another.
+- Peered ASEs exchange ILP packets, which are packets of value that contain transaction information. ILP packets are akin to the messages exchanged during the traditional clearing process.
+- The successful exchange of ILP packets between peers creates obligations between them that must be settled. The receipt of a fulfilled ILP packet is basically a conditional IOU—a promise to pay—that affects the financial accounting balances between the peers.
+
+You can read more about clearing as it relates to ILP in the [Interledger developer docs](https://interledger.org/developers/rfcs/peering-clearing-settling/).
+
+Conceptually, Rafiki sits at the clearing level, but isn't a clearing network. It's software that makes implementing the Interledger protocol faster and easier. Rafiki uses ILP to [track liquidity](/v1-beta/overview/concepts/accounting) between assets, payments, and peers. An ASE must still connect Rafiki to their existing backend system and internal ledger for authentication, fetching exchange rates, and managing liquidity itself. For example, if an incoming payment completes in Rafiki, the ASE’s backend must credit the recipient’s account on their own system, however that might look.
+
+In any case, no movement of actual money has occurred yet.
+
+## Settlement
+
+In traditional banking, settlement is the fulfillment of an obligation between ASEs. It turns the promise of payment into a real payment by moving actual money. This occurs over a shared settlement network, such as Fedwire in the United States.
+
+When a payer’s ASE settles with the payee’s ASE, there’s a high chance that the ASE isn’t physically handing over cash. There’s more likely to be an intermediary, like a reserve bank or central bank, that maintains accounts for both ASEs. The intermediary moves funds from one account to the other, crediting and debiting the accounts as necessary.
+
+With Interledger, the concept of settlement is not that different. Each [peer](/v1-beta/integration/requirements/peers) must agree on a settlement system to use to fulfill their obligations with one another. However, ILP itself is not a settlement system. This means peers must have some other way to fulfill their obligations and exchange value. Examples can include using a real-time gross settlement system like Fedwire, an automated clearing house (ACH) network, a money transfer service, or some other payment channel. You can read more about settling as it relates to ILP in the [Interledger developer docs](https://interledger.org/developers/rfcs/settlement-engines/).
+
+Rafiki is also not a settlement system. It [keeps track of liquidity](/v1-beta/overview/concepts/accounting) through the use of liquidity and settlement accounts. Liquidity accounts track deposit, withdrawal, and transfer amounts. Settlement accounts track the availability of funds, denominated in a single asset. A negative balance means funds are available. The closer a settlement account’s balance is to zero, the more likely it is that one peer needs to settle with the other over their agreed-upon settlement system.
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/interledger.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/interledger.mdx
new file mode 100644
index 0000000000..081d172fb9
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/interledger.mdx
@@ -0,0 +1,74 @@
+---
+title: Interledger
+slug: es/v1-beta/overview/concepts/interledger
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Building and maintaining your own connector to participate on the Interledger network can be a time consuming and complex undertaking. As a reference implementation of the Interledger stack, Rafiki gives you all the tools you need to quickly and easily join the network and enable Interledger capabilities on your users’ accounts.
+
+## Packets
+
+At the core of Interledger is the Interledger Protocol (ILP). It’s a request/response protocol where requests and responses are ILP packets.
+
+These packets of data carry information about a payment. Typically, information about a single aggregate payment from sender to receiver is split into multiple ILP packets.
+
+Each packet represents a conditional IOU which affects financial accounting balances between peers. Amounts adjust based on the sender's asset, the receiver's asset, and Rafiki's configured [exchange rates service](/v1-beta/integration/requirements/exchange-rates). Then, the amounts are used to update account liquidity in your accounting database (TigerBeetle or Postgres).
+
+## Peers
+
+Interledger itself is a network of computers that enables sending payment messages across payment networks. Each computer on the network is a node.
+
+For two nodes on the Interledger network to exchange ILP packets with one another, the two nodes must be peers. There are a number of [requirements](/v1-beta/integration/requirements/peers#perform-prerequisites) that both you and your potential peer must meet to form a peering relationship.
+
+Since the purpose of peering is to facilitate payments, which often involves extending lines of credit, your peer should be someone you trust. We strongly recommend you and your potential peer define your expectations and outline your agreements in a legally binding document peering with one another.
+
+## Connectors
+
+Each node on the Interledger network can take on the role of sender, connector, or receiver, depending on the payment.
+
+- Sender - Originates the payment by sending ILP packets.
+- Connector - An intermediary between a sender and receiver that forwards ILP packets. Connectors can facilitate payments to or from anyone they’re peered with.
+- Receiver - The final recipient of the ILP packets and, as such, the payment.
+
+If the sender and receiver nodes are peers, then the payment flow is straightforward and no intermediary connector nodes are needed. However, if the sender and receiver aren’t peers, then the payment must route through one or more connectors. Rafiki’s [backend service](/v1-beta/integration/deployment/services/backend-service#interledger-connector) includes an Interledger connector for sending and receiving ILP packets.
+
+In the image below, the sender node (A) and the receiver node (C) share a common peer (B). In payments from the sender to the receiver, node B performs the role of connector to facilitate payments between the two.
+
+
+
+In reality, there can be multiple connectors between a sender node and a receiver node. As more nodes that support different assets peer with one another, the easier it becomes for payments to traverse the Interledger network.
+
+## Payment pointer
+
+A payment pointer is a type of wallet address that serves as an SPSP endpoint to facilitate sending and receiving ILP packets. Rafiki will assign each of your customers' accounts with a payment pointer.
+
+Payment pointers must resolve to an HTTPS URL and can be written out using the `$` shorthand (for example, `$wallet.example.com/alice`) or as a URL (for example, `https://wallet.example.com/alice`).
+
+See [Payment pointers and wallet addresses](/v1-beta/overview/concepts/payment-pointers) for more information.
+
+## Simple Payment Setup Protocol (SPSP)
+
+The Simple Payment Setup Protocol (SPSP) is an application layer protocol that uses HTTPS to exchange payment information.
+Every payment pointer issued by Rafiki serves as an SPSP endpoint by default (`ENABLE_SPSP_PAYMENT_POINTERS`).
+
+When a `GET` request is made to a payment pointer, the response contains the ILP address of the destination account and a shared secret. These details are needed to set up a STREAM connection between two counterparties to facilitate direct payments over Interledger.
+
+## STREAM Protocol
+
+The STREAM Protocol is the transport layer protocol in the Interledger Protocol stack. After SPSP communicates the ILP address of the destination account and the shared secret, the STREAM protocol uses these details to set up a STREAM connection between the
+counterparties.{' '}
+
+Rafiki’s `backend` service includes an Interledger connector for sending and receiving STREAM packets. STREAM packets are encoded, encrypted, and sent as the `data` field in ILP packets. The protocol uses the shared secret to authenticate and encrypt packets, and to generate conditions and fulfillments.
+
+A critical function of STREAM is to determine the path exchange rate and handle any changes in the rate. Rafiki’s exchange rates service allows you to provide exchange rate information.
+
+### STREAM receipts
+
+In a STREAM connection, a receipt can be generated by the party receiving funds for every fulfilled ILP packet. Each receipt contains a progressively higher amount, representing the total amount received over the STREAM connection.
+
+Rafiki’s Interledger connector keeps track of the total amount received for a STREAM connection in Redis to support STREAM receipts.
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/open-payments.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/open-payments.mdx
new file mode 100644
index 0000000000..2adcd350e2
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/open-payments.mdx
@@ -0,0 +1,39 @@
+---
+title: Pagos Abiertos
+tableOfContents:
+ maxHeadingLevel: 2
+slug: es/v1-beta/overview/concepts/open-payments
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Rafiki follows the Open Payments standard to enable third-party clients to securely retrieve account information and initiate payments from your customers’ accounts with their consent. The standard describes a uniform way to create and manage grants and resources for incoming payments, quotes, and outgoing payments.
+
+### Example use case - retrieve account information
+
+Some of your customers use a third-party application that allows them to create budgets and monitor their spending. The application can call the Open Payments APIs, enabling it to communicate with any account servicing entity that implements the Open Payments standard. When your customers give the app permission to retrieve their transaction history, the app communicates with your Rafiki instance via the Open Payments APIs to obtain grants from your authorization server and transaction history from your resource server.
+
+### Further reading
+
+We strongly encourage you to familiarize yourself with the Open Payments standard. Extensive documentation is available on the Open Payments website. We recommend you start by reviewing all the pages within the _Intro to Open Payments_ section. Here are a few links to get you started.
+
+-
+ Getting started with Open Payments
+
+-
+ Client keys
+
+-
+ HTTP message signatures
+
+-
+ Grant negotiation and authorization
+
+
+## Rafiki's backend service
+
+Rafiki’s [`backend`](/v1-beta/integration/deployment/services/backend-service) service is the main service for handling business logic and external communication. The service is responsible for, among other things, exposing the endpoints of the Open Payments APIs for clients to perform account management tasks. Every request and response is validated against the Open Payments specification.
+
+## Rafiki's auth service
+
+Rafiki’s [`auth`](/v1-beta/integration/deployment/services/auth-service) service is a reference implementation of an opinionated Open Payments authorization server. The authorization server is responsible for delegating authorization (via grants) to clients to use the Open Payments APIs, resolving clients’ public keys to authenticate and authorize incoming requests, and creating payments and quotes on the backend. Open Payments leverages the Grant Negotiation and Authorization Protocol (GNAP) for delegating authorization. You can learn more about the protocol by reviewing its specification.
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/payment-pointers.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/payment-pointers.mdx
new file mode 100644
index 0000000000..006ab25b59
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/payment-pointers.mdx
@@ -0,0 +1,56 @@
+---
+title: Apuntadores de pago y direcciones de billeteras
+slug: es/v1-beta/overview/concepts/payment-pointers
+---
+
+## Payment pointers
+
+A payment pointer is a standardized identifier for a payment account that supports Interledger payments. Each payment pointer must resolve to an HTTPS URL that serves as an [SPSP](/v1-beta/overview/concepts/interledger#simple-payment-setup-protocol-spsp) endpoint to facilitate sending and receiving ILP packets.
+
+You can determine whether a URL is a payment pointer by sending a `GET` request to the URL with an `accept: application/spsp4+json` header.
+
+```http title="Example request"
+curl --request GET \
+ --url https://wallet.example.com/alice/ \
+ --header 'accept: application/spsp4+json'
+```
+
+A response from an SPSP server means the URL is a payment pointer.
+
+```http title="Example response"
+{
+ "destination_account":"example.0.cloudnine.ind.alice.cdfa5e16-e759",
+ "shared_secret":"7h0s7EpQDqcgzqmX-mwrNHFHinPvJq8Jw",
+}
+```
+
+Payment pointers are often written out using the `$` shorthand. For example, `$wallet.example.com/alice`, which resolves to `https://wallet.example.com/alice/`.
+
+Rafiki assigns each of your customers' accounts with a payment pointer. This payment pointer is also a wallet address because Rafiki supports both Interledger and Open Payments.
+
+## Wallet addresses
+
+A wallet address is a secure, unique URL for a payment account that supports Open Payments. It acts as an entry point into the Open Payments APIs, facilitating interactions like sending and receiving payments.
+
+You can determine whether a URL is a wallet address by sending a `GET` request to the URL with an `accept: application/json` header.
+
+```http title="Example request"
+curl --request GET \
+ --url https://wallet.example.com/alice \
+ --header 'accept: application/json'
+```
+
+A valid response means the URL is a wallet address.
+
+```http title="Example response"
+{
+ "id": "https://wallet.example.com/alice",
+ "publicName": "Alice",
+ "assetCode": "USD",
+ "assetScale": 2,
+ "authServer": "https://auth.wallet.example.com",
+ "resourceServer": "https://wallet.example.com",
+}
+```
+
+Rafiki assigns each of your customers' accounts with a wallet address. This wallet address is also a payment pointer because Rafiki supports Open Payments and Interledger. See the integration requirements for [wallet addresses](/v1-beta/integration/requirements/wallet-addresses) for more information.
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/telemetry.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/telemetry.mdx
new file mode 100644
index 0000000000..97e5baba84
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/concepts/telemetry.mdx
@@ -0,0 +1,265 @@
+---
+title: Telemetría
+slug: es/v1-beta/overview/concepts/telemetry
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+The objective of the telemetry feature is to gather metrics and establish an infrastructure for visualizing valuable network insights. Some of the metrics that we at the Interledger Foundation collect include:
+
+- The total amount of money transferred via packet data within a specified time frame (daily, weekly, monthly)
+- The number of transactions that have been at least partially successful
+- The number of ILP packets flowing through the network
+- The average amount of money held within the network per transaction
+- The average time it takes for an outgoing payment to complete
+
+Our goals are to:
+
+- Track the growth of the network in terms of transaction sizes and the number of transactions processed
+- Use the data for our own insights
+- Enable you to gain your own insights
+
+### Privacy and optionality
+
+Privacy is a paramount concern for the Interledger Foundation. Rafiki’s telemetry feature is designed to provide valuable network insights without violating privacy or aiding malicious ASEs. Review the [Privacy](#privacy) section below for more information.
+
+The telemetry feature is currently enabled by default on test environments (environments not dealing with real money). When active, the feature transmits metrics to the testnet collector. You can opt in to sharing your metrics with a livenet collector when operating in a production livenet environment (with real money). Regardless of environment, you can also opt-out of telemetry completely. Review the [telemetry environment variables](#telemetry-environment-variables) for more information.
+
+### Architecture
+
+
+
+### OpenTelemetry (OTEL)
+
+The Interledger Foundation has adopted OpenTelemetry (OTEL) to ensure compliance with a standardized framework that is compatible with a variety of tool suites. OTEL allows you to use your preferred tools for data analysis, while Rafiki is instrumented and observable through a standardized metrics format.
+
+### Telemetry Elastic Container Service (ECS) cluster
+
+The Telemetry Replica service is hosted on AWS ECS Fargate and is configured for availability and load balancing of custom ADOT (AWS Distro for OpenTelemetry) Collector ECS tasks.
+
+When you opt for telemetry, metrics are sent to our Telemetry service. To enable you to build your own telemetry solutions, instrumented Rafiki can send data to multiple endpoints. This allows for the integration of a local OTEL Collector container that can support custom requirements. Metrics communication is facilitated through gRPC.
+
+### OTEL SDK - Rafiki instrumentation
+
+The OTEL SDK is integrated into Rafiki to create, collect, and export metrics. The SDK integrates seamlessly with the OTEL Collector.
+
+### Prometheus - AMP
+
+The Interledger Foundation uses Amazon Managed Service for Prometheus (AMP) to collect data from the telemetry cluster.
+
+:::note
+AMP offers limited configuration options and cannot crawl data outside of AWS. This limitation led us to adopt a push model, using `prometheusRemoteWrite`, instead of a pull model. For future development, we may consider hosting our own Prometheus.
+:::
+
+### Grafana - Grafana Cloud
+
+Grafana Cloud is used for data visualization dashboards and offers multiple tools that extend Prometheus Promql.
+
+:::note
+The Interledger Foundation initially used Amazon-hosted Grafana which did not meet our needs for embedding dashboards. Grafana Cloud offers a feature called _public dashboards_ which allows us to share dashboards. However, embedding may still pose a challenge.
+:::
+
+### Exchange rates
+
+For telemetry purposes, all amounts collected by instrumented Rafiki should be converted to a base currency.
+
+:::caution[Privacy reasoning]
+If only two ASEs are peered over a non-USD currency and we collect data in that currency, it would be easy to determine the volumes moved between those two ASEs. To maintain privacy, we convert all amounts to a base currency.
+:::
+
+If an ASE does not provide the necessary exchange rate for a transaction, the telemetry solution still converts the amount to the base currency using external exchange rates. A Lambda function on AWS retrieves and stores the external exchange rates. The function is triggered by a daily `CloudWatch` event and stores the rates in a public S3 bucket. The S3 bucket does not have versioning, and the data is overwritten daily to further ensure privacy.
+
+### Instrumentation
+
+Rafiki has the following metrics. All data points (counter increases) are exported to collection endpoints at a configurable interval. The default interval is 15 seconds.
+
+
+
+| Metric | Type | Description | Behavior |
+| ------------------------- | --------- | ------------------------------------------ | ------------------------------------------------------------------------------ |
+| `transactions_total` | Counter | Count of funded outgoing transactions | Increases by 1 for each successfully funded outgoing payment resource |
+| `packet_count_prepare` | Counter | Count of ILP Prepare packets that are sent | Increases by 1 for each Prepare packet that's sent |
+| `packet_count_fulfill` | Counter | Count of ILP Fulfill packets | Increases by 1 for each Fulfill packet that's received |
+| `packet_count_reject` | Counter | Count of ILP Reject packets | Increases by 1 for each Reject packet that's received |
+| `packet_amount_fulfill` | Counter | Amount sent through the network | Increases by the amount sent in each ILP packet |
+| `transaction_fee_amounts` | Counter | Fee amount sent through network | Increases by the amount sent minus the amount received for an outgoing payment |
+| `ilp_pay_time_ms` | Histogram | Time to complete an ILP payment | Records the time taken to make an ILP payment |
+
+
+
+The current implementation only collects metrics on the SENDING side of a transaction. Metrics for external Open Payments transactions RECEIVED by a Rafiki instance in the network are not collected.
+
+## Privacy
+
+Rafiki telemetry is designed with a strong emphasis on privacy. The system anonymizes user data and refrains from collecting identifiable information. Since transactions can originate from any user to a Rafiki instance, the privacy measures are implemented directly at the source (each Rafiki instance). This means that at the individual level, the data is already anonymous as single Rafiki instances service transactions for multiple users.
+
+### Differential privacy and local differential privacy (LDP)
+
+Differential privacy is a system for publicly sharing information about a dataset by describing the patterns of groups within the dataset while withholding information about individuals in the dataset. Local differential privacy (LDP) is a variant of differential privacy where noise is added to each individual’s data point before the data point is sent to the server. This ensures that the server never sees the actual data, providing a strong privacy guarantee.
+
+### Rounding technique and bucketing
+
+Rafiki’s telemetry implementation uses a rounding technique that essentially aggregates multiple transactions into the same value, making them indistinguishable. This is achieved by dividing the transaction values into buckets and rounding the values to the nearest bucket.
+
+The bucket size is calculated based on the raw transaction value. For lower value transactions, which are expected to occur more frequently, the bucket sizes are determined linearly for higher granularity. However, after a certain threshold, the bucket size calculation switches to a logarithmic function to ensure privacy for higher value transactions (which are less frequent but pose greater privacy concerns).
+
+To handle outliers, a clipping technique is implemented, capping the buckets. Any value that exceeds a given threshold is placed in a single bucket. Conversely, any value that falls below a certain minimum is also placed in a single bucket. This ensures that both high and low outliers do not disproportionately affect the overall data, providing further privacy guarantees for these transactions.
+
+### Laplacian distribution
+
+The Laplacian distribution is often used in differential privacy due to its double exponential decay property. This property ensures that a small change in the data does not significantly affect the probability distribution of the output, providing a strong privacy guarantee.
+
+To achieve local differential privacy (LDP), noise is selected from the Laplacian distribution and added to the rounded values. The noise is generated based on a privacy parameter, which is calculated using the sensitivity of the function.
+
+The sensitivity of a function in differential privacy is the maximum amount that any single observation can change the output of the function. In this case, the sensitivity is considered to be the maximum of the rounded value and the bucket size.
+
+The privacy parameter is computed as one-tenth of the sensitivity. This parameter controls the trade-off between privacy and utility: a smaller privacy parameter means more privacy but less utility, and a larger privacy parameter means less privacy but more utility.
+
+The noise, selected from the Laplacian distribution, is then generated using this privacy parameter and added to the rounded value. If the resulting value is zero, the value is set to half the bucket size to ensure that the noise does not completely obscure the transaction value.
+
+### Currency conversion
+
+Another factor that obscures sensitive data is currency conversion. In cross-currency transactions, exchange rates are provided by you, as the ASE, internally. As such, the exchange rates cannot be correlated to an individual transaction. If you don’t or can’t provide the necessary rates, an external API for exchange rates is used. The obtained exchange rates are overwritten frequently in this case, with no versioning or history access. This introduces an additional layer of noise and further protects the privacy of the transactions.
+
+### Experimental transaction values when using the algorithm
+
+The following table shows the values in the algorithm when running transactions for different amounts. The raw value increases as you move down the rows of the table. All values are in scale 4.
+
+
+
+### References
+
+Rafiki’s telemetry solution is a combination of techniques described in various white papers on privacy-preserving data collection. More information can be found in the following papers:
+
+-
+ Local differential privacy for human-centered computing
+
+-
+ Collecting telemetry data privately
+
+-
+ RAPPOR: Randomized aggregatable privacy-preserving ordinal response
+
+
+## Deploy custom telemetry
+
+Rafiki allows you to build your own telemetry solution based on the OpenTelemetry (OTEL) standardized metrics format that Rafiki exposes.
+
+You must deploy your own OTEL Collector that acts as a sidecar container to Rafiki, then provide the OTEL Collector’s ingest endpoint so that Rafiki can begin sending metrics to the collector.
+
+### Telemetry environment variables
+
+#### Required
+
+When the `ENABLE_TELEMETRY` variable is `true`, the following are required.
+
+
+
+| Variable name | Type | Description |
+| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `INSTANCE_NAME` | String | Your Rafiki instance's name used to communicate for telemetry and auto-peering. For telemetry, it's used to distinguish between the different instances pushing data to the telemetry collector. |
+
+
+
+#### Optional
+
+
+
+| Variable name | Type | Description |
+| -------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ENABLE_TELEMETRY` | Boolean | Enables the telemetry service on Rafiki. Defaults to `true`. |
+| `LIVENET` | Boolean | Determines where to send metrics. Defaults to `false`, resulting in metrics being sent to the testnet OTEL Collector.
Set to `true` on production environments dealing with real money.
|
+| `OPEN_TELEMETRY_COLLECTOR_URLS` | String | A CSV of URLs for OTEL Collectors (e.g., `http://otel-collector-NLB-e3172ff9d2f4bc8a.elb.eu-west-2.amazonaws.com:4317,http://happy-life-otel-collector:4317`). |
+| `OPEN_TELEMETRY_EXPORT_INTERVAL` | Number | Indicates, in milliseconds, how often the instrumented Rafiki instance should send metrics. Defaults to`15000`. |
+| `TELEMETRY_EXCHANGE_RATES_URL` | String |
Defines the endpoint Rafiki queries for exchange rates. Used as a fallback if/when [exchange rates](/v1-beta/integration/requirements/exchange-rates) aren’t provided.
When set, the response format of the external exchange rates API should be of type `rates`, as is expected by the rate service.
Defaults to `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json`, which points to a public S3 that has the previously mentioned required format, updated daily.
|
+
+
+
+### Example Docker OTEL Collector image and configuration
+
+Below is an example of a Docker OTEL Collector image and configuration that integrates with Rafiki and sends data to a Prometheus remote write endpoint.
+
+You can test the configuration in our [Local Playground](/v1-beta/integration/playground/overview) by providing the environment variables in the preceding table to `happy-life-backend` in the `docker-compose.yml` file.
+
+#### Docker Compose config
+
+```yaml
+# Serves as example for optional local collector configuration
+happy-life-otel-collector:
+ image: otel/opentelemetry-collector-contrib:latest
+ command: ['--config=/etc/otel-collector-config.yaml', '']
+ environment:
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID-''}
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY-''}
+ volumes:
+ - ../collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml
+ networks:
+ - rafiki
+ expose:
+ - 4317
+ ports:
+ - '13132:13133' # health_check extension
+```
+
+#### OTEL Collector config
+
+Supplemental documentation is available in OTEL’s Collector Configuration documentation.
+
+```yaml wrap
+# Serves as example for the configuration of a local OpenTelemetry Collector that sends metrics to an AWS Managed Prometheus Workspace
+# Sigv4auth required for AWS Prometheus Remote Write access (USER with access keys needed)
+
+extensions:
+ sigv4auth:
+ assume_role:
+ arn: 'arn:aws:iam::YOUR-ROLE:role/PrometheusRemoteWrite'
+ sts_region: 'YOUR-REGION'
+
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ http:
+ cors:
+ allowed*origins:
+ - http://*
+ - https://\_
+
+processors:
+ batch:
+
+exporters:
+ logging:
+ verbosity: 'normal'
+ prometheusremotewrite:
+ endpoint: 'https://aps-workspaces.YOUR-REGION.amazonaws.com/workspaces/ws-YOUR-WORKSPACE-IDENTIFIER/api/v1/remote_write'
+ auth:
+ authenticator: sigv4auth
+
+service:
+ telemetry:
+ logs:
+ level: 'debug'
+ metrics:
+ level: 'detailed'
+ address: 0.0.0.0:8888
+ extensions: [sigv4auth]
+ pipelines:
+ metrics:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [logging, prometheusremotewrite]
+```
diff --git a/packages/documentation/src/content/docs/es/v1-beta/overview/overview.mdx b/packages/documentation/src/content/docs/es/v1-beta/overview/overview.mdx
new file mode 100644
index 0000000000..33f449ca21
--- /dev/null
+++ b/packages/documentation/src/content/docs/es/v1-beta/overview/overview.mdx
@@ -0,0 +1,45 @@
+---
+title: Sobre Rafiki
+slug: es/v1-beta/overview/overview
+---
+
+import { LinkOut, Tooltip } from '@interledger/docs-design-system'
+import { Card, CardGrid } from '@astrojs/starlight/components'
+
+Implementing and maintaining the [Interledger Protocol (ILP)](#interledger) stack on your own can be difficult and time-consuming. Rafiki makes it easy to integrate with the Interledger network without needing to develop and maintain your own implementations.
+
+Rafiki is open-source software maintained by a dedicated team and freely available to any licensed [account servicing entity](/es/v1-beta/overview/concepts/account-servicing-entity) (ASE) wanting to implement Interledger and [Open Payments](#pagos-abiertos) on users' accounts.
+
+:::tip[Try it out]
+The [Local Playground](/es/v1-beta/integration/playground/overview) allows you to test Rafiki by running two mock account servicing entities that automatically peer with one another.
+:::
+
+## Casos de Uso
+
+### Pagos entre pares (ASEs)
+
+In the context of Rafiki, a peer is another ASE with whom you transact. Forming a peering relationship requires you to both agree on the currency in which you will transact, on a settlement mechanism and cadence, and other details. Interledger creates interoperability between different payment systems and currencies, making it easier for peers to directly transact with one another.
+
+### Pagos de comercio electrónico
+
+If a merchant accepts Interledger or Open Payments as a payment method, then a customer can pay using their wallet address instead of entering, for example, a credit card number and other personal details on the merchant’s site. Rafiki’s implementation of Interledger and Open Payments means your account holders can use their wallet addresses for both one-time purchases and recurring purchases, such as subscriptions, anywhere Interledger or Open Payments is an accepted payment method.
+
+### Monetización web
+
+With Web Monetization, site visitors can pay an amount of their choosing to a participating site with little to no interaction. Both the site and the site visitor must have an Open Payments-enabled wallet address to receive and send payments. Rafiki’s implementation of Interledger’s Simple Payment Setup Protocol (SPSP) and the Open Payments standard means you can assign one or more wallet addresses to your account holders’ accounts, making these accounts support incoming and outgoing Web Monetization payments right out of the box.
+
+## Interledger
+
+The Interledger network is a network of nodes that have implemented the Interledger Protocol (ILP) stack. Rafiki is a reference implementation if the ILP stack, enabling you to more easily become a node on the network and start sending and receiving payments.
+
+Interledger is designed to be a network on top of existing payment networks that serves as the interoperability layer between them all, forwarding payment messages (packets) while also taking care of currency conversion. Interledger ensures that packets take the fastest and cheapest route from one Interledger node to another.
+
+[Learn more about Interledger](/es/v1-beta/overview/concepts/interledger)
+
+## Pagos Abiertos
+
+Open Payments is an API standard and set of open RESTful APIs that facilitate interoperability in the setup and completion of payments. The standard provides a uniform way to create and manage grants and resources for incoming payments, quotes, and outgoing payments.
+
+By following the Open Payments standard, Rafiki allows your customers' accounts to become Open Payments-enabled. Clients, such as mobile apps, can then call the Open Payments APIs to securely retrieve transaction data and initiate payments from your customers' accounts with your customers' prior consent.
+
+[Learn more about Open Payments](/es/v1-beta/overview/concepts/open-payments)
diff --git a/packages/documentation/src/content/docs/integration/deployment/helm-k8s.mdx b/packages/documentation/src/content/docs/integration/deployment/helm-k8s.mdx
index f3850eb8b0..710fe9849b 100644
--- a/packages/documentation/src/content/docs/integration/deployment/helm-k8s.mdx
+++ b/packages/documentation/src/content/docs/integration/deployment/helm-k8s.mdx
@@ -47,344 +47,13 @@ Create a `values.yaml` file to customize your Rafiki deployment.
A template file is in progress and will be included in this section when published.
:::
-
-
-Click to expand
-```yaml
-# Rafiki values.yaml for Kubernetes deployment
-
-# =====================================================================
-
-# REQUIRED CONFIGURATION:
-
-# The following sections contain values that MUST be customized
-
-# for your specific environment before deployment
-
-# =====================================================================
-
-# Global settings
-
-global:
-imageRegistry: "" # OPTIONAL: Specify if using a private registry
-imagePullSecrets: [] # REQUIRED: If using private registry, add your pull secrets here
-storageClass: "" # REQUIRED: Set to your cluster's storage class
-
-# Backend service configuration
-
-backend:
-enabled: true
-image:
-repository: ghcr.io/interledger/rafiki/backend
-tag: latest # REQUIRED: Change to specific version for production
-pullPolicy: IfNotPresent
-replicaCount: 1 # CONSIDER: Adjust based on your load requirements
-resources:
-requests:
-cpu: 100m
-memory: 256Mi
-limits:
-cpu: 500m
-memory: 512Mi
-service:
-type: ClusterIP # CONSIDER: May need LoadBalancer or NodePort depending on your setup
-port: 3000
-ingress:
-enabled: false # REQUIRED: Set to true if exposing outside the cluster
-annotations: {} # REQUIRED: Add annotations for your ingress controller (nginx, traefik, etc.)
-hosts: - host: rafiki-backend.local # REQUIRED: Change to your actual domain
-paths: ["/"]
-tls: [] # REQUIRED: Configure if using HTTPS
-env: # Environment variables for backend
-NODE_ENV: production
-DATABASE_URL: "postgresql://postgres:postgres@rafiki-postgresql:5432/rafiki" # REQUIRED: Update credentials
-REDIS_URL: "redis://rafiki-redis:6379" # REQUIRED: Update if using auth with Redis
-AUTH_SERVER_GRANT_TYPES: "authorization_code,refresh_token"
-AUTH_SERVER_DOMAIN: "http://rafiki-auth-server" # REQUIRED: Update to your auth server URL
-OPEN_PAYMENTS_URL: "https://wallet.example.com/open-payments" # REQUIRED: Update to your Open Payments URL # TigerBeetle configuration
-USE_TIGERBEETLE: "true" # REQUIRED: Enable TigerBeetle for accounting
-TIGERBEETLE_CLUSTER_ID: "0" # REQUIRED: Must match tigerbeetle.config.clusterID
-TIGERBEETLE_REPLICA_ADDRESSES: "tigerbeetle-0.tigerbeetle:3004,tigerbeetle-1.tigerbeetle:3004,tigerbeetle-2.tigerbeetle:3004" # REQUIRED: List all TigerBeetle replicas
-
-# Auth server configuration
-
-authServer:
-enabled: true
-image:
-repository: ghcr.io/interledger/rafiki/auth
-tag: latest # REQUIRED: Change to specific version for production
-pullPolicy: IfNotPresent
-replicaCount: 1 # CONSIDER: Adjust based on your load requirements
-resources:
-requests:
-cpu: 100m
-memory: 256Mi
-limits:
-cpu: 500m
-memory: 512Mi
-service:
-type: ClusterIP # CONSIDER: May need LoadBalancer or NodePort depending on your setup
-port: 3001
-ingress:
-enabled: false # REQUIRED: Set to true if exposing outside the cluster
-annotations: {} # REQUIRED: Add annotations for your ingress controller
-hosts: - host: rafiki-auth.local # REQUIRED: Change to your actual domain
-paths: ["/"]
-tls: [] # REQUIRED: Configure if using HTTPS
-env:
-NODE_ENV: production
-DATABASE_URL: "postgresql://postgres:postgres@rafiki-postgresql:5432/rafiki" # REQUIRED: Update credentials
-REDIS_URL: "redis://rafiki-redis:6379" # REQUIRED: Update if using auth with Redis
-BACKEND_URL: "http://rafiki-backend:3000" # REQUIRED: Must match your backend service name
-
-# Frontend configuration
-
-frontend:
-enabled: true
-image:
-repository: ghcr.io/interledger/rafiki/frontend
-tag: latest # REQUIRED: Change to specific version for production
-pullPolicy: IfNotPresent
-replicaCount: 1
-resources:
-requests:
-cpu: 100m
-memory: 128Mi
-limits:
-cpu: 200m
-memory: 256Mi
-service:
-type: ClusterIP # CONSIDER: May need LoadBalancer or NodePort depending on your setup
-port: 3002
-ingress:
-enabled: false # REQUIRED: Set to true and configure for user access
-annotations: {} # REQUIRED: Add annotations for your ingress controller
-hosts: - host: rafiki.local # REQUIRED: Change to your actual domain
-paths: ["/"]
-tls: [] # REQUIRED: Configure if using HTTPS
-env:
-NODE_ENV: production
-BACKEND_URL: "http://rafiki-backend:3000" # REQUIRED: Must match your backend service configuration
-AUTH_SERVER_URL: "http://rafiki-auth-server:3001" # REQUIRED: Must match your auth server configuration
-
-# Connector configuration
-
-connector:
-enabled: true
-image:
-repository: ghcr.io/interledger/rafiki/connector
-tag: latest # REQUIRED: Change to specific version for production
-pullPolicy: IfNotPresent
-replicaCount: 1 # CONSIDER: Adjust based on your traffic needs
-resources:
-requests:
-cpu: 200m
-memory: 256Mi
-limits:
-cpu: 1000m
-memory: 512Mi
-service:
-type: ClusterIP
-port: 3003
-env:
-NODE_ENV: production
-DATABASE_URL: "postgresql://postgres:postgres@rafiki-postgresql:5432/rafiki" # REQUIRED: Update credentials
-REDIS_URL: "redis://rafiki-redis:6379" # REQUIRED: Update if using auth with Redis
-BACKEND_URL: "http://rafiki-backend:3000" # REQUIRED: Must match your backend service name
-ILP_ADDRESS: "test.rafiki" # REQUIRED: Set to your production ILP address
-CONNECTOR_URL: "http://0.0.0.0:3003" # REQUIRED: Set to your connector's publicly accessible URL for ILP peers # TigerBeetle configuration
-USE_TIGERBEETLE: "true" # REQUIRED: Enable TigerBeetle for accounting
-TIGERBEETLE_CLUSTER_ID: "0" # REQUIRED: Must match tigerbeetle.config.clusterID
-TIGERBEETLE_REPLICA_ADDRESSES: "tigerbeetle-0.tigerbeetle:3004,tigerbeetle-1.tigerbeetle:3004,tigerbeetle-2.tigerbeetle:3004" # REQUIRED: List all TigerBeetle replicas
-
-# PostgreSQL configuration
-
-postgresql:
-enabled: true # Set to false if using external PostgreSQL
-auth:
-username: postgres # REQUIRED: Change for production
-password: postgres # REQUIRED: Change for production - USE A STRONG PASSWORD
-database: rafiki
-primary:
-persistence:
-enabled: true
-size: 8Gi # REQUIRED: Adjust based on your data volume
-service:
-ports:
-postgresql: 5432
-
-# Redis configuration
-
-redis:
-enabled: true # Set to false if using external Redis
-architecture: standalone # CONSIDER: Use replication for production
-auth:
-enabled: false # REQUIRED: Set to true and configure password for production
-password: "" # REQUIRED: Set a strong password if auth is enabled
-master:
-persistence:
-enabled: true
-size: 8Gi # REQUIRED: Adjust based on your data volume
-service:
-ports:
-redis: 6379
-
-# Monitoring
-
-monitoring:
-enabled: false # CONSIDER: Enable for production environments
-prometheus:
-enabled: false # CONSIDER: Enable for production monitoring
-grafana:
-enabled: false # CONSIDER: Enable for production dashboards
-
-# Persistence configuration for data that needs to be persisted
-
-persistence:
-enabled: true
-storageClass: "" # REQUIRED: Set to your cluster's storage class
-accessMode: ReadWriteOnce
-size: 10Gi # REQUIRED: Adjust based on your data volume
-
-# Security settings
-
-securityContext:
-enabled: true
-runAsUser: 1000
-runAsGroup: 1000
-fsGroup: 1000
-
-# Pod Security settings
-
-podSecurityContext:
-enabled: true
-
-# Network policy settings
-
-networkPolicy:
-enabled: false # CONSIDER: Enable for production to restrict pod communication
-
-# Configure service accounts
-
-serviceAccount:
-create: true
-name: "rafiki" # REQUIRED: Change if conflicts with existing service accounts
-annotations: {} # REQUIRED: Add annotations if using IAM roles for service accounts
-
-# Configure pod disruption budget
-
-podDisruptionBudget:
-enabled: false # CONSIDER: Enable for production for high availability
-minAvailable: 1
-
-# Resource requests and limits for init containers
-
-initContainers:
-resources:
-requests:
-cpu: 100m
-memory: 128Mi
-limits:
-cpu: 200m
-memory: 256Mi
-
-# =====================================================================
-
-# TIGERBEETLE CONFIGURATION:
-
-# Configuration for TigerBeetle as the accounting database
-
-# =====================================================================
-
-tigerbeetle:
-enabled: true # REQUIRED: Set to true to use TigerBeetle for accounting
-image:
-repository: ghcr.io/tigerbeetle/tigerbeetle
-tag: 0.14.2 # REQUIRED: Check for the latest compatible version
-pullPolicy: IfNotPresent
-replicaCount: 3 # REQUIRED: For production, use at least 3 replicas for consensus
-resources:
-requests:
-cpu: 500m
-memory: 1Gi
-limits:
-cpu: 2000m
-memory: 4Gi
-service:
-type: ClusterIP
-port: 3004
-persistence:
-enabled: true
-storageClass: "" # REQUIRED: Set to your cluster's storage class
-size: 20Gi # REQUIRED: Adjust based on expected transaction volume
-accessMode: ReadWriteOnce
-config:
-clusterID: 0 # REQUIRED: Set a unique cluster ID
-replicaCount: 3 # Should match replicaCount above # For consensus algorithm (1f+1 redundancy where f is number of failures tolerated) # 1 replica: 0 fault tolerance # 3 replicas: 1 fault tolerance (recommended minimum for production) # 5 replicas: 2 fault tolerance (recommended for critical systems)
-
-````
-
-
-
-
-#### Configure environment variables
-
-Each Rafiki service can be configured via environment variables. Below are the main environment variables for each service:
-
-
-Auth service
-
-The Rafiki `auth` service is responsible for handling authentication and authorization for your application. It connects to a Postgres database to store auth-related resources and a Redis database for storing session data. See [Auth service](/integration/deployment/services/auth-service/) for more information.
-
-Ports exposed:
-
-- 3003 (`ADMIN_PORT`) is used for the Auth Admin API
-- 3006 (`AUTH_PORT`) is used for the Open Payments authorization server
-
-:::caution[Running Rafiki behind a proxy]
-If you plan to run your Rafiki instance behind a proxy, you must set the `TRUST_PROXY` variable to `true`
-:::
-
-
-
-
-
-Backend service
-
-The Rafiki `backend` service handles business logic and external communication. It exposes the Open Payments APIs and an Interledger connector for sending and receiving packets. It connects to a Redis database for caching, a Postgres database for Open Payments resources, and TigerBeetle for accounting liquidity. See [Backend service](/integration/deployment/services/backend-service) for more information.
-
-:::note[TigerBeetle or Postgres for accounting database]
-TigerBeetle is recommended, but if you would rather use Postgres as an accounting database make sure to set `USE_TIGERBEETLE` to false.
-:::
-
-Ports exposed:
-
-- 3000 (`OPEN_PAYMENTS_PORT`) is used for the Open Payments resource server
-- 3001 (`ADMIN_PORT`) is used for the Backend Admin API
-- 3002 (`CONNECTOR_PORT`) is used for the ILP connector to send and receive ILP packets
-
-
-
-
-
-Frontend service
-
-The Rafiki `frontend` service provides an internal admin interface for managing your Rafiki instance. It communicates with the Backend Admin API to facilitate administrative tasks. See [Frontend service](/integration/deployment/services/frontend-service) for more information.
-
-Ports exposed:
-
-- 3005 (`PORT`) is used to host the Rafiki Admin app
-
-
-
-
#### Install Rafiki
Install Rafiki using the following command:
```bash
helm install rafiki interledger/rafiki -f values.yaml
-````
+```
This will deploy all Rafiki components to your Kubernetes cluster with the configurations specified in your `values.yaml` file.
diff --git a/packages/documentation/src/content/docs/integration/deployment/services/auth-service.mdx b/packages/documentation/src/content/docs/integration/deployment/services/auth-service.mdx
index 42f6662219..9ac4ffa997 100644
--- a/packages/documentation/src/content/docs/integration/deployment/services/auth-service.mdx
+++ b/packages/documentation/src/content/docs/integration/deployment/services/auth-service.mdx
@@ -5,7 +5,7 @@ title: Auth service
import { LinkOut } from '@interledger/docs-design-system'
import Auth from '/src/partials/auth-variables.mdx'
-Rafiki’s `auth` service provides you with a reference implementation of an Open Payments authorization server. You can use the `auth` service as an alternative to developing your own in-house service for grant authorization and authentication.
+Rafiki’s `auth` service provides you with a reference implementation of an Open Payments authorization server and allows you to manage authentication and authorization on a per-tenant basis. You can use the `auth` service as an alternative to developing your own in-house service for grant authorization and authentication.
The authorization server:
@@ -36,7 +36,7 @@ Review the Open P
## Identity provider (IdP)
-An identity provider (IdP) is a system or service that manages user authentication, identity information, and consent. When you use your Google Account credentials to “Sign in with Google” on an app or website, for example, Google is acting as your identity provider.
+An identity provider (IdP) is a system or service that manages user authentication, identity information, and consent. When you use your Google Account credentials to “Sign in with Google” on an app or website, for example, Google is acting as your identity provider. Each tenant must use their own IdP for authentication.
You must integrate with an [IdP](/integration/requirements/open-payments/idp) when using Rafiki's `auth` service because the Open Payments standard requires interactive outgoing payment _grant_ requests. In an interactive request, there must be explicit interaction by an individual (for example, a client's end-user) to approve or deny the grant. In this case, the grant must be explicitly approved before an outgoing payment is created.
diff --git a/packages/documentation/src/content/docs/integration/deployment/services/backend-service.mdx b/packages/documentation/src/content/docs/integration/deployment/services/backend-service.mdx
index 8997f68c04..317a18ba73 100644
--- a/packages/documentation/src/content/docs/integration/deployment/services/backend-service.mdx
+++ b/packages/documentation/src/content/docs/integration/deployment/services/backend-service.mdx
@@ -16,12 +16,17 @@ Rafiki’s `frontend` service provides an optional internal admin interface, called the [Rafiki Admin application](/admin/admin-user-guide), for you to manage your Rafiki instance through a Remix web app. This service communicates with the [Backend Admin API](/apis/graphql/admin-api-overview#backend-admin-api) to facilitate administrative tasks within Rafiki.
+Rafiki’s `frontend` service provides an optional internal admin interface, called the [Rafiki Admin application](/admin/admin-user-guide), for you to manage your Rafiki instance through a Remix web app. The Rafiki Admin application is the primary way for operators to manage tenants and their resources. This service communicates with the [Backend Admin API](/apis/graphql/admin-api-overview#backend-admin-api) to facilitate administrative tasks within Rafiki.
## Requirements
@@ -21,7 +21,7 @@ You must also set the environment variables for the `frontend` service.
## Rafiki Admin settings
-While the `frontend` service is not required to run a Rafiki instance, it's highly recommended. A number of administrative tasks could be performed programmatically via the Backend Admin API, but the `frontend` service makes these functions available through a user-friendly interface.
+While the `frontend` service is not required to run a Rafiki instance, it's highly recommended. A number of administrative tasks could be performed programmatically via the Backend Admin API, but the `frontend` service makes these functions available through a user-friendly interface. The Rafiki Admin application allows operators to manage tenants and their resources, and to configure tenant-specific settings such as the wallet address prefix. The `frontend` service HMAC-signs requests (HMAC SHA-256) to the Backend Admin API and includes a `tenant-id` header. The `tenant-id` header identifies the tenant on whose behalf the request is made.
## Environment variables
diff --git a/packages/documentation/src/content/docs/integration/playground/overview.mdx b/packages/documentation/src/content/docs/integration/playground/overview.mdx
index 61c3116093..6398d3745e 100644
--- a/packages/documentation/src/content/docs/integration/playground/overview.mdx
+++ b/packages/documentation/src/content/docs/integration/playground/overview.mdx
@@ -33,7 +33,7 @@ These packages depend on the following databases:
style='max-width:300px'
/>
-The Local Playground comes with containerized versions of the Rafiki packages and two pre-configured docker-compose files (Cloud Nine Wallet and Happy Life Bank) to start two mock account servicing entities with their respective Rafiki `backend` and `auth` servers. They automatically peer, and two to three user accounts are created on both of them.
+The Local Playground comes with containerized versions of the Rafiki packages and three pre-configured docker-compose files. Cloud Nine Wallet and Happy Life Bank will start two mock account servicing entities with their respective Rafiki `backend` and `auth` servers. They automatically peer, and two to three user accounts are created on both of them. The third file is for Cloud Ten Wallet which is a mock ASE representing a tenant in a multi-tenant environment. See [Enabling multi-tenancy](#enabling-multi-tenancy) for more information.
This environment will set up a playground where you can use the GraphQL Admin APIs and the Open Payments APIs.
@@ -72,12 +72,22 @@ If you want the primary instance (Cloud Nine Wallet) to use Postgres as the acco
pnpm localenv:compose:psql up
```
-The local environment consists of a primary and secondary Rafiki instance, each with its docker-compose file (Cloud Nine Wallet, Happy Life Bank). The primary Cloud Nine Wallet docker-compose file (`./cloud-nine-wallet/docker-compose.yml`) includes the primary Rafiki `backend` and `auth` services, as well as the required data stores, which include TigerBeetle (if enabled), Redis, and Postgres. The primary instance contains all of the necessary components so that it can run independently.
+#### Enabling multi-tenancy
-The secondary Happy Life Bank docker-compose file (`./happy-life-bank/docker-compose.yml`) includes only the Rafiki services, not the data stores. It uses the data stores created by the primary Rafiki instance, so it can’t be run independently. The `pnpm localenv:compose up` command starts both the primary and secondary instances.
+The Local Playground can be configured to simulate a multi-tenant environment. In this configuration, Cloud Nine Wallet and Happy Life Bank are configured as separate operators, while Cloud Ten Wallet is configured as a tenant of Cloud Nine Wallet.
+
+To run the local environment with multi-tenancy enabled, execute the following command from the root of the project:
+
+```bash title="Enabling multi-tenancy"
+pnpm localenv:compose:multitenancy up
+```
### Environment components
+The local environment consists of a primary and secondary Rafiki instance, each with its docker-compose file (Cloud Nine Wallet, Happy Life Bank). The primary Cloud Nine Wallet docker-compose file (`./cloud-nine-wallet/docker-compose.yml`) includes the primary Rafiki `backend` and `auth` services, as well as the required data stores, which include TigerBeetle (if enabled), Redis, and Postgres. The primary instance contains all of the necessary components so that it can run independently.
+
+The secondary Happy Life Bank docker-compose file (`./happy-life-bank/docker-compose.yml`) includes only the Rafiki services, not the data stores. It uses the data stores created by the primary Rafiki instance, so it can’t be run independently. The `pnpm localenv:compose up` command starts both the primary and secondary instances.
+
The following components are made available via the Local Playground:
diff --git a/packages/documentation/src/content/docs/integration/requirements/assets.mdx b/packages/documentation/src/content/docs/integration/requirements/assets.mdx
index 90f39924eb..3d6a0b137f 100644
--- a/packages/documentation/src/content/docs/integration/requirements/assets.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/assets.mdx
@@ -7,9 +7,19 @@ tableOfContents:
import { Tabs, TabItem } from '@astrojs/starlight/components'
import { Badge } from '@astrojs/starlight/components'
import { LinkOut } from '@interledger/docs-design-system'
+import TenantIdHmacNote from '/src/partials/tenant-id-hmac-note.mdx'
An asset represents an item of value that can be transferred via the Interledger Protocol. Assets in Rafiki are added through the Backend Admin API or the [Rafiki Admin](/admin/admin-user-guide/#assets) application.
+**Permissions**
+
+- Operators can create assets for any tenant
+- Operators can view any asset
+- Operators can edit and delete assets that belong to any tenant
+- Tenants can only view their own assets
+- Tenants can only edit and delete their own assets
+- Tenants cannot create assets
+
## Add an asset
@@ -24,6 +34,7 @@ An asset represents an item of value that can be transferred via the Interledger
id
code
scale
+ tenantId
}
}
}
@@ -39,6 +50,9 @@ An asset represents an item of value that can be transferred via the Interledger
}
```
For more information about this mutation's input object, see [`CreateAssetInput`](/apis/graphql/backend/#definition-CreateAssetInput).
+
+
+
```json
@@ -51,11 +65,17 @@ An asset represents an item of value that can be transferred via the Interledger
"asset": {
"id": "b3dffeda-1e0e-47d4-82a3-69b1a622eeb9",
"code": "USD",
- "scale": 2
+ "scale": 2,
+ "tenantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
}
}
```
+
+:::note[Tenant ID in the asset response]
+The `asset` object in the response will include the `tenantId` of the tenant to which the asset belongs. This `tenantId` is used to identify the tenant when processing requests related to the asset.
+:::
+
diff --git a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx
index 10f4864692..f2c43a0964 100644
--- a/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/exchange-rates.mdx
@@ -5,7 +5,7 @@ title: Exchange rates
import { Badge } from '@astrojs/starlight/components'
import { LinkOut } from '@interledger/docs-design-system'
-If you plan to support cross-currency transactions, you must specify from where your Rafiki instance will fetch current exchange rates.
+If you plan to support cross-currency transactions, you must specify where your Rafiki instance fetches exchange rates. Tenants may host their own rates endpoint, but if one isn’t configured, Rafiki falls back to the default `EXCHANGE_RATES_URL`.
A rate probe precedes every Interledger payment. The probe provides a quote that estimates the full cost of transferring value over the network. For a rate probe involving a cross-currency transaction to be successful, Rafiki needs to know the exchange rates for each currency that makes up the transaction.
@@ -13,7 +13,7 @@ Often, it's the receiving ASE that provides the exchange rates for each ILP pack
## Specify your exchange rates endpoint
-Rafiki fetches exchange rates from your exchange rates endpoint. Set your endpoint via the `backend` service's `EXCHAGE_RATES_URL` variable. An OpenAPI specification for the endpoint is available.
+Operators configure a tenant-specific exchange rates endpoint using the Backend Admin API. If a tenant doesn’t have one configured, Rafiki uses the default `EXCHANGE_RATES_URL` environment variable. An OpenAPI specification for the endpoint is available.
```bash title='Example'
EXCHANGE_RATES_URL: http://cloud-nine-wallet/rates
@@ -88,10 +88,10 @@ export function loader({ request }: LoaderFunctionArgs) {
-| Variable | Type | Description | Required |
-| ------------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- |
-| `EXCHANGE_RATES_URL` | `backend` | Your exchange rates endpoint | Y |
-| `EXCHANGE_RATES_LIFETIME` | `backend` | The amount of time Rafiki caches exchange rates, in ms | Y |
-| `SLIPPAGE` | `backend` | The variance allowed between a quote and the actual amount required when a payment is initiated | Y |
+| Variable | Type | Description | Required |
+| ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | -------- |
+| `EXCHANGE_RATES_URL` | `backend` | The default exchange rates endpoint. This URL is used if a tenant-specific rates endpoint isn't configured. | Y |
+| `EXCHANGE_RATES_LIFETIME` | `backend` | The amount of time Rafiki caches exchange rates, in ms | Y |
+| `SLIPPAGE` | `backend` | The variance allowed between a quote and the actual amount required when a payment is initiated | Y |
diff --git a/packages/documentation/src/content/docs/integration/requirements/open-payments/idp.mdx b/packages/documentation/src/content/docs/integration/requirements/open-payments/idp.mdx
index a362154fb7..0b784b30d4 100644
--- a/packages/documentation/src/content/docs/integration/requirements/open-payments/idp.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/open-payments/idp.mdx
@@ -16,6 +16,8 @@ Responsibilities of your IdP include:
- Sending a request to the authorization server to finish the interaction
- Redirecting the user after the interaction is complete
+Each tenant must use their own IdP.
+
:::note
We provide Ory Kratos, a cloud-based user management system, for the identity and user management of your Rafiki Admin users. Kratos is for internal use only and **cannot** be used as your IdP for Open Payments.
:::
@@ -45,7 +47,7 @@ Rafiki's authorization server also extends an [API](#interaction-endpoints) that
### Environment variables
-The following variables must be configured for the `auth` service.
+The following variables configure the `auth` service. Operators set the `IDENTITY_SERVER_URL` and `IDENTITY_SERVER_SECRET` variables per tenant via the Backend Admin API. The `backend` service persists these values and keeps them in sync with the `auth` service's tenant table and are used to secure communications between the `auth` service and the tenant's IdP.
@@ -133,7 +135,7 @@ The `x-idp-secret` header is specific to Rafiki's authorization server and is us
- `POST /grant/:id/:nonce/accept`
- `POST /grant/:id/:nonce/reject`
-The header's purpose is to secure communications between the IdP and the authorization server. Its value should be a shared secret known to both entities. When the IdP server sends requests to the authorization server, the IdP must provide the secret via this header.
+The header's purpose is to secure communications between the IdP and the authorization server. Its value should be the `IDENTITY_SERVER_SECRET` configured for the specific tenant. When the IdP server sends requests to the authorization server, the IdP must provide the secret via this header.
:::note
If you're running your own authorization server rather than using the server provided by Rafiki, you can add security in any way you see fit. You aren't required to use the `x-idp-secret` header.
diff --git a/packages/documentation/src/content/docs/integration/requirements/overview.mdx b/packages/documentation/src/content/docs/integration/requirements/overview.mdx
index 100c499263..7269d5a3ef 100644
--- a/packages/documentation/src/content/docs/integration/requirements/overview.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/overview.mdx
@@ -13,6 +13,12 @@ An account servicing entity (ASE) is an entity that provides and maintains payme
Rafiki should not be used in production by non-regulated entities.
+## Configure multi-tenancy
+
+You must configure multi‑tenancy by establishing the operator and enabling tenant‑scoped access. Generate a UUID v4 for `OPERATOR_TENANT_ID` and a strong, random `ADMIN_API_SECRET`, and plan a secure out‑of‑band process to deliver each tenant’s `id` and `apiSecret` after creation.
+
+[Manage tenants](/integration/requirements/tenants)
+
## Support at least one asset
You must set up Rafiki to support at least one asset. An asset in Rafiki represents an item of value that can be transferred via the Interledger Protocol. Since the Interledger Protocol aims to create an internet of value, it allows for the transfer of any asset, not just currency. In practice, however, assets are usually denominated in a currency (fiat or branded currencies).
@@ -64,6 +70,9 @@ You must integrate with an IdP if you plan to use the authorization server provi
Ensure you've completed the following tasks before you deploy Rafiki to a production environment and join the Interledger network.
- [ ] You are a licensed financial account servicing entity within the jurisdictions you operate in
+- [ ] You have generated a UUID v4 for `OPERATOR_TENANT_ID` and a strong, random `ADMIN_API_SECRET`
+- [ ] You are HMAC-signing Backend Admin API requests (HMAC SHA-256) and include `tenant-id` and `signature` headers
+- [ ] You have established a secure, out-of-band process to deliver tenant credentials (`id` and `apiSecret`) to each tenant after creation
- [ ] You have added at least one asset, either through the Backend Admin API or the Rafiki Admin app
- [ ] You have implemented a strategy for creating wallet addresses for your account holders
- [ ] You have set up your webhook endpoint and understand how to handle each webhook event
diff --git a/packages/documentation/src/content/docs/integration/requirements/peers.mdx b/packages/documentation/src/content/docs/integration/requirements/peers.mdx
index 4cf634ffe1..99c9683858 100644
--- a/packages/documentation/src/content/docs/integration/requirements/peers.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/peers.mdx
@@ -7,6 +7,7 @@ tableOfContents:
import { Tabs, TabItem } from '@astrojs/starlight/components'
import { LinkOut } from '@interledger/docs-design-system'
import { Badge } from '@astrojs/starlight/components'
+import TenantIdHmacNote from '/src/partials/tenant-id-hmac-note.mdx'
To join the Interledger network and be able to send and receive payments, you must add one or more peers to your Rafiki instance. Peering establishes the connections needed for your Rafiki instance to interact with another account servicing entity (ASE). The purpose of this guide is to help you set up and manage peers.
@@ -18,6 +19,15 @@ Refer to the [Rafiki Admin user guide](/admin/admin-user-guide#peers) for detail
Whether you are using the Backend Admin API or the Rafiki Admin application, the underlying configurations and requirements remain the same. Choose the interface that best suits your individual workflow.
:::
+**Permissions**
+
+- Operators can create peers for any tenant
+- Operators can view any peer
+- Operators can edit and delete peers that belong to any tenant
+- Tenants can only view their own peers
+- Tenants can only edit and delete their own peers
+- Tenants cannot create peers
+
## Perform prerequisites
:::note
@@ -40,11 +50,11 @@ Your ILP
### Communicate a connection endpoint
-The connection endpoint will be a url that the other peer will send packets to.
+The connection endpoint will be a URL that the other peer will send packets to.
### Exchange auth tokens for the connection endpoint
-Incoming `authtokens` allow you to authenticate that packets sent from your peer originated from your peer's Interledger connector and were not tampered en route. The outgoing `authtoken` allows your peer to authenticate that received packets originated from your Interledger connector and were not tampered with en route. The use of auth tokens is not required when [autopeering with the Test Network](/integration/playground/autopeering).
+Incoming `authtokens` allow you to authenticate that packets sent from your peer originated from your peer's Interledger connector and were not tampered en route. The outgoing `authtoken` allows your peer to authenticate that received packets originated from your Interledger connector and were not tampered with en route. The use of auth tokens is not required when [autopeering with the Interledger test network](/integration/playground/autopeering).
### Agree on a settlement mechanism
@@ -54,9 +64,9 @@ The settlement mechanism you both agree to use is what facilitates the transfer
### Deposit an initial liquidity for your peer
-While you may deposit an `initiaLiquidity` for your peer, you can deposit liquidity later using the `depositPeerLiquidity` mutation.
+While you may deposit an `initialLiquidity` for your peer, you can deposit liquidity later using the `depositPeerLiquidity` mutation.
-### Define a maxPacketAmout value
+### Define a maxPacketAmount value
The `maxPacketAmount` specifies the maximum packet size you are willing to accept from the peer. Your peer's `maxPacketAmount` value does not need to match, as this value is independently set by each ASE. If omitted, payments will not be broken into smaller packets.
@@ -86,6 +96,7 @@ As mentioned in the prerequisites, you must add an asset to your Rafiki instance
}
staticIlpAddress
name
+ tenantId
}
}
}
@@ -107,6 +118,9 @@ As mentioned in the prerequisites, you must add an asset to your Rafiki instance
}
```
For more information about this mutation's input object, see [`CreatePeerInput`](/apis/graphql/backend/#definition-CreatePeerInput).
+
+
+
```json
@@ -123,12 +137,18 @@ As mentioned in the prerequisites, you must add an asset to your Rafiki instance
"scale": 2
},
"staticIlpAddress": "g.othergreatwallet",
- "name": "The Other Great Wallet"
+ "name": "The Other Great Wallet",
+ "tenantId": "123e4567-e89b-12d3-a456-426614174000"
}
}
}
}
```
+
+:::note[Tenant ID in the peer response]
+The `peer` object in the response will include the `tenantId` of the tenant to which the peer belongs. This `tenantId` is used to identify the tenant when processing requests related to the peer.
+:::
+
@@ -158,6 +178,7 @@ In this example we will update the peer we just created. Rather than change any
}
maxPacketAmount
liquidityThreshold
+ tenantId
}
}
}
@@ -175,6 +196,9 @@ In this example we will update the peer we just created. Rather than change any
}
```
For more information about this mutation's input object, see [`UpdatePeerInput`](/apis/graphql/backend/#definition-UpdatePeerInput).
+
+
+
```json
@@ -194,12 +218,18 @@ In this example we will update the peer we just created. Rather than change any
}
},
"maxPacketAmount": 1000,
- "liquidityThreshold": 100
+ "liquidityThreshold": 100,
+ "tenantId": "123e4567-e89b-12d3-a456-426614174000"
}
}
}
}
```
+
+:::note[Tenant ID in the peer response]
+The `peer` object in the response will include the `tenantId` of the tenant to which the peer belongs. This `tenantId` is used to identify the tenant when processing requests related to the peer.
+:::
+
diff --git a/packages/documentation/src/content/docs/integration/requirements/tenants.mdx b/packages/documentation/src/content/docs/integration/requirements/tenants.mdx
new file mode 100644
index 0000000000..7be3a1e5fd
--- /dev/null
+++ b/packages/documentation/src/content/docs/integration/requirements/tenants.mdx
@@ -0,0 +1,198 @@
+---
+title: Tenants
+tableOfContents:
+ maxHeadingLevel: 4
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+In Rafiki, a tenant represents an isolated environment for an account servicing entity (ASE). Each tenant has its own set of resources, such as assets, peers, and wallet addresses, and its own configuration settings. This allows multiple ASEs to share a single Rafiki instance while maintaining data isolation and security. The purpose of this guide is to help you set up and manage tenants.
+
+While this guide focuses on operators managing tenants from the Backend Admin API, the Rafiki Admin application offers the same capabilities in a user-friendly interface.
+
+Refer to the [Rafiki Admin user guide](/admin/admin-user-guide/#tenants) for detailed instructions and examples of creating and managing tenants through the application.
+
+## Tenant properties
+
+Each tenant on a given Rafiki instance has the following properties:
+
+
+
+| Property | Description |
+| --------------- | ------------------------------------------------------------------------------------------------------------------- |
+| `id` | Unique identifier for the tenant used in API requests and webhook events. |
+| `email` | The tenant's email address. |
+| `apiSecret` | Secret used to HMAC-sign Backend Admin API requests (HMAC SHA-256) for this tenant. |
+| `idpConsentUrl` | The tenant's identity provider (IdP) consent URL used to redirect end-users for interactive grants (Open Payments). |
+| `idpSecret` | Secret used to authenticate requests from the tenant's IdP to Rafiki. |
+| `publicName` | Public display name for the tenant (shown in the Rafiki Admin application). |
+| `settings` | Key-value pairs for initial tenant settings. See the table below. |
+
+
+
+## Tenant settings
+
+Tenant settings allow operators to customize tenant behavior. These settings are stored as key-value pairs and can be managed via the Backend Admin API or the Rafiki Admin application.
+
+
+
+| Setting | Description |
+| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `EXCHANGE_RATES_URL` | The URL of the tenant's exchange rates service. This setting is used to configure the source of exchange rate data for the tenant. |
+| `WEBHOOK_URL` | The URL of the tenant's webhook endpoint. This setting is used to configure the endpoint that will receive webhook events for the tenant. |
+| `WEBHOOK_TIMEOUT` | The timeout for the tenant's webhook requests (in milliseconds). This setting is used to configure the maximum amount of time to wait for a response from the webhook endpoint. |
+| `WEBHOOK_MAX_RETRY` | The maximum number of retries for the tenant's webhook event when a non-200 status is returned or if the request timed out. |
+| `WALLET_ADDRESS_URL` | Base URL for wallet addresses created for the tenant. This setting **cannot** be updated once set. |
+| `ILP_ADDRESS` | Base Interledger Protocol (ILP) address for the tenant. |
+
+
+
+:::note
+Only operators can create, edit, and delete tenants.
+:::
+
+## Create a tenant
+
+After you create a tenant, securely communicate the tenant `id` and `apiSecret` to the tenant out-of-band.
+
+
+
+ ```graphql
+ mutation CreateTenant($input: CreateTenantInput!) {
+ createTenant(input: $input) {
+ tenant {
+ id
+ publicName
+ email
+ apiSecret
+ idpConsentUrl
+ idpSecret
+ }
+ }
+ }
+ ```
+
+
+ ```json
+ {
+ "input": {
+ "publicName": "Tenant Name",
+ "email": "tenant@example.com",
+ "apiSecret": "your-secret-api-key",
+ "idpConsentUrl": "https://example.com/consent",
+ "idpSecret": "your-idp-secret"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateTenantInput`](/apis/graphql/backend/#definition-CreateTenantInput).
+
+
+
+ ```json
+ {
+ "data": {
+ "createTenant": {
+ "tenant": {
+ "id": "123e4567-e89b-12d3-a456-426614174000",
+ "publicName": "Tenant Name",
+ "email": "tenant@example.com",
+ "apiSecret": "your-secret-api-key",
+ "idpConsentUrl": "https://example.com/consent",
+ "idpSecret": "your-idp-secret"
+ }
+ }
+ }
+ }
+ ```
+
+
+
+## Update a tenant
+
+
+
+ ```graphql
+ mutation UpdateTenant($input: UpdateTenantInput!) {
+ updateTenant(input: $input) {
+ tenant {
+ id
+ email
+ apiSecret
+ idpConsentUrl
+ idpSecret
+ publicName
+ }
+ }
+ }
+ ```
+
+
+ ```json
+ {
+ "input": {
+ "id": "123e4567-e89b-12d3-a456-426614174000",
+ "publicName": "New Tenant Name",
+ "email": "new-tenant@example.com",
+ "idpConsentUrl": "https://example.com/new-consent",
+ "idpSecret": "new-idp-secret"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`UpdateTenantInput`](/apis/graphql/backend/#definition-UpdateTenantInput).
+
+
+
+ ```json
+ {
+ "data": {
+ "updateTenant": {
+ "tenant": {
+ "id": "123e4567-e89b-12d3-a456-426614174000",
+ "publicName": "New Tenant Name",
+ "email": "new-tenant@example.com",
+ "apiSecret": "your-secret-api-key",
+ "idpConsentUrl": "https://example.com/new-consent",
+ "idpSecret": "new-idp-secret"
+ }
+ }
+ }
+ }
+ ```
+
+
+
+## Delete a tenant
+
+
+
+ ```graphql
+ mutation DeleteTenant($id: String!) {
+ deleteTenant(id: $id) {
+ success
+ }
+ }
+ ```
+
+
+ ```json
+ {
+ "id": "123e4567-e89b-12d3-a456-426614174000"
+ }
+ ```
+
+
+
+ ```json
+ {
+ "data": {
+ "deleteTenant": {
+ "success": true
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx
index c90b04dc0e..4d272c0904 100644
--- a/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/wallet-addresses.mdx
@@ -6,16 +6,25 @@ tableOfContents:
import { Tabs, TabItem } from '@astrojs/starlight/components'
import { LinkOut } from '@interledger/docs-design-system'
+import TenantIdHmacNote from '/src/partials/tenant-id-hmac-note.mdx'
-Each payment account belonging to your users (for example, your customers) must have at least one associated wallet address for the account to be able to send and receive payments over Interledger and Open Payments. A wallet address serves as a publicly shareable standardized ID for a payment account.
+Each payment account belonging to your users (for example, your customers) must have at least one associated wallet address for the account to be able to send and receive payments over Interledger and Open Payments. A wallet address serves as a publicly shareable standardized ID for a payment account. Each wallet address belongs to a specific tenant.
+
+**Permissions**
+
+- Operators can create wallet addresses for any tenant
+- Tenants can only create wallet addresses for themselves
:::note[Wallet address requirements]
- Your Rafiki instance must be set up with at least one asset before wallet addresses can be created as each wallet address must have an asset assigned to it.
- Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.
+- Operators must configure a wallet address prefix for each tenant. When creating wallet addresses, tenants are restricted to using this prefix.
:::
+Once the wallet address base (`WALLET_ADDRESS_URL`) is set for a tenant, it cannot be changed.
+
## Create wallet addresses
There are a few ways in which you can create wallet addresses.
@@ -26,7 +35,7 @@ There are a few ways in which you can create wallet addresses.
### Create wallet addresses through a script
-Writing your own script that loops through your list of account is one way to batch create wallet addresses for your existing account holders.
+Writing your own script that loops through your list of accounts is one way to batch-create wallet addresses for your existing account holders.
Ensure your script calls the `createWalletAddress` GraphQL mutation.
@@ -48,6 +57,7 @@ Ensure your script calls the `createWalletAddress` GraphQL mutation.
id
scale
}
+ tenantId
}
}
}
@@ -77,6 +87,9 @@ Ensure your script calls the `createWalletAddress` GraphQL mutation.
}
```
For more information about this mutation's input object, see [`CreateWalletAddressInput`](/apis/graphql/backend/#definition-CreateWalletAddressInput).
+
+
+
```json
@@ -95,12 +108,18 @@ Ensure your script calls the `createWalletAddress` GraphQL mutation.
"id": "0ddc0b7d-1822-4213-948e-915dda58850b",
"code": "USD",
"scale": 2
- }
+ },
+ "tenantId": "123e4567-e89b-12d3-a456-426614174000"
}
}
}
}
```
+
+:::note[Tenant ID in the wallet address response]
+The `walletAddress` object in the response will include the `tenantId` of the tenant to which the wallet address belongs. This `tenantId` is used to identify the tenant when processing Open Payments requests.
+:::
+
diff --git a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx
index 09643346fe..97ef716c14 100644
--- a/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx
+++ b/packages/documentation/src/content/docs/integration/requirements/webhook-events.mdx
@@ -7,7 +7,7 @@ tableOfContents:
import { Badge, Tabs, TabItem, Steps } from '@astrojs/starlight/components'
import { Mermaid, LinkOut } from '@interledger/docs-design-system'
-The main communication channel between you and your Rafiki instance is composed of the Backend Admin API and a set of webhook events.
+The main communication channel between you and your Rafiki instance consists of the Backend Admin API and webhook events sent by the `backend` service. By default, events are sent only to the tenant's configured webhook URL. The operator can choose to receive webhook events for all tenants by setting the `SEND_TENANT_WEBHOOKS_TO_OPERATOR` environment variable to `true` on the `backend` service.
Most events require you to interact with Rafiki to provide wallet address information or manage (deposit or withdraw) liquidity. This page describes how you should handle each webhook event.
@@ -19,13 +19,15 @@ Rafiki doesn't hold _user_ account balances. Instead, Rafiki keeps track of the
For Rafiki to notify you about webhook events, you must expose a webhook endpoint that listens for the events dispatched by Rafiki. These events notify your system of time-sensitive status updates, warnings, and errors so that you can react accordingly.
+The webhook URL is configured on a per-tenant basis. The operator can configure a tenant-specific webhook URL using the Backend Admin API. If a tenant-specific webhook URL is not configured, Rafiki will use the default `WEBHOOK_URL` environment variable.
+
When an event occurs, the [`backend`](/integration/deployment/services/backend-service) service makes a request to your configured webhook endpoint. The `backend` service expects a `200` status in return.
-| Variable | Type | Description |
-| ------------- | --------- | ------------------------------------------------------------------- |
-| `WEBHOOK_URL` | `backend` | The endpoint to where requests are made when a webhook event occurs |
+| Variable | Type | Description |
+| ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `WEBHOOK_URL` | `backend` | The default endpoint to where requests are made when a webhook event occurs. This URL is only used if a tenant-specific webhook URL is not configured. |
@@ -44,7 +46,7 @@ Each webhook event is sent as a JSON payload with the following structure in the
:::tip[Duplicate events]
-The `id` in the webhook event payload is unique. Your system can use the ID to determine whether the event has was previously received, preventing duplicate event processing.
+The `id` in the webhook event payload is unique. Your system can use the ID to determine whether the event was previously received, preventing duplicate event processing.
:::
@@ -407,7 +409,7 @@ for \$12 is complete. \$11.50 was sent. You choose to keep \$0.50 as a service f
-The `outgoing.payment_completed` event indicates that as much as possible has been sent to the recipient against their incoming payment.
+The `outgoing_payment.completed` event indicates that as much as possible has been sent to the recipient against their incoming payment.
If there is excess liquidity in Rafiki due to differences between the sent and received amounts, withdraw the excess from the outgoing payment. What you choose to do with the excess is a business decision. One option is to return the excess to the sender. Another option is to retain the excess as a service fee. Lastly, remove the hold on your sender's account and debit their account on your ledger.
diff --git a/packages/documentation/src/content/docs/overview/concepts/multi-tenancy.mdx b/packages/documentation/src/content/docs/overview/concepts/multi-tenancy.mdx
new file mode 100644
index 0000000000..4e3a67db68
--- /dev/null
+++ b/packages/documentation/src/content/docs/overview/concepts/multi-tenancy.mdx
@@ -0,0 +1,46 @@
+---
+title: Multi-tenancy
+---
+
+Multi-tenancy is an architectural approach that enables a single Rafiki instance to service multiple account servicing entities (ASEs). This allows organizations to share application services and database resources while maintaining data isolation and security. By implementing multi-tenancy, Rafiki simplifies the integration process for ASEs, making onboarding faster and easier.
+
+In a multi-tenant environment, the entity responsible for managing a Rafiki instance that serves multiple ASEs is called an **operator**. Each ASE that uses the shared Rafiki instance is called a **tenant**.
+
+## Operator and tenant roles and responsibilities
+
+Both operators and tenants have distinct roles and responsibilities.
+
+### Operator
+
+An operator oversees operational and administrative tasks associated with managing a shared Rafiki instance, including:
+
+- Creating, updating, and deleting tenants
+- Managing assets, liquidity, peering relationships, rate information, and other details, including the ability to manage these on behalf of other tenants
+- Onboarding new tenants
+- Holding Open Payments resources
+- Owning the wallet root domain
+- Configuring global settings for the Rafiki instance
+- Monitoring the health and performance of the Rafiki instance
+- Managing the underlying infrastructure of the Rafiki instance
+- The operator also has the same capabilities as a tenant
+
+Tenants are added through the Backend Admin API or the [Rafiki Admin application](/admin/admin-user-guide/#tenants). Only operators can create new tenants in a Rafiki instance. When adding new tenants, operators should communicate credentials out-of-band with the tenant.
+
+### Tenant
+
+A tenant is an ASE that connects to a shared Rafiki instance rather than running its own environment. To connect to the shared environment, each tenant must install and run their own integration service. Tenants are responsible for the following:
+
+- Creating and managing wallet addresses for their users (for example, their customers)
+- Sending and receiving payments
+- Configuring tenant-specific settings, such as the webhook URL and exchange rates URL
+- Managing their own assets and liquidity
+
+## Benefits of multi-tenancy
+
+- Centralized maintenance lets operators perform updates once for all tenants.
+- Enhanced onboarding allows new ASEs to connect to the shared environment without deploying their own Rafiki instance.
+- Simplified administration provides operators with a quick way to add and remove tenants.
+
+### Key considerations
+
+With centralized maintenance, all changes affect all tenants. There's no way to isolate upgrades or environment maintenance activities. Tenants have limited customization capabilities beyond basic configuration like ILP addresses and identity provider URLs.
diff --git a/packages/documentation/src/content/docs/overview/concepts/payment-pointers.mdx b/packages/documentation/src/content/docs/overview/concepts/payment-pointers.mdx
index ce5f0e22b6..6c96c7858d 100644
--- a/packages/documentation/src/content/docs/overview/concepts/payment-pointers.mdx
+++ b/packages/documentation/src/content/docs/overview/concepts/payment-pointers.mdx
@@ -47,9 +47,11 @@ A valid response means the URL is a wallet address.
"publicName": "Alice",
"assetCode": "USD",
"assetScale": 2,
- "authServer": "https://auth.wallet.example.com",
- "resourceServer": "https://wallet.example.com",
+ "authServer": "https://auth.wallet.example.com/123e4567-e89b-12d3-a456-426614174000",
+ "resourceServer": "https://wallet.example.com/123e4567-e89b-12d3-a456-426614174000",
}
```
+The `authServer` and `resourceServer` URLs include the tenant ID (UUID v4) as a path segment to ensure that Open Payments requests are properly routed to the correct tenant.
+
Rafiki assigns each of your customers' accounts with a wallet address. This wallet address is also a payment pointer because Rafiki supports Open Payments and Interledger. See the integration requirements for [wallet addresses](/integration/requirements/wallet-addresses) for more information.
diff --git a/packages/documentation/src/content/docs/resources/architecture.mdx b/packages/documentation/src/content/docs/resources/architecture.mdx
index f69bc80a95..ddc2364fdd 100644
--- a/packages/documentation/src/content/docs/resources/architecture.mdx
+++ b/packages/documentation/src/content/docs/resources/architecture.mdx
@@ -4,7 +4,7 @@ title: Architecture
import { LinkOut } from '@interledger/docs-design-system'
-Rafiki is a collection of three services that run together. Each one can scale horizontally.
+Rafiki is a collection of three services that run together. They can scale horizontally and are designed to support [multi-tenancy](/overview/concepts/multi-tenancy).
- [Backend](/integration/deployment/services/backend-service) - The main service, responsible for handling business logic and external communication
- [Auth](/integration/deployment/services/auth-service) - A reference implementation of an Open Payments authorization server, used for grant authorization and authentication
@@ -14,7 +14,7 @@ These services rely on a number of databases.
- A Postgres database used by the `auth` service for storing auth-related resources (grants, access tokens, and interactions)
- A Redis database used by the `auth` service to store session data
-- A Postgres database used by the `backend` service for Open Payments resources and application data
+- A Postgres database used by the `backend` service for Open Payments resources, tenant information, and other application data
-
TigerBeetle
@@ -24,3 +24,7 @@ These services rely on a number of databases.
An additional package for [token introspection](/integration/deployment/services/auth-service#token-introspection) is also included with Rafiki. This is an internal package that requires no action on your part if you’re using Rafiki’s `auth` service.
+
+:::note
+This diagram illustrates the core Rafiki architecture. In a multi-tenant deployment, multiple tenants would connect to the same Rafiki instance, with the operator managing resources and configurations for each tenant.
+:::
diff --git a/packages/documentation/src/content/docs/resources/environment-variables.mdx b/packages/documentation/src/content/docs/resources/environment-variables.mdx
index 4a64dd72ca..5819db2a67 100644
--- a/packages/documentation/src/content/docs/resources/environment-variables.mdx
+++ b/packages/documentation/src/content/docs/resources/environment-variables.mdx
@@ -8,7 +8,7 @@ import AuthEnv from '/src/partials/auth-variables.mdx'
import FrontEnv from '/src/partials/frontend-variables.mdx'
import VarWarn from '/src/partials/variables-warning.mdx'
-Environment variables are key value pairs used to configure how your Rafiki instance will run within your infrastructure and integrate with your systems.
+Environment variables are key value pairs used to configure how your Rafiki instance will run within your infrastructure and integrate with your systems. Some environment variables are configured globally for the entire instance, while others can be configured to allow customized settings for each tenant.
Each environment variable name is uppercase, followed by an equal sign and the value of the variable.
diff --git a/packages/documentation/src/content/docs/resources/glossary.mdx b/packages/documentation/src/content/docs/resources/glossary.mdx
index a9e996d43c..23d386a722 100644
--- a/packages/documentation/src/content/docs/resources/glossary.mdx
+++ b/packages/documentation/src/content/docs/resources/glossary.mdx
@@ -58,6 +58,10 @@ A unit of data that carries payment information through the Interledger network.
An API standard and a set of APIs that allows clients to securely retrieve account information and initiate payments from your customers’ accounts with their consent. By adhering to this standard, Rafiki enables integration with external applications and supports the secure and uniform management of payments, quotes, and account data through the Open Payments APIs. For more information, visit the Open Payments documentation.
+## Operator
+
+The account servicing entity (ASE) responsible for managing the Rafiki instance and its resources, including tenants, peering relationships, assets, and liquidity. Operators typically have administrative privileges and can perform actions that tenants cannot, such as creating and deleting tenants.
+
## Outgoing payment
An object created by the sender’s ASE, on their resource server, that represents a payment being sent. This object contains information about the outgoing payment, such as the amount, currency, receiver’s wallet address, and payment status.
@@ -88,6 +92,10 @@ An Interledger application layer protocol for exchanging payment information bet
An Interledger transport layer protocol for sending and receiving authenticated ILP packets between peers and determining the path exchange rate. See the STREAM specification for more information.
+## Tenant
+
+An account servicing entity (ASE) that uses the shared Rafiki instance to manage its payment accounts and interact with the Interledger network. Tenants have their own isolated set of resources and are managed by an operator. Tenants have limited customization capabilities and cannot manage other tenants.
+
## Wallet address
A secure, unique URL that identifies an Open Payments-enabled account. It acts as an entry point to the Open Payments APIs, facilitating interactions like sending and receiving payments. Similar to how an email address serves as a public identifier for an email account, a wallet address is publicly shareable and used to interact with the underlying payment account without compromising its security. Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.
diff --git a/packages/documentation/src/content/docs/v1-beta/404.mdx b/packages/documentation/src/content/docs/v1-beta/404.mdx
new file mode 100644
index 0000000000..32b4468475
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/404.mdx
@@ -0,0 +1,13 @@
+---
+title: 404 - Page Not Found
+description: It seems like the page you're looking for doesn't exist.
+template: splash
+hero:
+ tagline: It seems like the page you're looking for doesn't exist.
+ actions:
+ - text: Go to Rafiki docs
+ link: /v1-beta/overview/overview
+ icon: open-book
+ variant: primary
+slug: v1-beta/404
+---
diff --git a/packages/documentation/src/content/docs/v1-beta/admin/admin-user-guide.mdx b/packages/documentation/src/content/docs/v1-beta/admin/admin-user-guide.mdx
new file mode 100644
index 0000000000..167d53e1e6
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/admin/admin-user-guide.mdx
@@ -0,0 +1,335 @@
+---
+title: Rafiki Admin application user guide
+slug: v1-beta/admin/admin-user-guide
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import { LargeImg } from '@interledger/docs-design-system'
+import KratosWarn from '/src/partials/kratos-warning.mdx'
+
+The Rafiki Admin application provides tools to manage peers, assets, wallet addresses, webhooks, payments, and account settings. It functions as an interface to the Rafiki [backend service](/v1-beta/integration/deployment/services/backend-service/) and all actions performed, such as fetching data or executing commands, are passed to the Rafiki `backend` service. The purpose of this document is to help you navigate and use the Rafiki Admin application effectively.
+
+## Getting started
+
+### Prerequisites
+
+- Familiarity with general Rafiki concepts. The [Rafiki overview](/v1-beta/overview/overview) is a great place to start.
+- Running the Rafiki `frontend` package. See [Frontend service](/v1-beta/integration/deployment/services/frontend-service) for more information.
+
+## Identity and user management
+
+Rafiki Admin relies on the Ory Kratos identity and user management solution to handle authentication (login) and user management (account creation and password recovery).
+
+:::note
+Ory Kratos and Rafiki Admin should be hosted on the same top-level domain. Hosting Kratos on a subdomain is generally not recommended by Ory, but if you choose this approach, ensure you follow the guidelines provided in the Kratos documentation.
+:::
+
+### Login and account management
+
+Access to Rafiki Admin uses an invitation-only system to ensure that only authorized users can register for an account. New users must be invited by an administrator. The registration flow is not public, so users cannot sign up on their own. Instead, administrators create accounts using the `invite-user` script.
+
+#### Invite a user
+
+An administrator (someone with backend interface system access) can run the `invite-user` script in one of two ways: from outside the container on the host machine where Docker is running or directly inside the Rafiki Admin Docker container.
+
+```nginx title="Outside container on host machine"
+docker exec -it npm run invite-user -- example@mail.com
+```
+
+```nginx title="Inside Rafiki Admin Docker container"
+npm run invite-user -- example@mail.com
+```
+
+After running the `invite-user` script, the script generates a recovery link that also serves as an invitation link. This link is output to the terminal, and the administrator can send it to the user. When the user opens the link in their browser, they are automatically logged in and taken to the account settings page where they can set a new password. Afterward, they can log in normally via the Rafiki Admin URL.
+
+
+
+:::note
+The invitation link is single-use for security purposes. Once accessed, it becomes invalid.
+
+If sending the link through Slack, ensure you format it as code by placing it inside backticks (\`) to prevent Slack from automatically previewing the link, which would invalidate it. For example:
+
+{/* prettier-ignore */}
+```js wrap
+`http://localhost:4433/self-service/recovery?flow=116250ee-07bd-4b5c-a98e-87406192bb4b&token=miv0yZ7DFKKw8RyBBQvWoOsTRa2TVuZm`
+```
+
+:::
+
+#### Generate a recovery link
+
+Rafiki Admin provides an automated account recovery flow which requires an SMTP mail server for sending recovery links to users. Alternatively, an administrator can generate a recovery link using the same `invite-user` script.
+
+#### Remove a user
+
+To remove a user, administrators can run the following script in a terminal window:
+
+```nginx
+docker exec -it npm run delete-user -- example@mail.com.
+```
+
+### Why Ory Kratos?
+
+We chose Kratos for its open-source nature, lightweight design, and robust security features. It eliminates the need to manage password hashing, storage, or account recovery flows ourselves, allowing us to focus on what we do best.
+
+Kratos also enhances security with features like built-in breach detection, secure session management, and regular security updates.
+
+Ory Kratos provides frontend components (such as forms and buttons) for identity management flows like login, and account settings. These components are not fixed in design; they are fetched via API calls which allows us to match the identity management components with Rafiki Admin's overall look and feel.
+
+
+
+## Navigation
+
+After logging in, you’ll be greeted by the main landing page with a left-hand navigation menu. This menu provides access to the main functionality needed to manage your Rafiki instance.
+
+
+
+Each of the following menu items leads to a different page of the Rafiki Admin application:
+
+- [Peers](#peers)
+- [Assets](#assets)
+- [Wallet addresses](#wallet-addresses)
+- [Webhooks](#webhooks)
+- [Payments](#payments)
+- [Account settings](#account-settings)
+
+## Peers
+
+The Peers page allows you to manage peering relationships in your Rafiki instance, including viewing, creating, editing, and deleting peers.
+
+On this page, all configured peers appear in a table where you can view the peer name, its ILP address, asset details including the asset type and scale, and the outgoing HTTP endpoint.
+
+
+
+### Create peer
+
+To create a new peer, select **Create Peer** from the main Peers page.
+
+
+
+Fill out the following fields to configure and create your peer:
+
+| Section | Field | Description |
+| ------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------- |
+| General Information | Name | A public name for the peer to identify it on your system. |
+| | Static ILP Address | The peer’s ILP address, obtained from the peer. |
+| | Max Packet Amount | The agreed-upon maximum number of packets a payment is split into. |
+| HTTP Information | Incoming Auth Tokens | A comma-separated list of tokens accepted by your Rafiki instance from a peer for incoming requests. |
+| | Outgoing Auth Token | A single token used by your Rafiki instance for all outgoing requests to authenticate itself with the peer. |
+| | Outgoing Endpoint | The URL of the peer’s server where your Rafiki instance sends outgoing requests. |
+| Asset Information | Asset | The asset used for transactions with this peer. |
+
+After completing these fields, select **Create** to add the new peer.
+
+### Edit peer
+
+To edit an existing peer, select any peer entry from the table on the main Peers page. This opens the Edit Peer page where you can view and change peer settings.
+
+
+
+While the Edit Peer page shares fields with the Create Peer page, it also includes fields and actions specific to managing an existing peer:
+
+| Section | Field/Action | Description |
+| --------------------- | ------------------ | ----------------------------------------------------------------------------------------- |
+| General Information | Peer ID | A unique identifier assigned by Rafiki when the peer was created. This cannot be changed. |
+| Asset Information | View Asset | For more information about an asset, select **View asset**. |
+| Liquidity Information | Amount | Current amount of peer liquidity available. |
+| | Deposit Liquidity | To increase the amount of liquidity available, select **Deposit liquidity**. |
+| | Withdraw Liquidity | To reduce the amount of liquidity available, select **Withdraw liquidity**. |
+
+After editing any of the preceding fields in the General Information or HTTP Information sections, select **Save** to commit those changes.
+
+#### Delete peer
+
+The final section of the Peers page is the irreversible action of deleting a peer. Select **Delete peer** to make this change.
+
+
+
+Confirm the deletion by typing "delete peer" into the text field and selecting **Delete this peer**.
+
+## Assets
+
+The Assets page allows you to manage assets in your Rafiki instance, including viewing, editing, and creating assets.
+
+On this page, all configured assets appear in a table where you can view the asset ID, the asset code, the scale, and the withdrawal threshold.
+
+
+
+### Create asset
+
+To create a new asset, select **Add Asset** from the main Assets page.
+
+
+
+Fill out the following fields to create your new asset:
+
+| Section | Field | Description |
+| ------------------- | -------------------- | --------------------------------------------------------------------------------- |
+| General Information | Code | The asset code, generally an ISO 4217 currency code where available. |
+| | Scale | Difference in order of magnitude between the standard unit and a fractional unit. |
+| | Withdrawal Threshold | The minimum amount of liquidity that can be withdrawn from the asset. |
+
+After completing these fields, select **Create** to add the new asset.
+
+### Edit asset
+
+To edit an existing asset, select any asset entry from the table on the main Assets page. This opens the Edit Asset page where you can view and change asset settings.
+
+
+
+While the Edit Asset page shares fields with the Create Asset page, it also includes fields and actions specific to managing an existing asset:
+
+| Section | Field/Action | Description |
+| --------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| General Information | Asset ID | A unique identifier assigned by Rafiki when the asset was created. This cannot be changed. |
+| Liquidity Information | Amount | Current amount of asset liquidity available. |
+| | Deposit Liquidity | To increase the amount of liquidity available, select **Deposit liquidity**. |
+| | Withdraw Liquidity | To reduce the amount of liquidity available, select **Withdraw liquidity**. |
+| Sending Fee | Fixed Fee | Flat fee per asset, specified in the smallest unit as defined by the asset scale. In our screenshot above, a fixed fee value of 100 with a scale of 2 represents 1 USD. |
+| | Basis Points | A variable fee per asset. One basis point fee is equal to 0.01% of the total amount, 100 basis points = 1%, 10000 basis points = 100% |
+| | Fee history | To view a list of asset fees over time, select **Fee history**. |
+
+After editing any of the preceding fields in the General Information or Sending Fee sections, select **Save** to commit those changes.
+
+## Wallet addresses
+
+The Wallet Addresses page allows you to manage the wallet addresses associated with your Rafiki instance, including viewing, editing, and creating wallet addresses.
+
+On this page, all configured wallet addresses appear in the table where you can view the address URL, the public name, and the wallet status.
+
+
+
+### Create wallet address
+
+To create a new wallet address, select **Create wallet address** from the main Wallet Address page.
+
+
+
+Fill out the following fields to create a new wallet address:
+
+| Section | Field | Description |
+| ------------------- | ------------------- | --------------------------------------------------------------------------- |
+| General Information | Wallet address name | The case-insensitive URL of the wallet. Once set, it cannot be changed. |
+| | Public name | The name associated with the wallet that is visible to anyone with the URL. |
+| | Asset | Select an asset to associate with this wallet. |
+
+:::note[Wallet address requirements]
+
+- At least one asset must be created before creating a new wallet address. Refer to [Create asset](#create-asset) for more information.
+- Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.
+ :::
+
+After completing this section, select **Create** to add the new wallet address.
+
+### Edit wallet address
+
+To edit an existing wallet address, select any wallet address entry from the table on the main Wallet Addresses page. This opens the Edit Wallet Address page where you can view and change wallet address details.
+
+
+
+While the Edit Wallet Address page shares fields with the Create Wallet Address page, it also includes fields and actions specific to managing an existing wallet address.
+
+| Section | Field/Action | Description |
+| --------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
+| General Information | ID | A unique identifier assigned by Rafiki when the wallet was created. This cannot be changed. |
+| | URL | The wallet address. This cannot be changed. |
+| | Status | The current status of the wallet, either active or inactive. |
+| Asset Information | Code | The asset code, generally an ISO 4217 currency code where available. |
+| | Scale | Difference in order of magnitude between the standard unit and a fractional unit. |
+| | Withdrawal threshold | The minimum amount of liquidity that can be withdrawn from the asset. |
+| | View asset | For more information about an asset, select **View asset**. |
+| Liquidity Information | Amount | Current amount of liquidity available for this wallet. |
+| | Withdraw | To withdraw funds from this wallet, select **Withdraw**. |
+
+After editing any of the preceding fields in the General Information section, select **Save** to commit those changes.
+
+:::note[What if I need to edit or delete a wallet address?]
+When managing wallet addresses in Rafiki, there are certain restrictions and limitations to be aware of:
+
+**Editing wallet address URLs**
+
+The URL of an existing wallet address cannot be edited. Changing the URL could disrupt the transaction history associated with that wallet address, potentially leading to inaccuracies in payment records.
+
+**Deleting wallet addresses**
+
+Wallet addresses cannot be deleted from the system. This restriction exists to ensure that any payments tied to a wallet address remain intact and accessible for reporting purposes.
+
+**Solution**
+
+In both cases, the recommended approach is to create a new wallet address and deactivate the old one. To deactivate a wallet address, change the wallet status to Inactive.
+:::
+
+## Webhooks
+
+The Webhook Events page allows you to monitor and manage webhook events within your Rafiki instance. Webhook events in Rafiki are the main communication channel between you and your Rafiki instance. See Webhook events for more information about webhook events.
+
+All triggered webhook events appear in the table. For each webhook event, you can see the webhook ID, the event type, and the date and time of the event. A field at the top of the page allows you to filter the table by event type, making it easier to drill down into specific events.
+
+
+
+To view the webhook event as a JSON representation, select **View data**.
+
+## Payments
+
+The Payments page allows you to view all incoming and outgoing payments in your Rafiki instance. This allows you to monitor payment activity as well as track the status of payments.
+
+All payments appear in the table. For each payment, you can view the unique payment ID, type of payment, state of the payment, and the date and time the payment was created. A field at the top of the page allows you to filter the table by payment type, making it easier to drill down into specific transactions.
+
+
+
+## Account settings
+
+The Account Settings page allows you to manage your personal account information, including updating your email address and password.
+
+
+
+To change your email address, enter the new email address and select **Save**.
+
+To change your password, enter your new password and select **Save**.
diff --git a/packages/documentation/src/content/docs/v1-beta/admin/liquidity/asset-liquidity.mdx b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/asset-liquidity.mdx
new file mode 100644
index 0000000000..6f907824c9
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/asset-liquidity.mdx
@@ -0,0 +1,116 @@
+---
+title: Asset liquidity
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/admin/liquidity/asset-liquidity
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+import IdempotencyNote from '/src/partials/liquidity-idempotency.mdx'
+
+Asset liquidity is the amount of value, denominated in a given asset, that Rafiki has available to handle cross-currency (foreign exchange) transactions between you and your peer. Whenever an outgoing payment/incoming payment is in a different asset than the peering relationship, the liquidity of asset accounts change depending on the FX direction.
+
+You should deposit and withdraw liquidity as necessary, based on your risk tolerance. Rafiki fails any transaction that would cause an asset's liquidity to fall below zero.
+
+For more information about how Rafiki handles liquidity, see the [Accounting](/v1-beta/overview/concepts/accounting) concepts page and the [low asset liquidity](/v1-beta/integration/requirements/webhook-events#low-asset-liquidity) section of the webhook events page.
+
+## Manage asset liquidity using Rafiki Admin
+
+You can deposit and withdraw asset liquidity through the Rafiki Admin application's [Assets](/v1-beta/admin/admin-user-guide/#edit-asset) screen.
+
+## Manage asset liquidity using the Backend Admin API
+
+
+
+### Deposit asset liquidity
+
+
+
+ ```graphql
+ mutation DepositAssetLiquidity($input: DepositAssetLiquidityInput!) {
+ depositAssetLiquidity(input: $input) {
+ assetId
+ amount
+ id
+ idempotencyKey
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "assetId": "7b8b0f65-896d-4403-b7ba-2e24bf20eb35",
+ "amount": "100",
+ "id": "b97fd85a-126e-42ef-b40d-1a50a70ffa6f",
+ "idempotencyKey": "b97fd85a-126e-42ef-b40d-1a50a70ffa6f",
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`DepositAssetLiquidityInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#depositassetliquidityinput).
+
+
+
+
+ When an asset liquidity deposit is successful, `DepositAssetLiquidity` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+
+ ```
+
+
+
+
+### Withdraw asset liquidity
+
+
+
+ ```graphql wrap
+ mutation CreateAssetLiquidityWithdrawal($input: CreateAssetLiquidityWithdrawalInput!) {
+ createAssetLiquidityWithdrawal(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "id": "b97fd85a-126e-42ef-b40d-1a50a70ffa6f",
+ "assetId": "7b8b0f65-896d-4403-b7ba-2e24bf20eb35",
+ "amount": "100",
+ "idempotencyKey": "b97fd85a-126e-42ef-b40d-1a50a70ffa6f",
+ "timeoutSeconds": 0
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateAssetLiquidityWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#createassetliquiditywithdrawalinput).
+
+
+
+
+ When an asset liquidity withdrawal is successful, `CreateAssetLiquidityWithdrawal` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/admin/liquidity/payment-liquidity.mdx b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/payment-liquidity.mdx
new file mode 100644
index 0000000000..a5fcc0bd9f
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/payment-liquidity.mdx
@@ -0,0 +1,154 @@
+---
+title: Payment liquidity
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/admin/liquidity/payment-liquidity
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+import IdempotencyNote from '/src/partials/liquidity-idempotency.mdx'
+
+Payment liquidity represents:
+
+- The value received from a completed incoming payment
+- The value available to send in an outgoing payment
+
+Because Rafiki doesn't hold funds, anything you receive in an incoming payment must be withdrawn and then credited to the recipient's account on your ledger. Listen for the [incoming payments](/v1-beta/integration/requirements/webhook-events#incoming-payments) webhook events to know when you need to interact with Rafiki.
+
+Any excess liquidity that remains after an outgoing payment completes must be withdrawn. You may also find that you must deposit liquidity into Rafiki to fund an outgoing payment. Listen for Rafiki's [outgoing payments](/v1-beta/integration/requirements/webhook-events#outgoing-payments) webhook events to know when action is required on your part.
+
+:::note[Rafiki Admin]
+The Rafiki Admin does not allow you to manage payment liquidity, but you can view details about incoming and outgoing payments through the application's [Payments](/v1-beta/admin/admin-user-guide#payments) screen.
+:::
+
+For more information about how Rafiki handles liquidity, see the [Accounting](/v1-beta/overview/concepts/accounting) concepts page.
+
+## Manage payment liquidity using the Backend Admin API
+
+
+
+### Withdraw incoming payment liquidity
+
+
+
+ ```graphql wrap
+ mutation CreateIncomingPaymentWithdrawal($input: CreateIncomingPaymentWithdrawalInput!) {
+ createIncomingPaymentWithdrawal(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "incomingPaymentId": "b4f85d5c-652d-472d-873c-4ba2a5e39052",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775",
+ "timeoutSeconds": 0
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateIncomingPaymentWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#createincomingpaymentwithdrawalinput).
+
+
+
+
+ When an incoming payment liquidity withdrawal is successful, `CreateIncomingPaymentWithdrawal` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
+
+### Deposit outgoing payment liquidity
+
+
+
+ ```graphql wrap
+ mutation DepositOutgoingPaymentLiquidity($input: DepositOutgoingPaymentLiquidityInput!) {
+ depositOutgoingPaymentLiquidity(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "outgoingPaymentId": "b4f85d5c-652d-472d-873c-4ba2a5e39052",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`DepositOutgoingPaymentLiquidityInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#depositoutgoingpaymentliquidityinput).
+
+
+
+
+ When an outgoing payment liquidity deposit is successful, `DepositOutgoingPaymentLiquidity` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
+
+### Withdraw outgoing payment liquidity
+
+
+
+ ```graphql wrap
+ mutation CreateOutgoingPaymentWithdrawal($input: CreateOutgoingPaymentWithdrawalInput!) {
+ createOutgoingPaymentWithdrawal(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "outgoingPaymentId": "b4f85d5c-652d-472d-873c-4ba2a5e39052",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775",
+ "timeoutSeconds": 0
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateOutgoingPaymentWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#createoutgoingpaymentwithdrawalinput).
+
+
+
+
+ When an outgoing payment liquidity withdrawal is successful, `CreateOutgoingPaymentWithdrawal` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/admin/liquidity/peer-liquidity.mdx b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/peer-liquidity.mdx
new file mode 100644
index 0000000000..d76a4db244
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/peer-liquidity.mdx
@@ -0,0 +1,112 @@
+---
+title: Peer liquidity
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/admin/liquidity/peer-liquidity
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import IdempotencyNote from '/src/partials/liquidity-idempotency.mdx'
+
+Peer liquidity is the line of credit you extend to a peer, denominated in your agreed upon asset. A peer's liquidity account balance represents the amount of credit the peer still has available to them.
+
+A peer's liquidity increases when payments are made to the peer and decreases when payments are made from the peer. For example, if a customer of your peer sends your customer a payment of $20 USD, then your peer's liquidity account decreases by 20.
+
+If a peer’s liquidity is insufficient (for example, they’ve used up their allotted credit line), transactions initiated from the peer will fail. Once a peer's liquidity is used up, you should settle with your peer and reset their liquidity. Deposit and withdraw peer liquidity as necessary, based on your risk profile.
+
+For more information about how Rafiki handles liquidity, see the [Accounting](/v1-beta/overview/concepts/accounting) concepts page and the [low peer liquidity](/v1-beta/integration/requirements/webhook-events#low-peer-liquidity) section of the webhook events page.
+
+## Manage peer liquidity using Rafiki Admin
+
+You can deposit and withdraw peer liquidity through the Rafiki Admin application's [Peers](/v1-beta/admin/admin-user-guide/#edit-peer) screen.
+
+## Manage peer liquidity using the Backend Admin API
+
+
+
+### Deposit peer liquidity
+
+
+
+ ```graphql wrap
+ mutation DepositPeerLiquidity($input: DepositPeerLiquidityInput!) {
+ depositPeerLiquidity(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "id": "a09b730d-8610-4fda-98fa-ec7acb19c775",
+ "peerId": "73158598-2e0c-4973-895e-aebd115af260",
+ "amount": "1000000",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`DepositPeerLiquidityInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#depositpeerliquidityinput).
+
+
+
+
+ When a peer liquidity deposit is successful, `DepositPeerLiquidity` returns `true`.
+
+ ```json
+ {
+ "data": {
+ "success": true
+ }
+ }
+ ```
+
+
+
+
+### Withdraw peer liquidity
+
+
+
+ ```graphql wrap
+ mutation CreatePeerLiquidityWithdrawal($input: CreatePeerLiquidityWithdrawalInput!) {
+ createPeerLiquidityWithdrawal(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "id": "421fae87-9a59-4217-9ff8-faf55ffab9c6",
+ "peerId": "73158598-2e0c-4973-895e-aebd115af260",
+ "amount": "100",
+ "idempotencyKey": "b97fd85a-126e-42ef-b40d-1a50a70ffa6f",
+ "timeoutSeconds": 0
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreatePeerLiquidityWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#createpeerliquiditywithdrawalinput).
+
+
+
+
+ When a peer liquidity withdrawal is successful, `CreatePeerLiquidityWithdrawal` returns `true`.
+
+ ```json
+ {
+ "data": {
+ "success": true
+ }
+ }
+ ```
+
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/admin/liquidity/two-phase-transfers.mdx b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/two-phase-transfers.mdx
new file mode 100644
index 0000000000..0798ecc9c1
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/admin/liquidity/two-phase-transfers.mdx
@@ -0,0 +1,112 @@
+---
+title: Two-phase transfers
+slug: v1-beta/admin/liquidity/two-phase-transfers
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import IdempotencyNote from '/src/partials/liquidity-idempotency.mdx'
+
+Rafiki allows for two-phase transfers, which moves funds in two stages.
+
+1. Reserve funds (`pending`)
+2. Resolve funds (`post`, `void`, or `expire`)
+
+The following transactions support two-phase transfers:
+
+- Asset liquidity withdrawal
+- Peer liquidity withdrawal
+- Incoming payment withdrawal
+- Outgoing payment withdrawal
+- Wallet address withdrawal
+
+When a withdraw-liquidity transaction is requested with a timeout greater than `0`, the transaction processes as a two-phase transfer. A `0` denotes the absence of a timeout.
+
+If the timeout interval passes before the transaction posts or is voided, the transaction expires and the full amount is returned to the original account.
+
+## Manage two-phase transfers using the Backend Admin API
+
+
+
+### Post and commit a successful transfer
+
+
+
+ ```graphql wrap
+ mutation PostLiquidityWithdrawal($input: PostLiquidityWithdrawalInput!) {
+ postLiquidityWithdrawal(input: $input) {
+ error
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "withdrawalId": "b4f85d5c-652d-472d-873c-4ba2a5e39052",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`PostLiquidityWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#postliquiditywithdrawalinput).
+
+
+
+
+ When a liquidity withdrawal post is successful, `PostLiquidityWithdrawal` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
+
+### Void and roll-back an unsuccessful transfer
+
+
+
+ ```graphql wrap
+ mutation VoidLiquidityWithdrawal($input: VoidLiquidityWithdrawalInput!) {
+ voidLiquidityWithdrawal(input: $input) {
+ error
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "withdrawalId": "b4f85d5c-652d-472d-873c-4ba2a5e39052",
+ "idempotencyKey": "a09b730d-8610-4fda-98fa-ec7acb19c775"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`VoidLiquidityWithdrawalInput`](https://rafiki.dev/apis/graphql/backend/inputobjects/#voidliquiditywithdrawalinput).
+
+
+
+
+ When a liquidity withdrawal is successfully voided and rolled back, `VoidLiquidityWithdrawal` returns `true`.
+
+ ```json
+ {
+ data: {
+ success: true
+ }
+ }
+ ```
+
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/apis/graphql/admin-api-overview.mdx b/packages/documentation/src/content/docs/v1-beta/apis/graphql/admin-api-overview.mdx
new file mode 100644
index 0000000000..fb9c40ad4f
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/apis/graphql/admin-api-overview.mdx
@@ -0,0 +1,28 @@
+---
+title: Overview
+slug: v1-beta/apis/graphql/admin-api-overview
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Rafiki provides two GraphQL APIs, described below. As described on GraphQL.org, GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. GraphQL APIs are organized in terms of types and fields, not endpoints.
+
+## Backend Admin API
+
+The Backend Admin API provides you with comprehensive capabilities to manage your Rafiki instance. Core functionality includes managing peering relationships, assets, wallet addresses and their public keys, as well as liquidity management through deposits and withdrawals. Another important aspect of the Backend Admin API is to manage Open Payments resources like payments and quotes.
+
+This API is complemented by the Rafiki Admin application, a frontend interface for Rafiki management that directly interacts with the Backend Admin API. Check out the [Rafiki Admin Application User Guide](/v1-beta/admin/admin-user-guide) for more information.
+
+## Auth Admin API
+
+The Auth Admin API allows you to get information about a grant, such as its status, state, related payment details, and the wallet address of the grantee’s account. The API also allows you to revoke grants.
+
+## Idempotency
+
+Wikipedia describes idempotence as being the property of “certain operations in…computer science whereby \[the operations] can be applied multiple times without changing the result beyond the initial application.” “An operation can be repeated or retried as often as necessary without causing unintended effects. With non-idempotent operations, the algorithm may have to keep track of whether the operation was already performed.”
+
+Several mutations in the Admin APIs utilize an idempotency key to allow for safely retrying requests without performing operations multiple times. The key should be unique, typically a V4 UUID.
+
+For the Admin APIs, whenever a mutation with an `idempotencyKey` is called, the request payload and the request response are saved under that key. Any subsequent requests made with the same idempotency key will return the original response and status of the request, regardless of whether the request was successful. Keys are cached for a default of 24 hours. The default can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS backend` environment flag.
+
+Additionally, in the chance that a request is made while still concurrently processing the first request under the same `idempotencyKey`, the APIs will return an error. This provides further safeguards from potential errors in the system. The timing to prevent processing concurrent requests is `2` seconds by default. The default can be changed via the `backend` service’s `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` environment flag.
diff --git a/packages/documentation/src/content/docs/v1-beta/i18n/.keepme b/packages/documentation/src/content/docs/v1-beta/i18n/.keepme
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/documentation/src/content/docs/v1-beta/index.mdx b/packages/documentation/src/content/docs/v1-beta/index.mdx
new file mode 100644
index 0000000000..54c5a9b46e
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/index.mdx
@@ -0,0 +1,46 @@
+---
+title: Hello from Rafiki
+description: Rafiki is open source software that provides an efficient solution
+ for an Account Servicing Entity to enable Interledger functionality on its
+ users' accounts.
+template: splash
+hero:
+ tagline:
+ Rafiki is open source software that provides an efficient solution for
+ an account servicing entity (ASE) to enable Interledger functionality on its
+ users' accounts.
+ actions:
+ - text: Read Rafiki docs
+ link: /v1-beta/overview/overview
+ icon: open-book
+ variant: primary
+ attrs:
+ data-umami-event: Landing page - Rafiki docs
+slug: v1-beta
+---
+
+import { Card, CardGrid, LinkCard } from '@astrojs/starlight/components'
+
+
+
+
+ Test Rafiki by running two mock ASEs that automatically peer with one
+ another.
+
+
+
+
+ Review the requirements for deploying Rafiki to a production environment.
+
+
+
+
+ Discover what's in our Backend GraphQL schema.
+
+
+
+
+ Discover what's in our Auth GraphQL schema.
+
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/deployment/docker-compose.mdx b/packages/documentation/src/content/docs/v1-beta/integration/deployment/docker-compose.mdx
new file mode 100644
index 0000000000..2346b83f5a
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/deployment/docker-compose.mdx
@@ -0,0 +1,850 @@
+---
+title: Docker Compose
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/deployment/docker-compose
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import KratosWarn from '/src/partials/kratos-warning.mdx'
+
+This guide is an example of deploying Rafiki using Docker Compose with Nginx as a reverse proxy on a virtual machine (VM) in a cloud environment. This guide also uses Certbot to generate Let’s Encrypt TLS certificates to secure exposed ports using HTTPS.
+
+From Docker's documentation, Docker Compose is a tool for defining and running multi-container applications using a single YAML file. It simplifies the process of configuring and running multiple services.
+
+:::note
+While Docker Compose could serve as a production deployment, it is an environment best suited for development and testing. For more information, refer to Docker's documentation on common use cases.
+:::
+
+## Prerequisites
+
+### Deploy VM and install Docker
+
+:::caution[Recommended software version]
+We recommended using the latest vendor supported version for each of the software dependencies listed in this section.
+:::
+
+Deploy a general purpose VM with the following minimum specifications:
+
+- OS: Linux distro
+- RAM: 4 GB
+- vCPUs: 2
+
+Install the following software on the VM:
+
+-
+ Docker Engine
+
+-
+ Docker Compose
+
+
+### Install Nginx and Certbot
+
+Once you have provisioned the VM in your cloud environment, install Nginx along with Certbot:
+
+```sh
+sudo apt update && sudo apt install nginx certbot python3-certbot-nginx
+```
+
+### Domain preparation
+
+Generate the Let’s Encrypt certificates using Certbot:
+
+```sh wrap
+certbot certonly --manual --preferred-challenges=dns --email EMAIL --server https://acme-v02.api.letsencrypt.org/directory --agree-tos -d DOMAIN
+```
+
+:::caution[Update TXT record]
+Domain can be in wildcard format. You will also need to update the TXT record in this step.
+:::
+
+As Let's Encrypt certificates are valid for 90 days, you must set up a cron process to renew the certificate on a regular schedule:
+
+```sh
+crontab -e
+
+0 3 * * * certbot renew
+```
+
+### Domain and DNS configuration
+
+Map the [Open Payments resource server](/v1-beta/integration/deployment/services/backend-service#open-payments) to your domain, and the [ILP connector](/v1-beta/integration/deployment/services/backend-service#interledger-connector), [Open Payments auth server](/v1-beta/integration/deployment/services/auth-service), and [Admin UI](/v1-beta/integration/deployment/services/frontend-service) to subdomains. Using the DNS host of your choice, set up your domain and subdomains according to the following recommended convention:
+
+
+
+| Service | Exposes | URL | Example |
+| ----------------------------- | ----------------------------------------------------------------- | ------------ | ------------------ |
+| Open Payments resource server | Open Payments APIs | DOMAIN | myrafiki.com |
+| ILP connector | ILP connector to send and receive ILP packets between peers | ilp.DOMAIN | ilp.myrafiki.com |
+| Open Payments auth server | Reference implementation of an Open Payments authorization server | auth.DOMAIN | auth.myrafiki.com |
+| Admin UI | Admin UI to manage Rafiki | admin.DOMAIN | admin.myrafiki.com |
+
+
+
+:::note
+The example domain and subdomain values are for demonstration purposes only. You must use the actual domain names that you set up with your DNS host.
+:::
+
+Next, update the DNS records (A records) to point to the static external IP address of the virtual machine according to the table above.
+
+## Configure Compose file
+
+The Docker Compose file is a YAML configuration file used to define the services, networks, and volumes that make up a multi-container application. In this section, we'll explore the Compose file by breaking it down into the individual Rafiki services.
+
+:::note[Update Compose file]
+Before using the Compose file, you must update the variables with values relevant to your environment. Specifically, change the values enclosed within the brackets and substitute `newest-version` with the latest Rafiki version.
+:::
+
+### Docker Compose example
+
+While the actual Compose file is a single YAML file containing all of the services, this page will guide you through each service one by one. For each service, we'll look at the relevant configuration details along with the corresponding environment variables.
+
+#### Auth service
+
+The Rafiki `auth` service is responsible for handling authentication and authorization for your application. It connects to a Postgres database to store auth-related resources and a Redis database for storing session data. See [Auth service](/v1-beta/integration/deployment/services/auth-service/) for more information.
+
+Ports exposed:
+
+- 3003 (`ADMIN_PORT`) is used for the Auth Admin API
+- 3006 (`AUTH_PORT`) is used for the Open Payments authorization server
+
+Make sure to configure the `AUTH_DATABASE_URL` and `REDIS_URL` environment variables to point to your database instances.
+
+:::caution[Running Rafiki behind a proxy]
+If you plan to run your Rafiki instance behind a proxy, you must set the `TRUST_PROXY` variable to `true`
+:::
+
+```sh
+rafiki-auth:
+ image: ghcr.io/interledger/rafiki-auth:
+ container_name: rafiki-auth
+ environment:
+ AUTH_DATABASE_URL: {postgresql://postgres:password@localhost:5432/auth_development}
+ AUTH_SERVER_URL: {https://auth.myrafiki.com}
+ ADMIN_PORT: 3003
+ AUTH_PORT: 3006
+ INTROSPECTION_PORT: 3007
+ INTERACTION_PORT: 3009
+ COOKIE_KEY: {...}
+ IDENTITY_SERVER_SECRET: {...}
+ IDENTITY_SERVER_URL: {https://idp.mysystem.com}
+ REDIS_URL: {redis://127.0.0.1:6379}
+ TRUST_PROXY: true
+ depends_on:
+ - postgres
+ networks:
+ - rafiki
+ ports:
+ - '3003:3003'
+ - '3006:3006'
+ - '3007:3007'
+ - '3009:3009'
+ restart: always
+```
+
+##### Environment variables
+
+
+
+| Variable | Required | Description |
+| --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `AUTH_DATABASE_URL` | Y | The URL of the Postgres database storing your Open Payments grant data. |
+| `AUTH_SERVER_URL` | Y | The public endpoint for your Rafiki instance’s public Open Payments routes. |
+| `COOKIE_KEY` | Y | The koa KeyGrip key that is used to sign cookies for an interaction session. |
+| `IDENTITY_SERVER_SECRET` | Y | A shared secret between the authorization server and the IdP server; the authorization server will use the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](/integration/requirements/open-payments/idp#x-idp-secret-header) header. |
+| `IDENTITY_SERVER_URL` | Y | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
+| `REDIS_URL` | Y | The connection URL for Redis. |
+| `ACCESS_TOKEN_DELETION_DAYS` | N | The days until expired and/or revoked access tokens are deleted. |
+| `ACCESS_TOKEN_EXPIRY_SECONDS` | N | The expiry time, in seconds, for access tokens. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | N | The TTL, in seconds, for which a request’s signature will be valid. |
+| `ADMIN_API_SIGNATURE_VERSION` | N | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_PORT` | N | The port of your Rafiki Auth Admin API server. |
+| `AUTH_PORT` | N | The port of your Open Payments authorization server. |
+| `INCOMING_PAYMENT_INTERACTION` | N | When `true`, incoming Open Payments grant requests are interactive. |
+| `INCOMING_PAYMENT_WORKERS` | N | The number of workers processing incoming payment requests. |
+| `INTERACTION_EXPIRY_SECONDS` | N | The time, in seconds, for which a user can interact with a grant request before the request expires. |
+| `INTERACTION_PORT` | N | The port number of your Open Payments interaction-related APIs. |
+| `INTROSPECTION_PORT` | N | The port of your Open Payments access token introspection server. |
+| `LIST_ALL_ACCESS_INTERACTION` | N | When `true`, grant requests that include a `list-all` action will require interaction. In these requests, the client asks to list resources that it did not create. |
+| `LOG_LEVEL` | N | Pino log level. |
+| `NODE_ENV` | N | The type of node environment: `development`, `test`, or `production`. |
+| `QUOTE_INTERACTION` | N | When `true`, quote grants are interactive. |
+| `REDIS_TLS_CA_FILE_PATH` | N | Redis TLS config. |
+| `REDIS_TLS_CERT_FILE_PATH` | N | Redis TLS config. |
+| `REDIS_TLS_KEY_FILE_PATH` | N | Redis TLS config. |
+| `TRUST_PROXY` | N | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+| `WAIT_SECONDS` | N | The wait time, in seconds, included in a grant request response (`grant.continue`). |
+
+
+
+#### Backend service
+
+The Rafiki `backend` service handles business logic and external communication. It exposes the Open Payments APIs and an Interledger connector for sending and receiving packets. It connects to a Redis database for caching, a Postgres database for Open Payments resources, and TigerBeetle for accounting liquidity. See [Backend service](/v1-beta/integration/deployment/services/backend-service) for more information.
+
+:::note[TigerBeetle or Postgres for accounting database]
+TigerBeetle is recommended, but if you would rather use Postgres as an accounting database make sure to set `USE_TIGERBEETLE` to false.
+:::
+
+Ports exposed:
+
+- 3000 (`OPEN_PAYMENTS_PORT`) is used for the Open Payments resource server
+- 3001 (`ADMIN_PORT`) is used for the Backend Admin API
+- 3002 (`CONNECTOR_PORT`) is used for the ILP connector to send and receive ILP packets
+
+Make sure to configure the `DATABASE_URL` and `REDIS_URL` environment variables to point to your database instances.
+
+```sh
+rafiki-backend:
+ image: ghcr.io/interledger/rafiki-backend:
+ container_name: rafiki-backend
+ depends_on:
+ - postgres
+ - redis
+ environment:
+ AUTH_SERVER_GRANT_URL: {https://auth.myrafiki.com}
+ AUTH_SERVER_INTROSPECTION_URL: {https://auth.myrafiki.com/3007}
+ DATABASE_URL: {postgresql://postgres:password@localhost:5432/development}
+ ILP_ADDRESS: {test.myrafiki}
+ ADMIN_PORT: 3001
+ CONNECTOR_PORT: 3002
+ OPEN_PAYMENTS_PORT: 3000
+ OPEN_PAYMENTS_URL: {https://myrafiki.com}
+ REDIS_URL: {redis://127.0.0.1:6379}
+ WALLET_ADDRESS_URL: {https://myrafiki.com/rafiki-instance}
+ WEBHOOK_URL: {https://mysystem.com/webhooks}
+ EXCHANGE_RATES_URL: {https://mysystem.com/rates}
+ ILP_CONNECTOR_URL: {https://ilp.myrafiki.com}
+ INSTANCE_NAME: {'My ASE name'}
+ TRUST_PROXY: true
+ KEY_ID: ...
+ USE_TIGERBEETLE: true
+ TIGERBEETLE_CLUSTER_ID: 0
+ TIGERBEETLE_REPLICA_ADDRESSES: 10.5.0.50:4342
+ networks:
+ - rafiki
+ ports:
+ - '3000:3000'
+ - '3001:3001'
+ - '3002:3002'
+ privileged: true
+ restart: always
+ volumes:
+ - ../temp/:/workspace/temp/
+```
+
+##### Environment variables
+
+
+
+| Variable | Required | Description |
+| ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `AUTH_SERVER_GRANT_URL` | Y | The endpoint on your Open Payments authorization server to grant a request. |
+| `AUTH_SERVER_INTROSPECTION_URL` | Y | The endpoint on your Open Payments authorization server to introspect an access token. |
+| `DATABASE_URL` | Y | The Postgres database URL of the database storing your resource data. |
+| `EXCHANGE_RATES_URL` | Y | The endpoint your Rafiki instance uses to request exchange rates. |
+| `ILP_ADDRESS` | Y | The ILP address of your Rafiki instance. |
+| `ILP_CONNECTOR_URL` | Y | The ILP connector address where ILP packets are received. |
+| `KEY_ID` | Y | Your Rafiki instance’s client key ID. |
+| `OPEN_PAYMENTS_URL` | Y | The public endpoint of your Open Payments resource server. |
+| `REDIS_URL` | Y | The Redis URL of the database handling ILP packet data. |
+| `USE_TIGERBEETLE` | Y | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
+| `WEBHOOK_URL` | Y | Your endpoint that consumes webhook events. |
+| `ADMIN_PORT` | N | The port of your Backend Auth API server. |
+| `API_SECRET` | N | N/A |
+| `API_SIGNATURE_VERSION` | N | The version of the request signing algorithm used to generate signatures. |
+| `AUTO_PEERING_SERVER_PORT` | N | If auto-peering is enabled, the server will use this port. |
+| `CONNECTOR_PORT` | N | The port of the ILP connector for sending packets via ILP over HTTP. |
+| `ENABLE_AUTO_PEERING` | N | When `true`, auto-peering is enabled. |
+| `ENABLE_MANUAL_MIGRATIONS` | N | When `true`, you must run the database manually with the command `npm run knex – migrate:latest –env production` |
+| `ENABLE_SPSP_PAYMENT_POINTERS` | N | When `true`, the SPSP route is enabled. |
+| `ENABLE_TELEMETRY` | N | Enables the telemetry service on Rafiki. |
+| `ENABLE_TELEMETRY_TRACES` | N | N/A |
+| `EXCHANGE_RATES_LIFETIME` | N | The time, in milliseconds, the exchange rates you provide via the `EXCHANGE_RATES_URL` are valid. |
+| `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` | N | The TTL, in milliseconds, for `idempotencyKey` concurrency lock on GraphQL mutations on the Backend Admin API. |
+| `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS` | N | The TTL, in milliseconds, for `idempotencyKey` on GraphQL mutations on the Backend Admin API. |
+| `INCOMING_PAYMENT_CREATED_POLL_FREQUENCY_MS` | N | N/A |
+| `INCOMING_PAYMENT_CREATED_POLL_TIMEOUT_MS` | N | N/A |
+| `INCOMING_PAYMENT_EXPIRY_MAX_MS` | N | The maximum into the future, in milliseconds, incoming payments expiry can be set to on creation. |
+| `INCOMING_PAYMENT_WORKER_IDLE` | N | The time, in milliseconds, that `INCOMING_PAYMENT_WORKERS` will wait until checking an empty incoming payment request queue again. |
+| `INCOMING_PAYMENT_WORKERS` | N | The number of workers processing incoming payment requests. |
+| `INSTANCE_NAME` | N | Your Rafiki instance's name used to communicate for auto-peering and/or [telemetry](/overview/concepts/telemetry). Required when auto-peering and/or telemetry is enabled. |
+| `LOG_LEVEL` | N | Pino log level |
+| `MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS` | N | Specifies how many times an outgoing payment is retried before failing completely. |
+| `NODE_ENVIRONMENT` | N | The type of node environment: `development`, `test`, or `production`. |
+| `OPEN_PAYMENTS_PORT` | N | The port of your Open Payments resource server. |
+| `OPEN_TELEMETRY_COLLECTOR_URLS` | N | N/A |
+| `OPEN_TELEMETRY_EXPORT_INTERVAL` | N | N/A |
+| `OPEN_TELEMETRY_TRACE_COLLECTOR_URLS` | N | N/A |
+| `OUTGOING_PAYMENT_WORKER_IDLE` | N | The time, in milliseconds, that `OUTGOING_PAYMENT_WORKERS` wait until they check an empty outgoing payment request queue again. |
+| `OUTGOING_PAYMENT_WORKERS` | N | The number of workers processing outgoing payment requests. |
+| `POLL_INCOMING_PAYMENT_CREATED_WEBHOOK` | N | N/A |
+| `PRIVATE_KEY_FILE` | N | The path to your Rafiki instance’s client private key. |
+| `QUOTE_LIFESPAN` | N | The time, in milliseconds, an Open Payments quote is valid for. |
+| `REDIS_TLS_CA_FILE_PATH` | N | Redis TLS config |
+| `REDIS_TLS_CERT_FILE_PATH` | N | Redis TLS config |
+| `REDIS_TLS_KEY_FILE_PATH` | N | Redis TLS config |
+| `SIGNATURE_SECRET` | N | The secret to generate request header signatures for webhook event requests. |
+| `SIGNATURE_VERSION` | N | The version number to generate request header signatures for webhook events. |
+| `SLIPPAGE` | N | The accepted ILP rate fluctuation. |
+| `STREAM_SECRET` | N | The seed secret to generate shared STREAM secrets. |
+| `TELEMETRY_EXCHANGE_RATES_LIFETIME` | N | N/A |
+| `TELEMETRY_EXCHANGE_RATES_URL` | N | The endpoint Rafiki will query for exchange rates. Used as a fallback if/when [exchange rates](/integration/requirements/exchange-rates) aren’t provided. |
+| `TIGERBEETLE_CLUSTER_ID` | N | The TigerBeetle cluster ID picked by the system that starts the TigerBeetle cluster to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES` | N | TigerBeetle replica addresses for all replicas in the cluster. The addresses are comma-separated IP addresses/ports, to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES.SPLIT` | N | N/A |
+| `TIGERBEETLE_TWO_PHASE_TIMEOUT_SECONDS` | N | N/A |
+| `TRUST_PROXY` | N | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+| `WALLET_ADDRESS_DEACTIVATION_PAYMENT_GRACE_PERIOD_MS` | N | The time into the future, in milliseconds, to set expiration of Open Payments incoming payments when deactivating a wallet address. |
+| `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | N | The time, in milliseconds, you have to create a missing wallet address before timeout. |
+| `WALLET_ADDRESS_POLLING_FREQUENCY_MS` | N | The frequency of polling while waiting for you to create a missing wallet address. |
+| `WALLET_ADDRESS_URL` | N | Your Rafiki instance’s internal wallet address. |
+| `WALLET_ADDRESS_WORKER_IDLE` | N | The time, in milliseconds, that `WALLET_ADDRESS_WORKERS` wait until checking the empty wallet address request queue again. |
+| `WALLET_ADDRESS_WORKERS` | N | The number of workers processing wallet address requests. |
+| `WEBHOOK_MAX_RETRY` | N | The maximum number of times your Rafiki instance’s backend retries sending a certain webhook event to your configured `WEBHOOK_URL`. |
+| `WEBHOOK_TIMEOUT` | N | The time, in milliseconds, that your Rafiki instance will wait for a `200` response from your webhook endpoint. If a `200` response is not received, Rafiki will time out and try to send the webhook event again. |
+| `WEBHOOK_WORKER_IDLE` | N | The time, in milliseconds, that `WEBHOOK_WORKERS` will wait until they check the empty webhook event queue again. |
+| `WEBHOOK_WORKERS` | N | The number of workers processing webhook events. |
+| `WITHDRAWAL_THROTTLE_DELAY` | N | The delay in liquidity withdrawal processing. |
+
+
+
+#### Frontend service
+
+The Rafiki `frontend` service provides an internal admin interface for managing your Rafiki instance. It communicates with the Backend Admin API to facilitate administrative tasks. See [Frontend service](/v1-beta/integration/deployment/services/frontend-service) for more information.
+
+Ports exposed:
+
+- 3005 (`PORT`) is used to host the Rafiki Admin app
+
+Make sure to configure the `GRAPHQL_URL` and `OPEN_PAYMENTS_URL` environment variables to point to the appropriate endpoints.
+
+```sh
+rafiki-frontend:
+ image: ghcr.io/interledger/rafiki-frontend:
+ container_name: rafiki-frontend
+ depends_on:
+ - rafiki-backend
+ environment:
+ PORT: 3005
+ GRAPHQL_URL: {https://myrafiki.com:3001}
+ OPEN_PAYMENTS_URL: {https://myrafiki.com}
+ KRATOS_CONTAINER_PUBLIC_URL: {http://kratos:4433}
+ KRATOS_BROWSER_PUBLIC_URL: {https://admin.myrafiki.com/kratos}
+ KRATOS_ADMIN_URL: {http://kratos:4434/admin}
+ networks:
+ - rafiki
+ restart: always
+ privileged: true
+ ports:
+ - '3005:3005'
+```
+
+##### Environment variables
+
+
+
+| Variable | Required | Description |
+| -------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `GRAPHQL_URL` | Y | URL for Rafiki’s GraphQL Auth Admin API. |
+| `OPEN_PAYMENTS_URL` | Y | Your Open Payments API endpoint. |
+| `PORT` | Y | Port from which to host the Rafiki Remix app. |
+| `AUTH_ENABLED` | N | When `true`, only authenticated users can be granted access to Rafiki Admin by an administrator. |
+| `ENABLE_INSECURE_MESSAGE_COOKIE` | N | When set to `true`, `t`, or `1`, cookie will be transmitted over insecure HTTP connection. Insecure message cookies are required for flash messages to work over HTTP. |
+| `KRATOS_ADMIN_URL` | N | The admin endpoint/container address for Kratos. |
+| `KRATOS_BROWSER_PUBLIC_URL` | N | The URL for you to access the Kratos Docker container from a browser outside of the Docker network. This is used for calls from a browser (what you see in the Rafiki Admin UI) to the Kratos server on the backend. |
+| `KRATOS_CONTAINER_PUBLIC_URL` | N | The URL for you to access the Kratos Docker container from within the Docker network. This is used for backend calls to Kratos. |
+| `LOG_LEVEL` | N | Pino log level. |
+| `NODE_ENV` | N | The type of node environment: `development`, `test`, or `production`. |
+| `SIGNATURE_SECRET` | N | The signature secret used to authenticate requests to the Backend Admin API. |
+| `SIGNATURE_VERSION` | N | The signature version number used to authenticate requests to the Backend Admin API. |
+
+
+
+#### TigerBeetle
+
+TigerBeetle is a high-performance database designed to handle double-entry/double-ledger accounting. It is recommended for managing liquidity and settlement accounts due to its speed and efficiency. See [Accounting](/v1-beta/overview/concepts/accounting/#tigerbeetle) for more information.
+
+To use TigerBeetle, make sure that `USE_TIGERBEETLE` is set to true in the backend service environment variables.
+
+```sh
+tigerbeetle:
+ image: ghcr.io/tigerbeetle/tigerbeetle:0.16.29
+ privileged: true
+ volumes:
+ - tigerbeetle-data:/var/lib/tigerbeetle
+ networks:
+ rafiki:
+ ipv4_address: 10.5.0.50
+ entrypoint:
+ - /bin/sh
+ - -c
+ - |
+ set -ex
+ DATA_FILE=/var/lib/tigerbeetle/cluster_0_replica_0.tigerbeetle
+ set +e
+ ls $$DATA_FILE
+ DATA_FILE_EXISTS="$$?"
+ set -e
+ echo $$DATA_FILE_EXISTS
+ if [ "$$DATA_FILE_EXISTS" != 0 ]; then
+ ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 $$DATA_FILE;
+ fi
+ hostname -i
+ ls /var/lib/tigerbeetle
+ ./tigerbeetle start --addresses=0.0.0.0:4342 $$DATA_FILE
+```
+
+#### Postgres
+
+The Postgres service is a relational database management system used to store and manage application data. Both the `auth` and `backend` services rely on Postgres databases.
+
+```sh
+postgres:
+ image: 'postgres:16'
+ container_name: postgres
+ environment:
+ POSTGRES_USER: ...
+ POSTGRES_PASSWORD: ...
+ networks:
+ - rafiki
+ restart: unless-stopped
+ volumes:
+ - pg-data:/var/lib/postgresql/data
+ - ../dbinit.sql:/docker-entrypoint-initdb.d/init.sql
+```
+
+#### Redis
+
+The Redis service is used for caching and session management across the application. Both the `auth` and `backend` services rely on Redis databases.
+
+```sh
+redis:
+ image: 'redis:7'
+ restart: unless-stopped
+ networks:
+ - rafiki
+```
+
+#### Kratos
+
+The Kratos service is an identity and user management solution used by Rafiki's `frontend` service for handling authentication and user management tasks.
+
+
+
+```sh
+kratos:
+ image: 'oryd/kratos:v1.2.0'
+ privileged: true
+ ports:
+ - '4433:4433'
+ volumes:
+ - ../entrypoint.sh:/entrypoint.sh
+ - ../identity.schema.json:/etc/config/kratos/identity.schema.json
+ - ./kratos.yml:/etc/config/kratos/kratos.yml
+ entrypoint: ['/entrypoint.sh']
+ networks:
+ - rafiki
+```
+
+#### Networks and volumes
+
+In Docker Compose, networks and volumes are necessary for enabling communication between services and persisting data storage for containers.
+
+```sh
+networks:
+ testnet:
+ driver: bridge
+ ipam:
+ config:
+ - subnet: 10.5.0.0/24
+ gateway: 10.5.0.1
+
+volumes:
+ pg-data:
+ tigerbeetle-data:
+```
+
+#### Complete Docker Compose example
+
+
+ Click to expand
+
+```sh
+name: 'my-rafiki'
+services:
+ rafiki-auth:
+ image: ghcr.io/interledger/rafiki-auth:
+ container_name: rafiki-auth
+ environment:
+ AUTH_DATABASE_URL: {postgresql://...}
+ AUTH_SERVER_URL: {https://auth.myrafiki.com}
+ ADMIN_PORT: 3003
+ AUTH_PORT: 3006
+ INTROSPECTION_PORT: 3007
+ INTERACTION_PORT: 3009
+ SERVICE_API_PORT: 3011
+ COOKIE_KEY: {...}
+ IDENTITY_SERVER_SECRET: {...}
+ IDENTITY_SERVER_URL: {https://idp.mysystem.com}
+ REDIS_URL: {redis://...}
+ TRUST_PROXY: true
+ depends_on:
+ - postgres
+ networks:
+ - rafiki
+ ports:
+ - '3003:3003'
+ - '3006:3006'
+ - '3007:3007'
+ - '3009:3009'
+ - '3011:3011'
+ restart: always
+
+rafiki-backend:
+ image: ghcr.io/interledger/rafiki-backend:
+ container_name: rafiki-backend
+ depends_on:
+ - postgres
+ - redis
+ environment:
+ AUTH_SERVER_GRANT_URL: {https://auth.myrafiki.com}
+ AUTH_SERVER_INTROSPECTION_URL: {https://auth.myrafiki.com/3007}
+ AUTH_SERVICE_API_URL: {https://auth.myrafiki.com/3011}
+ DATABASE_URL: {postgresql://...}
+ ILP_ADDRESS: {test.myrafiki}
+ ADMIN_PORT: 3001
+ CONNECTOR_PORT: 3002
+ OPEN_PAYMENTS_PORT: 3000
+ OPEN_PAYMENTS_URL: {https://myrafiki.com}
+ REDIS_URL: {redis://...}
+ WALLET_ADDRESS_URL: {https://myrafiki.com/rafiki-instance}
+ WEBHOOK_URL: {https://mysystem.com/webhooks}
+ EXCHANGE_RATES_URL: {https://mysystem.com/rates}
+ ILP_CONNECTOR_URL: {https://ilp.myrafiki.com}
+ INSTANCE_NAME: {'My ASE name'}
+ TRUST_PROXY: true
+ KEY_ID: ...
+ USE_TIGERBEETLE: true
+ TIGERBEETLE_CLUSTER_ID: 0
+ TIGERBEETLE_REPLICA_ADDRESSES: 10.5.0.50:4342
+ networks:
+ - rafiki
+ ports:
+ - '3000:3000'
+ - '3001:3001'
+ - '3002:3002'
+ privileged: true
+ restart: always
+ volumes:
+ - ../temp/:/workspace/temp/
+
+rafiki-frontend:
+ image: ghcr.io/interledger/rafiki-frontend:
+ container_name: rafiki-frontend
+ depends_on:
+ - rafiki-backend
+ environment:
+ PORT: 3005
+ GRAPHQL_URL: {https://myrafiki.com:3001}
+ OPEN_PAYMENTS_URL: {https://myrafiki.com}
+ KRATOS_CONTAINER_PUBLIC_URL: {http://kratos:4433}
+ KRATOS_BROWSER_PUBLIC_URL: {https://admin.myrafiki.com/kratos}
+ KRATOS_ADMIN_URL: {http://kratos:4434/admin}
+ networks:
+ - rafiki
+ restart: always
+ privileged: true
+ ports:
+ - '3005:3005'
+
+tigerbeetle:
+ image: ghcr.io/tigerbeetle/tigerbeetle:0.16.29
+ privileged: true
+ volumes:
+ - tigerbeetle-data:/var/lib/tigerbeetle
+ networks:
+ rafiki:
+ ipv4_address: 10.5.0.50
+ entrypoint:
+ - /bin/sh
+ - -c
+ - |
+ set -ex
+ DATA_FILE=/var/lib/tigerbeetle/cluster_0_replica_0.tigerbeetle
+ set +e
+ ls $$DATA_FILE
+ DATA_FILE_EXISTS="$$?"
+ set -e
+ echo $$DATA_FILE_EXISTS
+ if [ "$$DATA_FILE_EXISTS" != 0 ]; then
+ ./tigerbeetle format --cluster=0 --replica=0 --replica-count=1 $$DATA_FILE;
+ fi
+ hostname -i
+ ls /var/lib/tigerbeetle
+ ./tigerbeetle start --addresses=0.0.0.0:4342 --development $$DATA_FILE
+
+postgres:
+ image: 'postgres:16'
+ container_name: postgres
+ environment:
+ POSTGRES_USER: ...
+ POSTGRES_PASSWORD: ...
+ networks:
+ - rafiki
+ restart: unless-stopped
+ volumes:
+ - pg-data:/var/lib/postgresql/data
+ - ../dbinit.sql:/docker-entrypoint-initdb.d/init.sql
+
+redis:
+ image: 'redis:7'
+ restart: unless-stopped
+ networks:
+ - rafiki
+
+kratos:
+ image: 'oryd/kratos:v1.2.0'
+ privileged: true
+ ports:
+ - '4433:4433'
+ volumes:
+ - ../entrypoint.sh:/entrypoint.sh
+ - ../identity.schema.json:/etc/config/kratos/identity.schema.json
+ - ./kratos.yml:/etc/config/kratos/kratos.yml
+ entrypoint: ['/entrypoint.sh']
+ networks:
+ - rafiki
+
+networks:
+ rafiki:
+ driver: bridge
+ ipam:
+ config: - subnet: 10.5.0.0/24
+ gateway: 10.5.0.1
+
+volumes:
+ pg-data:
+ tigerbeetle-data:
+
+```
+
+
+
+## Create Nginx config files
+
+Create Nginx configuration files for every exposed domain:
+
+
+
+:::note
+The example domain and subdomain values are for demonstration purposes only. You must use the actual domain names that you set up with your DNS host.
+:::
+
+### Open Payments resource server (`backend` package)
+
+Using the editor of your choice, save the following file as `open_payments_resource_server.config` in the `/etc/nginx/sites-available` directory on your VM:
+
+```sh
+
+server {
+ server_name myrafiki.com;
+
+ listen 443 ssl;
+
+ ssl_certificate /etc/letsencrypt/live/myrafiki.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/myrafiki.com/privkey.pem;
+
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade "";
+ proxy_set_header Connection "";
+ proxy_set_header Host $server_name;
+ proxy_set_header Accept-Encoding "";
+ proxy_cache_bypass $http_upgrade;
+
+ proxy_pass_request_headers on;
+
+ proxy_pass http://localhost:3000;
+ }
+}
+
+server {
+ server_name myrafiki.com;
+
+ listen 80;
+
+ if ($host = myrafiki.com) {
+ return 301 https://$host$request_uri;
+ }
+
+ return 404;
+}
+```
+
+### ILP connector (`backend` package)
+
+Save the following file as `ilp.config` in the `/etc/nginx/sites-available` directory on your VM:
+
+```sh
+server {
+ server_name ilp.myrafiki.com;
+
+ listen 443 ssl;
+
+ ssl_certificate /etc/letsencrypt/live/ilp.myrafiki.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/ilp.myrafiki.com/privkey.pem;
+
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade "";
+ proxy_set_header Connection "";
+ proxy_set_header Host $server_name;
+ proxy_set_header Accept-Encoding "";
+ proxy_cache_bypass $http_upgrade;
+
+ proxy_pass_request_headers on;
+
+ proxy_pass http://localhost:3002;
+ }
+}
+
+server {
+ server_name ilp.myrafiki.com;
+
+ listen 80;
+
+ if ($host = ilp.myrafiki.com) {
+ return 301 https://$host$request_uri;
+ }
+
+ return 404;
+}
+```
+
+### Open Payments auth server (`auth` package)
+
+Save the following file as `open_payments_auth_server.config` in the `/etc/nginx/sites-available` directory on your VM:
+
+```sh
+server {
+ server_name auth.myrafiki.com;
+
+ listen 443 ssl;
+ ssl_certificate /etc/letsencrypt/live/auth.myrafiki.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/auth.myrafiki.com/privkey.pem;
+
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade "";
+ proxy_set_header Connection "";
+ proxy_set_header Host $server_name;
+ proxy_set_header Accept-Encoding "";
+ proxy_cache_bypass $http_upgrade;
+
+ proxy_pass_request_headers on;
+
+ proxy_pass http://localhost:3006;
+ }
+}
+
+server {
+ server_name auth.myrafiki.com;
+
+ listen 80;
+
+ if ($host = auth.myrafiki.com) {
+ return 301 https://$host$request_uri;
+ }
+
+ return 404;
+}
+```
+
+### Admin (`frontend` package)
+
+Save the following file as `admin.config` in the `/etc/nginx/sites-available` directory on your VM:
+
+```sh
+server {
+ server_name admin.myrafiki.com;
+
+ listen 443 ssl;
+
+ ssl_certificate /etc/letsencrypt/live/admin.myrafiki.com/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/admin.myrafiki.com/privkey.pem;
+
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ location / {
+ proxy_http_version 1.1;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade "";
+ proxy_set_header Connection "";
+ proxy_set_header Host $server_name;
+ proxy_set_header Accept-Encoding "";
+ proxy_cache_bypass $http_upgrade;
+
+ proxy_pass_request_headers on;
+
+ proxy_pass http://localhost:3005;
+ }
+}
+
+server {
+ server_name admin.myrafiki.com;
+
+ listen 80;
+
+ if ($host = admin.myrafiki.com) {
+ return 301 https://$host$request_uri;
+ }
+
+ return 404;
+}
+```
+
+## Set up symbolic links
+
+Once the Nginx configuration files have been created, set up symbolic links that will allow Nginx to read those files and redirect the local paths to the exposed domains and ports.
+
+```sh wrap
+sudo ln -s /etc/nginx/sites-available/admin.conf /etc/nginx/sites-enabled/admin.conf
+
+sudo ln -s /etc/nginx/sites-available/open_payments_auth_server.conf /etc/nginx/sites-enabled/open_payments_auth_server.conf
+
+sudo ln -s /etc/nginx/sites-available/ilp.conf /etc/nginx/sites-enabled/ilp.conf
+
+sudo ln -s /etc/nginx/sites-available/open_payments_resource_server.conf /etc/nginx/sites-enabled/open_payments_resource_server.conf
+
+```
+
+## Deploy with Docker Compose
+
+Deploy the configured Rafiki services with Docker Compose:
+
+```sh
+docker compose up -d
+```
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/deployment/helm-k8s.mdx b/packages/documentation/src/content/docs/v1-beta/integration/deployment/helm-k8s.mdx
new file mode 100644
index 0000000000..28dd05c231
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/deployment/helm-k8s.mdx
@@ -0,0 +1,395 @@
+---
+title: Helm & Kubernetes
+slug: v1-beta/integration/deployment/helm-k8s
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+This guide explains how to deploy Rafiki using Helm charts on a Kubernetes cluster. Helm is a package manager for Kubernetes that allows you to define, install, and upgrade complex Kubernetes applications through Helm charts.
+
+Rafiki uses the following key components:
+
+- **Tigerbeetle**: High-performance accounting database used for financial transaction processing and ledger management
+- **PostgreSQL**: Used for storing application data and metadata
+- **Redis**: Used for caching and messaging between components
+
+## Prerequisites
+
+:::caution[Recommended software version]
+We recommended using the latest vendor supported version for each of the software dependencies listed in this section.
+:::
+
+Before you begin, ensure you have the following:
+
+- Kubernetes cluster deployed
+- kubectl
+ installed and configured
+- Helm installed
+
+## Install Rafiki using Helm
+
+#### Add the Interledger Helm repository
+
+Add the official Interledger Helm repository which contains the Rafiki charts:
+
+```bash
+helm repo add interledger https://interledger.github.io/charts
+helm repo update
+```
+
+#### Create yaml file
+
+Create a `values.yaml` file to customize your Rafiki deployment.
+
+:::note
+A template file is in progress and will be included in this section when published.
+:::
+
+#### Install Rafiki
+
+Install Rafiki using the following command:
+
+```bash
+helm install rafiki interledger/rafiki -f values.yaml
+```
+
+This will deploy all Rafiki components to your Kubernetes cluster with the configurations specified in your `values.yaml` file.
+
+If you want to install to a specific namespace:
+
+```bash
+kubectl create namespace rafiki
+helm install rafiki interledger/rafiki -f values.yaml -n rafiki
+```
+
+#### Verify the deployment
+
+Check the status of your deployment with the following commands:
+
+```bash
+# Check all resources deployed by Helm
+helm status rafiki
+
+# Check the running pods
+kubectl get pods
+
+# Check the deployed services
+kubectl get services
+```
+
+## Configure ingress with NGINX Ingress Controller
+
+:::note
+This example demonstrates ingress using NGINX. The exact steps and commands will differ depending on the product you use for ingress in your Kubernetes environment.
+:::
+
+To expose Rafiki services outside the cluster using NGINX Ingress Controller:
+
+#### Install NGINX Ingress Controller
+
+If you don't already have NGINX Ingress Controller installed, you can install it using Helm:
+
+```bash
+# Add the ingress-nginx repository
+helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
+helm repo update
+
+# Install the ingress-nginx controller
+helm install nginx-ingress ingress-nginx/ingress-nginx \
+ --set controller.publishService.enabled=true
+```
+
+Wait for the Load Balancer to be provisioned:
+
+```bash
+kubectl get services -w nginx-ingress-ingress-nginx-controller
+```
+
+#### Configure DNS
+
+Once the Load Balancer has an external IP or hostname assigned, create DNS records:
+
+- `auth.example.com` pointing to the Load Balancer IP/hostname
+- `backend.example.com` pointing to the Load Balancer IP/hostname
+
+:::note
+The example domain and subdomain values are for demonstration purposes only. You must use the actual domain names that you set up with your DNS host.
+:::
+
+#### Apply the configuration
+
+Apply your updated configuration:
+
+```bash
+helm upgrade rafiki interledger/rafiki -f values.yaml
+```
+
+#### Verify ingress configuration
+
+Check if your ingress resources were created correctly:
+
+```bash
+kubectl get ingress
+```
+
+You should find entries for the auth server and backend API ingress resources.
+
+## Port forwarding
+
+If you don't want to use ingress to access Rafiki services, you can use port forwarding to directly access the services:
+
+| Service | Port-Forward Command |
+| ----------- | -------------------------------------------------------- |
+| Auth Server | `kubectl port-forward svc/rafiki-auth-server 3000:3000` |
+| Backend API | `kubectl port-forward svc/rafiki-backend-api 3001:3001` |
+| Admin UI | `kubectl port-forward svc/rafiki-backend-api 3001:3001` |
+| PostgreSQL | `kubectl port-forward svc/rafiki-postgresql 5432:5432` |
+| Redis | `kubectl port-forward svc/rafiki-redis-master 6379:6379` |
+
+## Upgrade Rafiki
+
+To upgrade your Rafiki deployment to a newer version:
+
+```bash
+# Update the Helm repository
+helm repo update
+
+# Upgrade Rafiki
+helm upgrade rafiki interledger/rafiki -f values.yaml
+```
+
+## Uninstall Rafiki
+
+To uninstall Rafiki from your cluster:
+
+```bash
+helm uninstall rafiki
+```
+
+Note that this won't delete Persistent Volume Claims (PVC) created by the PostgreSQL and Redis deployments. If you want to delete them as well:
+
+```bash
+kubectl delete pvc -l app.kubernetes.io/instance=rafiki
+```
+
+## Troubleshooting
+
+### Check pod logs
+
+If a component isn't working correctly, you can check its logs:
+
+```bash
+# List all pods
+kubectl get pods
+
+# Check logs for a specific pod
+kubectl logs pod/rafiki-auth-server-0
+```
+
+### Check resources and logs
+
+```bash
+# List pods and their status
+kubectl get pods
+
+# Check logs for a specific pod
+kubectl logs pod/rafiki-auth-server-0
+
+# Get details about a pod
+kubectl describe pod/rafiki-auth-server-0
+
+# Check services and their endpoints
+kubectl get services
+
+# Check Persistent Volume Claims
+kubectl get pvc
+
+# Check ingress resources
+kubectl get ingress
+```
+
+## Common issues
+
+#### Database connection errors
+
+1. Check if PostgreSQL pods are running:
+ ```
+ kubectl get pods -l app.kubernetes.io/name=postgresql
+ ```
+2. Check PostgreSQL logs:
+ ```
+ kubectl logs pod/rafiki-postgresql-0
+ ```
+3. Verify that the database passwords match those in your `values.yaml`
+
+#### Tigerbeetle initialization failures
+
+1. Check Tigerbeetle logs:
+ ```
+ kubectl logs pod/tigerbeetle-0
+ ```
+2. Ensure that the PVC for Tigerbeetle has been created correctly
+ ```
+ kubectl get pvc -l app.kubernetes.io/name=tigerbeetle
+ ```
+3. Verify that the cluster ID is consistent across all components
+
+#### Ingress issues
+
+1. Verify NGINX Ingress Controller is running:
+ ```
+ kubectl get pods -n ingress-nginx
+ ```
+2. Check if your DNS records are correctly pointing to the ingress controller's external IP
+3. Check the ingress resource:
+ ```
+ kubectl get ingress
+ ```
+4. Check ingress controller logs:
+ ```
+ kubectl logs -n ingress-nginx deploy/nginx-ingress-ingress-nginx-controller
+ ```
+5. Verify that TLS secrets exist if HTTPS is enabled:
+ ```
+ kubectl get secrets
+ ```
+
+#### TLS certificate problems
+
+1. If using cert-manager, check if certificates are properly issued:
+ ```
+ kubectl get certificates
+ ```
+2. Check certificate status:
+ ```
+ kubectl describe certificate [certificate-name]
+ ```
+3. Check cert-manager logs:
+ ```
+ kubectl logs -n cert-manager deploy/cert-manager
+ ```
+
+#### Service unavailable
+
+1. Check if services are running:
+ ```
+ kubectl get services
+ ```
+2. Verify pod health:
+ ```
+ kubectl describe pod [pod-name]
+ ```
+3. Check for resource constraints:
+ ```
+ kubectl top pods
+ ```
+
+#### Connectivity between components
+
+1. Ensure all required services are running:
+ ```
+ kubectl get services
+ ```
+2. Verify service endpoints:
+ ```
+ kubectl get endpoints
+ ```
+3. Test connectivity between pods using temporary debugging pods:
+ ```
+ kubectl run -it --rm debug --image=busybox -- sh
+ # Inside the pod
+ wget -q -O- http://rafiki-auth-server:3000/health
+ ```
+
+## Security considerations
+
+When deploying Rafiki in production, consider the following security practices:
+
+- **Use secure passwords**: Replace all default passwords with strong, unique passwords
+- **Enable TLS**: Use HTTPS for all external communications
+- **Implement network policies**: Use Kubernetes network policies to restrict traffic between pods
+- **Use RBAC**: Use Kubernetes Role-Based Access Control to limit access to your cluster
+- **Use secrets management**: Consider using a secrets management solution
+- **Perform regular updates**: Keep your Rafiki deployment updated
+
+## Backup and recovery
+
+### Database backup
+
+#### PostgreSQL backup
+
+To create a backup of your PostgreSQL database:
+
+```bash
+# Forward PostgreSQL port to local machine
+kubectl port-forward svc/rafiki-postgresql 5432:5432
+
+# Use pg_dump to create a backup
+pg_dump -h localhost -U rafiki -d rafiki > rafiki_pg_backup.sql
+```
+
+#### Tigerbeetle backup
+
+Tigerbeetle is designed to be fault-tolerant with its replication mechanism. However, to create a backup of Tigerbeetle data, you can use the following approach:
+
+```bash
+# Create a snapshot of the Tigerbeetle PVC
+kubectl get pvc tigerbeetle-data-tigerbeetle-0 -o yaml > tigerbeetle-pvc.yaml
+
+# Create a volume snapshot
+cat <Container Storage Interface (CSI) driver capable of volume snapshots. Adjust the `volumeSnapshotClassName` according to your cluster setup.
+:::
+
+### Database recovery
+
+#### PostgreSQL recovery
+
+To restore from a PostgreSQL backup:
+
+```bash
+# Forward PostgreSQL port to local machine
+kubectl port-forward svc/rafiki-postgresql 5432:5432
+
+# Use psql to restore from backup
+psql -h localhost -U rafiki -d rafiki < rafiki_pg_backup.sql
+```
+
+#### Tigerbeetle recovery
+
+To restore Tigerbeetle from a snapshot:
+
+```bash wrap
+# Create a new PVC from the snapshot
+cat <`auth` service provides you with a reference implementation of an Open Payments authorization server. You can use the `auth` service as an alternative to developing your own in-house service for grant authorization and authentication.
+
+The authorization server:
+
+- Authorizes incoming requests from clients (for example, third-party applications) to create payments and quotes on the backend
+- Relays information about the interaction to the client and records the response
+- Issues access tokens, which describe access rights, to clients and communicates with the resource server to validate those rights
+
+## Requirements
+
+You must have the following when using the `auth` service.
+
+- A Redis database for storing session data
+- A Postgres database, separate from the `backend` service’s database, to store auth-related resources (grants, access tokens, and interactions)
+ :::note
+ You can use the same Postgres database instance for both the `backend` and `auth` services, but separate database schemas are required within that instance to maintain a boundary between the objects those services manage.
+ :::
+- Integration with an [identity provider](#identity-provider-idp)
+
+You must also set the environment variables for the `auth` service.
+
+## Incoming client auth requests
+
+When a request comes from a client with an account known to your local instance of Rafiki, the `auth` service uses data stored in the `auth` service’s Postgres database.
+
+When a request comes from a client registered with another instance of Rafiki, the `auth` service resolves the client's key endpoint (for example, `https://wallet.example.com/alice/jwks.json`) to retrieve the client's public keys, then filters out the correct key using the key id (`kid`) in the client's signature.
+
+Review the Open Payments documentation for more information about client keys.
+
+## Identity provider (IdP)
+
+An identity provider (IdP) is a system or service that manages user authentication, identity information, and consent. When you use your Google Account credentials to “Sign in with Google” on an app or website, for example, Google is acting as your identity provider.
+
+You must integrate with an [IdP](/v1-beta/integration/requirements/open-payments/idp) when using Rafiki's `auth` service because the Open Payments standard requires interactive outgoing payment _grant_ requests. In an interactive request, there must be explicit interaction by an individual (for example, a client's end-user) to approve or deny the grant. In this case, the grant must be explicitly approved before an outgoing payment is created.
+
+:::note
+Rafiki’s [`frontend`](/v1-beta/integration/deployment/services/frontend-service) service requires an IdP for authentication and user management of your [Rafiki Admin](/v1-beta/admin/admin-user-guide) users. Out of the box, Rafiki uses Ory Kratos, a cloud-based user management system. Kratos is for internal use only and **cannot** be used as your customer-facing Open Payments authorization server.
+:::
+
+For more information about interactive grants and how they work with identity providers, review the [Identity Provider](/v1-beta/integration/requirements/open-payments/idp) page and the Grant negotiation and authorization page in the Open Payments docs.
+
+## GraphQL Auth Admin API
+
+The `auth` service exposes a [GraphQL Auth Admin API](/v1-beta/apis/graphql/admin-api-overview#auth-admin-api) to manage auth-related resources, such as Open Payments grants.
+
+## Environment variables
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `AUTH_DATABASE_URL` | `auth.postgresql.host`, `auth.postgresql.port`, `auth.postgresql.username`, `auth.postgresql.database`, `auth.postgresql.password` | `postgresql://postgres:password@localhost:5432/auth_development` | The URL of the Postgres database storing your Open Payments grant data. For Helm, these components are provided individually. |
+| `AUTH_SERVER_URL` | `auth.server.domain` | _undefined_ | The public endpoint for your Rafiki instance’s public Open Payments routes. |
+| `COOKIE_KEY` | `auth.cookieKey` | _undefined_ | The koa KeyGrip key that is used to sign cookies for an interaction session. |
+| `IDENTITY_SERVER_URL` | `auth.identityServer.domain` | _undefined_ | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
+| `IDENTITY_SERVER_SECRET` | `auth.identityServer.secret` | _undefined_ | A shared secret between the authorization server and the IdP server; the authorization server will use the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](/integration/requirements/open-payments/idp#x-idp-secret-header) header. |
+| `REDIS_URL` | `auth.redis.host`, `auth.redis.port` | `redis://127.0.0.1:6379` | The connection URL for Redis. For Helm, these components are provided individually. |
+
+
+
+### Conditionally required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TRUST_PROXY` | `auth.trustProxy` | `false` | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| --------------------------------- | ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ACCESS_TOKEN_DELETION_DAYS` | `auth.accessToken.deletionDays` | `30` | The days until expired and/or revoked access tokens are deleted. |
+| `ACCESS_TOKEN_EXPIRY_SECONDS` | `auth.accessToken.expirySeconds` | `600` (10 minutes) | The expiry time, in seconds, for access tokens. |
+| `ADMIN_API_SIGNATURE_VERSION` | `auth.adminApi.signatureVersion` | `1` | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | `auth.adminAPI.signatureTtlSeconds` | `30` | The TTL, in seconds, for which a request’s signature will be valid. |
+| `ADMIN_PORT` | `auth.port.admin` | `3003` | The port of your Rafiki Auth Admin API server. |
+| `AUTH_PORT` | `auth.port.auth` | `3006` | The port of your Open Payments authorization server. |
+| `DATABASE_CLEANUP_WORKERS` | `auth.workers.cleanup` | `1` | The number of workers processing expired or revoked access tokens. |
+| `ENABLE_MANUAL_MIGRATIONS` | `auth.enableManualMigrations` | `false` | When `true`, you must run the auth Postgres database manually with the command `npm run knex – migrate:latest –envproduction` |
+| `INCOMING_PAYMENT_INTERACTION` | `auth.interaction.incomingPayment` | `false` | When `true`, incoming Open Payments grant requests are interactive |
+| `INTERACTION_EXPIRY_SECONDS` | `auth.interactionExpirySeconds` | `600` (10 minutes) | The time, in seconds, for which a user can interact with a grant request before the request expires. |
+| `INTERACTION_PORT` | `auth.port.interaction` | `3009` | The port number of your Open Payments interaction-related APIs. |
+| `INTROSPECTION_PORT` | `auth.port.introspection` | `3007` | The port of your Open Payments access token introspection server. |
+| `SERVICE_API_PORT` | `auth.port.serviceAPIPort` | `3011` | The port to expose the internal service api. |
+| `LIST_ALL_ACCESS_INTERACTION` | `auth.interaction.listAll` | `true` | When `true`, grant requests that include a `list-all` action will require interaction. In these requests, the client asks to list resources that it did not create. |
+| `LOG_LEVEL` | `auth.logLevel` | `info` | Pino log level |
+| `NODE_ENV` | `auth.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
+| `QUOTE_INTERACTION` | `auth.interaction.quote` | `false` | When `true`, quote grants are interactive. |
+| `REDIS_TLS_CA_FILE_PATH` | `auth.redis.tlsCaFile` | `''` | Redis TLS config |
+| `REDIS_TLS_CERT_FILE_PATH` | `auth.redis.tlsCertFile` | `''` | Redis TLS config |
+| `REDIS_TLS_KEY_FILE_PATH` | `auth.redis.tlsKeyFile` | `''` | Redis TLS config |
+| `WAIT_SECONDS` | `auth.grant.waitSeconds` | `5` | The wait time, in seconds, included in a grant request response (`grant.continue`). |
+
+
+
+## Token introspection
+
+When a client makes a request to a resource server, the resource server communicates with the authorization server to:
+
+- Check the validity of the client’s access token
+- Determine whether the client is authorized to access protected resources
+
+This process is called token introspection.
+
+The `token-introspection` package is a client library for making GNAP token introspection requests to an authorization server. It describes how the Rafiki `backend` and `auth` services communicate to validate access tokens. If you’re using Rafiki’s `auth` service, there’s nothing you need to do with this package. Rafiki automatically runs the package internally. If you’re writing your own auth service, you may find the files within the package to be helpful.
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/backend-service.mdx b/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/backend-service.mdx
new file mode 100644
index 0000000000..b9921a8c06
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/backend-service.mdx
@@ -0,0 +1,156 @@
+---
+title: Backend service
+slug: v1-beta/integration/deployment/services/backend-service
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import Backend from '/src/partials/backend-variables.mdx'
+
+Rafiki’s `backend` service is the software’s main service for handling business logic and external communication. The service is responsible for:
+
+- Exposing the endpoints of the [Open Payments APIs](#open-payments) for public clients to initiate payments and look up information. This is the external communication piece.
+- Exposing an [Interledger connector](#interledger-connector) to send and receive STREAM packets with peers.
+- Enabling you to manage accounts and track liquidity.
+- Exposing an internal [GraphQL Backend Admin API](#graphql-backend-admin-api) for service operators to manage accounts, liquidity, and application settings, like peering relationships.
+
+## Requirements
+
+The following are required when using the `backend` service.
+
+- A Redis database as a cache to share STREAM connection details and total amounts received across processes
+- A Postgres database, separate from the `auth` service’s database, for Open Payments resources
+- TigerBeetle or a separate Postgres database for accounting liquidity
+ :::note
+ You can use the same database instance of Postgres for the `backend` service, `auth` service, and accounting liquidity (if not using TigerBeetle). However, separate database schemas are required within the Rafiki instance to maintain boundaries between the managed objects.
+ :::
+
+You must also set the environment variables for the `backend` service.
+
+## Open Payments
+
+The `backend` service exposes the Open Payments APIs, which are auth-protected using an opinionated version of the Grant Negotiation and Authorization Protocol (GNAP). Review the [`auth`](/v1-beta/integration/deployment/services/auth-service) service page for more details about grant authorization and authentication.
+
+The `backend` service allows you to manage Open Payments incoming payments, quotes, and outgoing payments. Quotes and outgoing payments call the ILP connector, described in the next section, to send ILP packets. Quoting sends unfulfillable probe packets, for example to determine a transaction’s cost before executing the payment. Outgoing payments send packets as part of executing the payment.
+
+## Interledger connector
+
+The `backend` service exposes an ILP connector to send and receive ILP packets between peers.
+
+Some of the responsibilities of a connector include:
+
+- Authenticating packets against ILP account credentials.
+- Forwarding packets to a sender or receiver.
+- Rejecting a packet for any number of reasons, including expiration, insufficient liquidity, rate limit exceeded, or if the amount exceeds the `maxPacketAmount` [agreed to](/v1-beta/integration/requirements/peers#perform-prerequisites) by the connector and its peer.
+- Converting currencies.
+- Fulfilling packets with an internal STREAM server.
+
+The `backend` service includes an HTTP server listening on the configured `CONNECTOR_PORT`. Your connector can receive incoming packets via HTTP and/or direct calls from within the `backend` service. An incoming packet is only accepted if it's from a configured peer and accompanied by your peer’s incoming HTTP `authToken`.
+
+Similarly, if a packet's destination address corresponds to a peer, your connector forwards the packet to your peer over HTTP, along with your peer's outgoing HTTP `authToken`.
+
+:::note[Auth tokens]
+You and your peer should have exchanged incoming and outgoing auth tokens as part of establishing your [peering relationship](/v1-beta/integration/requirements/peers#perform-prerequisites).
+:::
+
+A packet can either continue on to your peer via HTTP or end at your Rafiki instance's STREAM server. If the packet terminates at your STREAM server, your connector attempts to extract and decode the payment tag from the packet's destination address. When your connector successfully matches the tag with a locally managed wallet address or incoming payment, the connector does not forward the packet. Instead, it credits the corresponding balance and track the total amount received in Redis to support STREAM receipts. Packets addressed to a wallet address happen via SPSP.
+
+## GraphQL Backend Admin API
+
+The `backend` service exposes a GraphQL [Backend Admin API](/v1-beta/apis/graphql/admin-api-overview#backend-admin-api) to manage assets, peers, wallet addresses, Open Payments resources, and several types of liquidity.
+
+## Environment variables
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| `AUTH_SERVER_GRANT_URL` | `backend.serviceUrls.AUTH_SERVER_GRANT_URL` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. |
+| `AUTH_SERVER_INTROSPECTION_URL` | `backend.serviceUrls.AUTH_SERVER_INTROSPECTION_URL` | _undefined_ | The endpoint on your Open Payments authorization server to introspect an access token. |
+| `DATABASE_URL` | `backend.postgresql.host`, `backend.postgresql.port`, `backend.postgresql.username`, `backend.postgresql.database`, `backend.postgresql.password` | `postgresql://postgres:password@localhost:5432/development` | The Postgres database URL of the database storing your resource data. For Helm, these components are provided individually. |
+| `EXCHANGE_RATES_URL` | `backend.serviceUrls.EXCHANGE_RATES_URL` | _undefined_ | The endpoint your Rafiki instance uses to request exchange rates. |
+| `ILP_ADDRESS` | `backend.ilp.address` | _undefined_ | The ILP address of your Rafiki instance. |
+| `ILP_CONNECTOR_URL` | `backend.ilp.connectorUrl` | _undefined_ | The ILP connector address where ILP packets are received. |
+| `KEY_ID` | `backend.key.id` | _undefined_ | Your Rafiki instance’s client key ID. |
+| `OPEN_PAYMENTS_URL` | `backend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | The public endpoint of your Open Payments resource server. |
+| `REDIS_URL` | `backend.redis.host`, `backend.redis.port` | `redis://127.0.0.1:6379` | The Redis URL of the database handling ILP packet data. For Helm, these components are provided individually. |
+| `USE_TIGERBEETLE` | `backend.use.tigerbeetle` | `true` | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
+| `WEBHOOK_URL` | `backend.serviceUrls.WEBHOOK_URL` | _undefined_ | Your endpoint that consumes webhook events. |
+
+
+
+### Conditionally required
+
+
+
+| Variable | Helm value name | Default | Description |
+| --------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `INSTANCE_NAME` | `backend.instance.name` | _undefined_ | Your Rafiki instance's name used to communicate for auto-peering and/or [telemetry](/overview/concepts/telemetry). Required when auto-peering and/or telemetry is enabled |
+| `TRUST_PROXY` | `backend.trustProxy` | `false` | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| ----------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ADMIN_PORT` | `backend.port.admin` | `3001` | The port of your Backend Auth API server. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The TTL, in seconds, for which a request’s signature will be valid. |
+| `API_SECRET` | _undefined_ | _undefined_ | N/A |
+| `API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the request signing algorithm used to generate signatures. |
+| `AUTO_PEERING_SERVER_PORT` | `backend.autoPeering.serverPort` | `3005` | If auto-peering is enabled, the server will use this port. |
+| `CONNECTOR_PORT` | `backend.port.connector` | `3002` | The port of the ILP connector for sending packets via ILP over HTTP. |
+| `ENABLE_AUTO_PEERING` | `backend.enable.autoPeering` | `false` | When `true`, auto-peering is enabled. |
+| `ENABLE_MANUAL_MIGRATIONS` | `backend.enableManualMigrations` | `false` | When `true`, you must run the database manually with the command `npm run knex – migrate:latest –env production` |
+| `ENABLE_SPSP_PAYMENT_POINTERS` | `backend.enable.spspPaymentPointers` | `true` | When `true`, the SPSP route is enabled. |
+| `ENABLE_TELEMETRY` | _undefined_ | `false` | Enables the telemetry service on Rafiki. |
+| `ENABLE_TELEMETRY_TRACES` | _undefined_ | `false` | N/A |
+| `EXCHANGE_RATES_LIFETIME` | `backend.lifetime.exchangeRate` | `15_000` | The time, in milliseconds, the exchange rates you provide via the `EXCHANGE_RATES_URL` are valid. |
+| `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` | `backend.idempotency.keyLockMs` | `2000` | The TTL, in milliseconds, for `idempotencyKey` concurrency lock on GraphQL mutations on the Backend Admin API. |
+| `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS` | `backend.idempotency.keyTTL` | `86400000` (24 hours) | The TTL, in milliseconds, for `idempotencyKey` on GraphQL mutations on the Backend Admin API. |
+| `INCOMING_PAYMENT_CREATED_POLL_FREQUENCY_MS` | _undefined_ | `1000` | N/A |
+| `INCOMING_PAYMENT_CREATED_POLL_TIMEOUT_MS` | _undefined_ | `10000` | N/A |
+| `INCOMING_PAYMENT_EXPIRY_MAX_MS` | `backend.incomingPayment.expiryMaxMs` | `2592000000` (30 days) | The maximum into the future, in milliseconds, incoming payments expiry can be set to on creation. |
+| `INCOMING_PAYMENT_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `INCOMING_PAYMENT_WORKERS` will wait until checking an empty incoming payment request queue again. |
+| `INCOMING_PAYMENT_WORKERS` | `backend.workers.incomingPayment` | `1` | The number of workers processing incoming payment requests. |
+| `LOG_LEVEL` | `backend.logLevel` | `info` | Pino log level |
+| `MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS` | _undefined_ | `5` | Specifies how many times an outgoing payment is retried before failing completely |
+| `NODE_ENVIRONMENT` | `backend.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
+| `OPEN_PAYMENTS_PORT` | `backend.port.openPayments` | `3003` | The port of your Open Payments resource server. |
+| `OPEN_TELEMETRY_COLLECTOR_URLS` | _undefined_ | \*undefined | N/A |
+| `OPEN_TELEMETRY_EXPORT_INTERVAL` | _undefined_ | `15000` | N/A |
+| `OPEN_TELEMETRY_TRACE_COLLECTOR_URLS` | _undefined_ | _undefined_ | N/A |
+| `OUTGOING_PAYMENT_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `OUTGOING_PAYMENT_WORKERS` wait until they check an empty outgoing payment request queue again. |
+| `OUTGOING_PAYMENT_WORKERS` | `backend.workers.outgoingPayment` | `4` | The number of workers processing outgoing payment requests. |
+| `POLL_INCOMING_PAYMENT_CREATED_WEBHOOK` | _undefined_ | `false` | N/A |
+| `PRIVATE_KEY_FILE` | `backend.key.file` | _undefined_ | The path to your Rafiki instance’s client private key. |
+| `QUOTE_LIFESPAN` | `backend.lifetime.quote` | `5 * 60_000` (5 minutes) | The time, in milliseconds, an Open Payments quote is valid for. |
+| `REDIS_TLS_CA_FILE_PATH` | `backend.redis.tlsCaFile` | `''` | Redis TLS config |
+| `REDIS_TLS_CERT_FILE_PATH` | `backend.redis.tlsCertFile` | `''` | Redis TLS config |
+| `REDIS_TLS_KEY_FILE_PATH` | `backend.redis.tlsKeyFile` | `''` | Redis TLS config |
+| `SIGNATURE_SECRET` | `backend.quoteSignatureSecret` | _undefined_ | The secret to generate request header signatures for webhook event requests. |
+| `SIGNATURE_VERSION` | `backend.signatureVersion` | `1` | The version number to generate request header signatures for webhook events. |
+| `SLIPPAGE` | `backend.ilp.slippage` | `0.01` (1%) | The accepted ILP rate fluctuation. |
+| `STREAM_SECRET` | `backend.ilp.streamSecret` | _undefined_ | The seed secret to generate shared STREAM secrets. |
+| `TELEMETRY_EXCHANGE_RATES_LIFETIME` | _undefined_ | `86_400_000` | N/A |
+| `TELEMETRY_EXCHANGE_RATES_URL` | _undefined_ | `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json` | The endpoint Rafiki will query for exchange rates. Used as a fallback if/when [exchange rates](/integration/requirements/exchange-rates) aren’t provided. |
+| `TIGERBEETLE_CLUSTER_ID` | _undefined_ | `0` | The TigerBeetle cluster ID picked by the system that starts the TigerBeetle cluster to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES` | _undefined_ | `3004` | TigerBeetle replica addresses for all replicas in the cluster. The addresses are comma-separated IP addresses/ports, to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES.SPLIT` | _undefined_ | `3004` | N/A |
+| `TIGERBEETLE_TWO_PHASE_TIMEOUT_SECONDS` | _undefined_ | `5` | N/A |
+| `WALLET_ADDRESS_DEACTIVATION_PAYMENT_GRACE_PERIOD_MS` | `backend.walletAddress.deactivationPaymentGratePeriodMs` | `86400000` (24 hours) | The time into the future, in milliseconds, to set expiration of Open Payments incoming payments when deactivating a wallet address. |
+| `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | `backend.walletAddress.lookupTimeoutMs` | `1500` | The time, in milliseconds, you have to create a missing wallet address before timeout. |
+| `WALLET_ADDRESS_POLLING_FREQUENCY_MS` | `backend.walletAddress.pollingFrequencyMs` | `100` | The frequency of polling while waiting for you to create a missing wallet address. |
+| `WALLET_ADDRESS_URL` | `backend.serviceUrls.WALLET_ADDRESS_URL` | `http://127.0.0.1:3001/.well-known/pay` | Your Rafiki instance’s internal wallet address. |
+| `WALLET_ADDRESS_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `WALLET_ADDRESS_WORKERS` wait until checking the empty wallet address request queue again. |
+| `WALLET_ADDRESS_WORKERS` | `backend.workers.walletAddress | `1` | The number of workers processing wallet address requests. |
+| `WEBHOOK_MAX_RETRY` | `backend.webhookMaxRetry` | `10` | The maximum number of times your Rafiki instance’s backend retries sending a certain webhook event to your configured `WEBHOOK_URL`. |
+| `WEBHOOK_TIMEOUT` | `backend.lifetime.webhook` | `2000` (2 seconds) | The time, in milliseconds, that your Rafiki instance will wait for a `200` response from your webhook endpoint. If a `200` response is not received, Rafiki will time out and try to send the webhook event again. |
+| `WEBHOOK_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `WEBHOOK_WORKERS` will wait until they check the empty webhook event queue again. |
+| `WEBHOOK_WORKERS` | `backend.workers.webhook` | `1` | The number of workers processing webhook events. |
+| `WITHDRAWAL_THROTTLE_DELAY` | `backend.withdrawalThrottleDelay` | _undefined_ | The delay in liquidity withdrawal processing. |
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/frontend-service.mdx b/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/frontend-service.mdx
new file mode 100644
index 0000000000..3d40f8e401
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/deployment/services/frontend-service.mdx
@@ -0,0 +1,68 @@
+---
+title: Frontend service
+slug: v1-beta/integration/deployment/services/frontend-service
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import Frontend from '/src/partials/frontend-variables.mdx'
+import KratosWarn from '/src/partials/kratos-warning.mdx'
+
+Rafiki’s `frontend` service provides an optional internal admin interface, called the [Rafiki Admin application](/v1-beta/admin/admin-user-guide), for you to manage your Rafiki instance through a Remix web app. This service communicates with the [Backend Admin API](/v1-beta/apis/graphql/admin-api-overview#backend-admin-api) to facilitate administrative tasks within Rafiki.
+
+## Requirements
+
+The following are required when using the `frontend` service:
+
+- A Rafiki [`backend`](/v1-beta/integration/deployment/services/backend-service) service up and running to access the Backend Admin API.
+- An identity provider for authentication and user management. Out of the box, the Rafiki Admin application uses Ory Kratos, a secure and fully open-source identity management solution.
+
+
+
+You must also set the environment variables for the `frontend` service.
+
+## Rafiki Admin settings
+
+While the `frontend` service is not required to run a Rafiki instance, it's highly recommended. A number of administrative tasks could be performed programmatically via the Backend Admin API, but the `frontend` service makes these functions available through a user-friendly interface.
+
+## Environment variables
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------- | ---------------------------------------- | ----------- | -------------------------------------------- |
+| `GRAPHQL_URL` | `frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki’s GraphQL Auth Admin API |
+| `OPEN_PAYMENTS_URL` | `frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint |
+| `PORT` | `frontend.port` | _undefined_ | Port from which to host the Rafiki Remix app |
+
+
+
+### Conditionally required
+
+The following variables are required only when `AUTH_ENABLED` is set to `true`.
+
+
+
+| Variable | Helm value name | Default | Description |
+| ----------------------------- | ------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `KRATOS_ADMIN_URL` | `frontend.kratos.adminUrl` | _undefined_ | The admin endpoint/container address for Kratos |
+| `KRATOS_CONTAINER_PUBLIC_URL` | `frontend.kratos.containerPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from within the Docker network. This is used for backend calls to Kratos. |
+| `KRATOS_BROWSER_PUBLIC_URL` | `frontend.kratos.browserPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from a browser outside of the Docker network. This is used for calls from a browser (what you see in the Rafiki Admin UI) to the Kratos server on the backend. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| -------------------------------- | -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `AUTH_ENABLED` | `frontend.authEnabled` | `true` | When `true`, only authenticated users can be granted access to Rafiki Admin by an administrator |
+| `SIGNATURE_SECRET` | `frontend.quoteSignatureSecret` | _undefined_ | The signature secret used to authenticate requests to the Backend Admin API. |
+| `SIGNATURE_VERSION` | `frontend.signatureVersion` | `1` | The signature version number used to authenticate requests to the Backend Admin API. |
+| `ENABLE_INSECURE_MESSAGE_COOKIE` | `frontend.enableInsecureMessageCookie` | `true` | When set to `true`, `t`, or `1`, cookie will be transmitted over insecure HTTP connection. Insecure message cookies are required for flash messages to work over HTTP. |
+| `NODE_ENV` | `frontend.nodeEnv` | `production` | The type of node environment: `development`, `test`, or `production`. |
+| `LOG_LEVEL` | `frontend.logLevel` | `info` | Pino log level |
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/playground/autopeering.mdx b/packages/documentation/src/content/docs/v1-beta/integration/playground/autopeering.mdx
new file mode 100644
index 0000000000..6c71e0925e
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/playground/autopeering.mdx
@@ -0,0 +1,52 @@
+---
+title: Auto-Peering with the Test Network
+slug: v1-beta/integration/playground/autopeering
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+You can start one local instance of Rafiki and peer it automatically with the remote Test Network by running the following commands:
+
+```sh
+## using Tigerbeetle DB
+pnpm localenv:compose:autopeer
+## OR using Postgres DB`
+pnpm localenv:compose:psql:autopeer
+```
+
+The mock account servicing entity, Cloud Nine Wallet, in your local Rafiki instance will automatically peer with the remote Test Network instance. The required services will be exposed externally using the localtunnel package.
+
+The exposed ports are:
+
+| Service | Port |
+| ------------- | ---- |
+| Open Payments | 3000 |
+| ILP connector | 3002 |
+| Auth server | 3006 |
+
+To use the Open Payments example in the Bruno API collection, follow these steps:
+
+1. Navigate to localhost:3030 to find the list of created wallet addresses. Alternatively, you can run the following command:
+
+```sh
+docker logs rafiki-cloud-nine-mock-ase-1
+```
+
+2. Copy the URL of one of the wallet addresses.
+
+3. Set the URL as the `senderWalletAddress` variable in the Bruno Autopeering environment.
+
+:::note
+To visit the consent screen for the outgoing payment grant request, you must go through an additional login step by providing your IPv4 address as a tunnel password. You can look up your current IPv4 address by visiting
+loca.lt/mytunnelpassword or whatismyip.com
+:::
+
+To shut down the connection and clear the environment, run the following command:
+
+```sh
+pnpm localenv:compose down
+
+## Running the command to shut down is necessary
+## as any subsequent script run to spin up a local Rafiki instance
+## will use different wallet addresses.
+```
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/playground/overview.mdx b/packages/documentation/src/content/docs/v1-beta/integration/playground/overview.mdx
new file mode 100644
index 0000000000..ea34f5abcb
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/playground/overview.mdx
@@ -0,0 +1,356 @@
+---
+title: Overview
+slug: v1-beta/integration/playground/overview
+---
+
+import {
+ Mermaid,
+ MermaidWrapper,
+ LinkOut,
+ Disclosure,
+ LargeImg
+} from '@interledger/docs-design-system'
+
+The Local Playground provides a suite of packages that, together, mock an account servicing entity that has deployed Rafiki. It exposes an SPSP endpoint, the [Open Payments APIs](/v1-beta/overview/concepts/open-payments) with its required GNAP auth endpoints to request grants, a STREAM endpoint for receiving Interledger packets, and the Rafiki Admin app to view and manage each Rafiki instance.
+
+This suite of packages includes:
+
+
|
+| [`auth`](/v1-beta/integration/deployment/services/auth-service) | GNAP auth server |
+| `mock-account-servicing-entity` | mocks an account servicing entity |
+| [`frontend`](/v1-beta/integration/deployment/services/frontend-service) | Remix app to expose a UI for Rafiki admin management via interaction with the Backend Admin APIs |
+
+
+
+These packages depend on the following databases:
+
+
+
+The Local Playground comes with containerized versions of the Rafiki packages and two pre-configured docker-compose files (Cloud Nine Wallet and Happy Life Bank) to start two mock account servicing entities with their respective Rafiki `backend` and `auth` servers. They automatically peer, and two to three user accounts are created on both of them.
+
+This environment will set up a playground where you can use the GraphQL Admin APIs and the Open Payments APIs.
+
+:::note
+The Mock ASE provided in this repository is intended solely for internal use and demonstration purposes. It is not designed to serve as a reference architecture. If you are looking for a reference implementation of an ASE, please refer to the Test Wallet.
+:::
+
+## Running the local environment
+
+To spin up the Local Playground install the following software on your machine:
+
+- A Rafiki local environment setup
+- The Docker platform.
+- An open-source API client Bruno
+
+### Setup
+
+The local environment can be set up using either TigerBeetle or Postgres as the accounting database.
+
+#### Using TigerBeetle
+
+This option enables the primary instance (Cloud Nine Wallet) to utilize TigerBeetle for its accounting database. The secondary instance (Happy Life Bank) will always run Postgres as its accounting database.
+
+To run the local environment with TigerBeetle, execute the following command from the root of the project:
+
+```bash title="Using TigerBeetle"
+pnpm localenv:compose up
+```
+
+#### Using Postgres
+
+If you want the primary instance (Cloud Nine Wallet) to use Postgres as the accounting database instead of TigerBeetle, you must use the `psql` variant of the `localenv:compose` command as follows:
+
+```bash title="Using Postgres"
+pnpm localenv:compose:psql up
+```
+
+The local environment consists of a primary and secondary Rafiki instance, each with its docker-compose file (Cloud Nine Wallet, Happy Life Bank). The primary Cloud Nine Wallet docker-compose file (`./cloud-nine-wallet/docker-compose.yml`) includes the primary Rafiki `backend` and `auth` services, as well as the required data stores, which include TigerBeetle (if enabled), Redis, and Postgres. The primary instance contains all of the necessary components so that it can run independently.
+
+The secondary Happy Life Bank docker-compose file (`./happy-life-bank/docker-compose.yml`) includes only the Rafiki services, not the data stores. It uses the data stores created by the primary Rafiki instance, so it can’t be run independently. The `pnpm localenv:compose up` command starts both the primary and secondary instances.
+
+### Environment components
+
+The following components are made available via the Local Playground:
+
+
+
+#### Mock account servicing entity 1 - Cloud Nine Wallet
+
+| Label | Component | URL |
+| ----- | ---------------------------------- | ------------------------------- |
+| a | User Interface | `http://localhost:3030` |
+| b | Backend Admin API | `http://localhost:3001/graphql` |
+| c | Open Payments API | `http://localhost:3000` |
+| d | Auth Admin API | `http://localhost:3003/graphql` |
+| e | Open Payments Auth API | `http://localhost:3006` |
+| f | Rafiki Admin UI | `http://localhost:3010` |
+| g | Kratos API - _disabled by default_ | `http://localhost:4433` |
+
+#### Mock account servicing entity 2 - Happy Life Bank
+
+| Label | Component | URL |
+| ----- | ---------------------------------- | ------------------------------- |
+| h | User Interface | `http://localhost:3031` |
+| i | Backend Admin API | `http://localhost:4001/graphql` |
+| j | Open Payments API | `http://localhost:4000` |
+| k | Auth Admin API | `http://localhost:4003/graphql` |
+| l | Open Payments Auth API | `http://localhost:4006` |
+| m | Rafiki Admin UI | `http://localhost:4010` |
+| n | Kratos API - _disabled by default_ | `http://localhost:4432` |
+
+#### Mail Slurper
+
+| Label | Component | URL |
+| ----- | ------------------------------- | ----------------------- |
+| o | Mail UI - _disabled by default_ | `http://localhost:4436` |
+
+#### Database
+
+| Component | URL |
+| --------------- | ----------------------- |
+| Postgres Server | `http://localhost:5432` |
+
+### Rafiki Admin
+
+Manage and view information about the Rafiki instance(s) through the [Rafiki Admin](/v1-beta/admin/admin-user-guide/) application. Rafiki Admin is a Remix app for querying info and executing mutations against the Rafiki [Backend Admin API](https://rafiki.dev/integration/playground/overview#admin-apis).
+
+- Cloud Nine Wallet - `http://localhost:3010`
+- Happy Life Bank - `http://localhost:4010`
+
+We have secured access to Rafiki Admin using Ory Kratos; however, in our local playground setup we've chosen to disable user authentication for easier development and testing interactions.
+
+:::note[Enabling Rafiki Admin authentication locally]
+If you'd like to enable authentication locally you can run `pnpm localenv:compose:adminauth up`
+
+Separate registrations/users are required for each mock ASE's Admin app as the ASEs are designed to run as separate mock entities. Visit the Rafiki Admin user guide to learn how to [invite](/v1-beta/admin/admin-user-guide#invite-a-user) and [remove](/v1-beta/admin/admin-user-guide#remove-a-user) users via provided scripts.
+
+After you've registered, you can come back to your Rafiki Admin account by navigating to `localhost:3010` (Cloud Nine Wallet) or `localhost:4010` (Happy Life Bank) and logging in.
+
+Follow these steps to reset a user's Rafiki Admin password.
+
+1. Select the forgot password link and enter an email for a registered user.
+2. Open [Mail Slurper](http://localhost:4436/) to access the recovery link for the account.
+ :::
+
+### Exploring Accounts on Cloud Nine Wallet
+
+Navigate to `localhost:3030` to view the accounts on Cloud Nine Wallet.
+
+:::note
+The accounts for Happy Life Bank can be found on `localhost:3031`.
+:::
+
+Select an account name to view a list of transactions.
+
+### Debugging
+
+Debuggers for the services are exposed on the following ports:
+
+| Services | IP and Port |
+| ----------------------- | -------------- |
+| Cloud Nine Backend | 127.0.0.1:9229 |
+| Cloud Nine Auth | 127.0.0.1.9230 |
+| Happy Life Bank Backend | 127.0.0.1:9231 |
+| Happy Life Bank Auth | 127.0.0.1:9232 |
+
+#### Debugging with a Chromium browser:
+
+1. Go to chrome://inspect
+2. Select **Configure** and add the IP addresses and ports detailed above
+3. Start the docker containers
+4. Select **Inspect** on the service you want to debug to open the Chromium debugger.
+
+You can either trigger the debugger by adding `debugger` statements in the code and restarting the Docker containers or by adding breakpoints directly to the Chromium debugger after starting the Docker containers.
+
+#### Debugging with VS Code:
+
+To debug with VS Code, add this configuration to your `.vscode/launch.json`:
+
+```json
+{
+ "name": "Attach to docker (cloud-nine-backend)",
+ "type": "node",
+ "request": "attach",
+ "port": 9229,
+ "address": "localhost",
+ "localRoot": "${workspaceFolder}",
+ "remoteRoot": "/home/rafiki/",
+ "restart": true
+},
+```
+
+The `localRoot` variable will depend on the location of the `launch.json` file relative to Rafiki’s root directory.
+
+For more ways to connect debuggers, refer to the Node JS docs for debugging.
+
+### Shutting down
+
+To shut down your local instance, run the following commands:
+
+```sh
+# tear down
+pnpm localenv:compose down
+
+# tear down and delete database volumes
+pnpm localenv:compose down --volumes
+
+# tear down, delete database volumes and remove images
+pnpm localenv:compose down --volumes --rmi all
+```
+
+### Commands
+
+The following are the most commonly used commands:
+
+| Description | Command |
+| ----------------------------------------------------------------- | ------------------------------------------------ |
+| Show all merged config (with TigerBeetle) | `pnpm localenv:compose config` |
+| Start (with TigerBeetle) | `pnpm localenv:compose up` |
+| Start (with TigerBeetle) detached | `pnpm localenv:compose up -d` |
+| Down (with TigerBeetle) | `pnpm localenv:compose down` |
+| Down and remove volumes (with TigerBeetle) | `pnpm localenv:compose down --volumes` |
+| Down and remove volumes (with TigerBeetle) and images | `pnpm localenv:compose down --volumes --rmi all` |
+| Show all merged config (with Postgres) | `pnpm localenv:compose:psql config` |
+| Build all the containers (with TigerBeetle) | `pnpm localenv:compose build` |
+| Start (with Postgres) | `pnpm localenv:compose:psql up` |
+| Start (with Postgres) detached | `pnpm localenv:compose:psql up -d` |
+| Down (with Postgres) | `pnpm localenv:compose:psql down` |
+| Down (with Postgres) and remove volumes | `pnpm localenv:compose:psql down --volumes` |
+| Build all the containers (with Postgres) | `pnpm localenv:compose:psql build` |
+| Start with local admin auth enabled (this is disabled by default) | `pnpm localenv:compose:adminauth up ` |
+
+:::note[Enabling TigerBeetle]
+When enabled, TigerBeetle is only used by the primary instance (Cloud Nine Wallet). The secondary instance (Happy Life Bank) will always run Postgres as its accounting database.
+:::
+
+### Interacting with the Local Playground
+
+#### Bruno & Open Payments APIs
+
+The Open Payments APIs can be interacted with using the [Bruno collection](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki) ([resource server endpoints](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/Open%20Payments%20APIs) and [auth server endpoints](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/Open%20Payments%20Auth%20APIs)).
+To interact with the Open Payments APIs using Bruno, you must:
+
+1. Load the collection into Bruno by selecting **Open Collection**.
+2. Navigate to `/rafiki/bruno/collections/Rafiki` on your machine and select **Open**.
+3. Furthermore, you need to either load the [Local Environment](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/environments/Local%20Playground.bru) or the [Remote Test Network Environment](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/environments/Remote.bru).
+
+:::tip[Troubleshooting Bruno errors]
+If you receive the error below while running the Bruno collection, try switching from Safe Mode to Developer Mode
+
+``Error invoking remote method `send-http-request`: Error : Error: Cannot find module crypto``
+:::
+
+The Examples folder in the Bruno collection includes an [Open Payments](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/Examples/Open%20Payments) example that can be executed in the following sequence:
+
+
+ >SW: 1. GET wallet address
+ SW-->>B: 200 OK (authServer URL)
+
+ B->>RW: 2. GET wallet address
+ RW-->>B: 200 OK (authServer URL)
+
+ Note over B,AS: Non-interactive grant for incoming payment resource
+ B->>AS: 3. POST grant request (incoming-payment)
+ AS-->>B: 200 OK (accessToken)
+
+ B->>RW: 4. POST /incoming-payments
+ RW-->>B: 201 Created (incomingPaymentId)
+
+ Note over B,AS: Non-interactive grant for quote resource
+ B->>AS: 5. POST grant request (quote)
+ AS-->>B: 200 OK (accessToken)
+
+ B->>SW: 6. POST /quotes
+ SW-->>B: 201 Created (quoteId)
+
+ Note over B,AS: Interactive grant required for outgoing payment resource
+ B->>AS: 7. POST grant request (outgoing-payment)
+ AS-->>B: 200 OK (redirect_uri)
+
+ rect rgb(240, 240, 240)
+ Note over B,AS: Interactive authorization User interaction required to obtain consent
+ B->>IdP: Redirect for user consent
+ IdP-->>B: User consents
+ B->>AS: 8. POST /continue/{continueId}
+ end
+
+ AS-->>B: 200 OK (accessToken)
+
+ B->>SW: 9. POST /outgoing-payments
+ SW-->>B: 201 Created (outgoingPaymentId)
+
+ B->>SW: 10. GET /outgoing-payments/{outgoingPaymentId}
+ SW-->>B: 200 OK
+
+`}
+/>
+
+
+
+The sequence of steps outlined below corresponds to the interactions depicted in the diagram above:
+
+1. Requests the sender's wallet address
+2. Requests the receiver's wallet address
+3. Requests a grant to create an incoming payment on the receiver's account
+4. Creates an incoming payment on receiver's account
+5. Requests a grant to create and read a quote on the sender's account
+6. Creates a quote on the sender's account
+7. Requests a grant to create and read an outgoing payment on the sender's account
+
+:::note
+You have to go through an interaction flow by selecting the `redirect` link in the grant request's response. More information about the interaction flow can be found here.
+:::
+
+8. Continues the grant request
+9. Creates an outgoing payment on the sender's account
+10. Fetches the outgoing payment on the sender's account
+
+#### Admin APIs
+
+In addition to using Rafiki Admin to interact with the Admin APIs, you can also use the Apollo Explorer on `localhost:3001/graphql` and `localhost:4001/graphql` for Cloud Nine Wallet and Happy Life Bank, respectively, and via the [Bruno collection](https://github.com/interledger/rafiki/tree/main/bruno/collections/Rafiki/Rafiki%20Admin%20APIs). The Bruno collection is configured to use the local environment's default endpoints.
+
+#### SPSP
+
+Every wallet address also serves as an SPSP endpoint. A `GET` request with an `Accept` header `application/spsp4+json` will return an SPSP response with STREAM connection details. The following example uses `http://localhost:3000/accounts/gfranklin` as the SPSP endpoint.
+
+```sh wrap
+GET http://localhost:3000/accounts/gfranklin HTTP/1.1
+Host:backend
+Accept:application/spsp4+json
+
+HTTP/1.1 200 OK
+Connection:keep-alive
+Content-Length:220
+Content-Type:application/spsp4+json
+Date:Thu, 23 Feb 2023 13:07:24 GMT
+Keep-Alive:timeout=5
+
+
+{
+ "destination_account": "test.rafiki.viXmy1OVHgvmQakNjX1C6kQM",
+ "receipts_enabled": false,
+ "shared_secret": "Rz_vudcg13EPs8ehL2drvZFJS1LJ4Y3EltOI60-lQ78"
+}
+```
+
+### Known issues
+
+#### TigerBeetle container exists with code 137
+
+There is a known issue when running TigerBeetle in Docker. The container exits without logs and simply shows error code 137. To fix this, increase the Docker memory limit. If you run the local Docker playground on a Windows machine via the Windows Subsystem for Linux (WSL), you can increase the memory limit by configuring your `.wslconfig` file.
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/playground/testnet.mdx b/packages/documentation/src/content/docs/v1-beta/integration/playground/testnet.mdx
new file mode 100644
index 0000000000..0b89806071
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/playground/testnet.mdx
@@ -0,0 +1,25 @@
+---
+title: Testnet
+slug: v1-beta/integration/playground/testnet
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+## The Test Network
+
+The [Local Playground](/v1-beta/integration/playground/overview/) is not the only way to test and try out Rafiki. You can also try the Test Network, which is a Rafiki implementation bundled with a wallet and an e-commerce application available online. As it is a test environment, you can experiment integrating with the Interledger network without using real money.
+
+### Applications
+
+The current applications include:
+
+-
+ An Interledger test wallet
+
+-
+ An e-commerce application
+
+
+## Peering with the Test Network
+
+If you have installed the [Local Playground](/v1-beta/integration/playground/overview/) you can peer your local Rafiki instance with the remote Test Network. Refer to the [autopeering](/v1-beta/integration/playground/autopeering/) page for more details.
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/assets.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/assets.mdx
new file mode 100644
index 0000000000..bda9db3821
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/assets.mdx
@@ -0,0 +1,66 @@
+---
+title: Assets
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/requirements/assets
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { Badge } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+An asset represents an item of value that can be transferred via the Interledger Protocol. Assets in Rafiki are added through the Backend Admin API or the [Rafiki Admin](/v1-beta/admin/admin-user-guide/#assets) application.
+
+## Add an asset
+
+
+
+ ```graphql
+ mutation CreateAsset($input: CreateAssetInput!) {
+ createAsset(input: $input) {
+ code
+ success
+ message
+ asset {
+ id
+ code
+ scale
+ }
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "code": "USD",
+ "scale": 2
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateAssetInput`](/v1-beta/apis/graphql/backend/#definition-CreateAssetInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "createAsset": {
+ "code": "200",
+ "success": true,
+ "message": "Created Asset",
+ "asset": {
+ "id": "b3dffeda-1e0e-47d4-82a3-69b1a622eeb9",
+ "code": "USD",
+ "scale": 2
+ }
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/exchange-rates.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/exchange-rates.mdx
new file mode 100644
index 0000000000..3c35cb150e
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/exchange-rates.mdx
@@ -0,0 +1,98 @@
+---
+title: Exchange rates
+slug: v1-beta/integration/requirements/exchange-rates
+---
+
+import { Badge } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+If you plan to support cross-currency transactions, you must specify from where your Rafiki instance will fetch current exchange rates.
+
+A rate probe precedes every Interledger payment. The probe provides a quote that estimates the full cost of transferring value over the network. For a rate probe involving a cross-currency transaction to be successful, Rafiki needs to know the exchange rates for each currency that makes up the transaction.
+
+Often, it's the receiving ASE that provides the exchange rates for each ILP packet. For example, say you and your peer transact in USD. Your peer also supports MXN. If a USD payment from your side is addressed to a wallet address set up for MXN on your peer's side, then your peer would provide the USD to MXN exchange rate.
+
+## Specify your exchange rates endpoint
+
+Rafiki fetches exchange rates from your exchange rates endpoint. Set your endpoint via the `backend` service's `EXCHAGE_RATES_URL` variable. An OpenAPI specification for the endpoint is available.
+
+```bash title='Example'
+EXCHANGE_RATES_URL: http://cloud-nine-wallet/rates
+```
+
+The endpoint must accept requests and respond as follows.
+
+```bash title="Example API request"
+GET https://cloud-nine-wallet/rates
+```
+
+```bash title='Example API response'
+{
+ "base": "USD",
+ "rates": {
+ "EUR": 0.813399,
+ }
+}
+```
+
+### Response objects
+
+
+
+| Variable | Type | Description | Required |
+| -------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------- | -------- |
+| `base` | String | The asset code represented as an ISO 4217 currency code, e.g. USD | Y |
+| `rates` | Object | Object containing `` pairs, e.g. `{EUR: 0.8930}` | Y |
+| `rates.` | Number | The exchange rate given `base` and `` | Y |
+
+
+
+## Specify rate caching duration (optional)
+
+Specify how long your Rafiki instance will cache exchange rates via the `backend` service's `EXCHANGE_RATES_LIFETIME` variable or use the default setting of `15_000` ms (15 seconds).
+
+Caching improves performance as Rafiki will not need to request the rates from your endpoint for every payment.
+
+## Specify slippage (optional)
+
+As exchange rates and fees charged by connectors fluctuate, there will likely be a variance between the estimated amount provided in the quote and the actual amount required when the payment is initiated. This difference is called slippage.
+
+Set your allowed slippage rate to a value between 0 and 1 via the `backend` service's `SLIPPAGE` variable or use the default setting of `0.01` (1%).
+
+```bash title='Example'
+SLIPPAGE: 0.05
+```
+
+### Example
+
+Let's say your Rafiki instance is using the default slippage of `0.01` (1%). The rate probe that precedes a USD payment returns a quote of `$1.00`. One percent of one dollar equals one cent.
+
+If the total of the payment, inclusive of currency exchange rates and network fees, amounts to `$1.01`, the payment will be successful. If the total is `$1.02` or more, the payment will fail.
+
+Below is a minimalistic example of a successful (200) response.
+
+```json
+export function loader({ request }: LoaderFunctionArgs) {
+ const base = new URL(request.url).searchParams.get('base') || 'USD'
+
+ return json(
+ {
+ base,
+ rates: config.seed.rates[base] || {}
+ },
+ { status: 200 }
+ )
+}
+```
+
+## Environment variables
+
+
+
+| Variable | Type | Description | Required |
+| ------------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- |
+| `EXCHANGE_RATES_URL` | `backend` | Your exchange rates endpoint | Y |
+| `EXCHANGE_RATES_LIFETIME` | `backend` | The amount of time Rafiki caches exchange rates, in ms | Y |
+| `SLIPPAGE` | `backend` | The variance allowed between a quote and the actual amount required when a payment is initiated | Y |
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/grants-revoking.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/grants-revoking.mdx
new file mode 100644
index 0000000000..2a8852dcd7
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/grants-revoking.mdx
@@ -0,0 +1,192 @@
+---
+title: Viewing and revoking grants
+slug: v1-beta/integration/requirements/open-payments/grants-revoking
+---
+
+import { Badge, Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+Grants are the mechanism in Open Payments by which your account holders give permission to a client application to access their accounts and send payments on their behalf. Providing your account holders the ability to view and revoke grants is not required to implement and operate Rafiki, but allowing them to do so is critical to providing an optimal user experience.
+
+## View grants
+
+Use the `Grants` GraphQL query to look up all grants associated with a wallet address.
+
+
+
+ ```graphql
+ query Grants(
+ $after: String
+ $before: String
+ $first: Int
+ $last: Int
+ $filter: GrantFilter
+ ) {
+ grants(
+ after: $after
+ before: $before
+ first: $first
+ last: $last
+ filter: $filter
+ ) {
+ edges {
+ cursor
+ node {
+ id
+ client
+ createdAt
+ state
+ access {
+ createdAt
+ id
+ identifier
+ limits {
+ interval
+ receiveAmount {
+ assetScale
+ value
+ assetCode
+ }
+ receiver
+ debitAmount {
+ assetCode
+ assetScale
+ value
+ }
+ }
+ actions
+ type
+ }
+ }
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ hasPreviousPage
+ startCursor
+ }
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "after": null,
+ "before": null,
+ "first": null,
+ "last": null,
+ "filter": {
+ "state": {
+ "in": ["PROCESSING", "PENDING", "APPROVED", "FINALIZED"]
+ },
+ "identifier": {
+ "in": ["https://cloud-nine-wallet-backend/accounts/gfranklin"]
+ }
+ }
+ }
+ }
+ ```
+
+ For more information about this query's variables, see [`grants`](/v1-beta/apis/graphql/auth/#query-grants).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "grants": {
+ "edges": [
+ {
+ "cursor": "82637448-30d2-4242-9c85-464821dfbaf5",
+ "node": {
+ "id": "82637448-30d2-4242-9c85-464821dfbaf5",
+ "client": "https://happy-life-bank-backend/accounts/pfry",
+ "createdAt": "2025-03-27T13:48:23.615Z",
+ "state": "APPROVED",
+ "access": [
+ {
+ "createdAt": "2025-03-27T13:48:23.617Z",
+ "id": "05a5413b-7009-4ce1-949a-e0ff1b243268",
+ "identifier": "https://cloud-nine-wallet-backend/accounts/gfranklin",
+ "limits": {
+ "interval": null,
+ "receiveAmount": {
+ "assetScale": 2,
+ "value": "100",
+ "assetCode": "USD"
+ },
+ "receiver": null,
+ "debitAmount": {
+ "assetCode": "USD",
+ "assetScale": 2,
+ "value": "205"
+ }
+ },
+ "actions": [
+ "create",
+ "read",
+ "list"
+ ],
+ "type": "outgoing-payment"
+ }
+ ]
+ }
+ }
+ ],
+ "pageInfo": {
+ "endCursor": "82637448-30d2-4242-9c85-464821dfbaf5",
+ "hasNextPage": false,
+ "hasPreviousPage": false,
+ "startCursor": "82637448-30d2-4242-9c85-464821dfbaf5"
+ }
+ }
+ }
+ }
+ ```
+
+
+
+## Revoke a grant
+
+Use the `revokeGrant` GraphQL mutation to revoke a particular grant.
+
+
+
+ ```graphql
+ mutation revokeGrant($input: RevokeGrantInput!) {
+ revokeGrant(input: $input) {
+ id
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "grantId": "2117891e-4b89-42ae-984e-e0762d5888c1"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`RevokeGrantInput`](/v1-beta/apis/graphql/auth/#definition-RevokeGrantInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "revokeGrant": {
+ "id": "2117891e-4b89-42ae-984e-e0762d5888c1"
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/idp.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/idp.mdx
new file mode 100644
index 0000000000..377cdaaf74
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/idp.mdx
@@ -0,0 +1,143 @@
+---
+title: Identity provider (IdP)
+slug: v1-beta/integration/requirements/open-payments/idp
+---
+
+import { Badge, Steps } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+An identity provider (IdP) is a system or service that stores and manages user identity information, authentication, and consent. Examples of IdPs include OpenID Connect and Okta.
+
+Open Payments requires any authorization server that issues interactive grants be integrated with an IdP. Interactive grants are used to gather consent. More information about interactive grants is available [below](#interactive-grants).
+
+Responsibilities of your IdP include:
+
+- Providing an interface to gather end-user consent for a particular action
+- Sending the interaction choice (approve or deny) to the authorization server
+- Sending a request to the authorization server to finish the interaction
+- Redirecting the user after the interaction is complete
+
+:::note
+We provide Ory Kratos, a cloud-based user management system, for the identity and user management of your Rafiki Admin users. Kratos is for internal use only and **cannot** be used as your IdP for Open Payments.
+:::
+
+## Interactive grants
+
+In Open Payments, grants indicate a resource owner, such as an account holder, has given a piece of software, such as a mobile app, permission (consent) to act on their behalf.
+
+Rafiki's implementation of an Open Payments authorization server requires that consent is collected via an interactive grant before an outgoing payment request is issued. A grant is interactive when explicit interaction by a resource owner (for example, the software's end user) is required to approve or deny the grant. Tapping an _Approve_ button to authorize a payment is an example of an explicit interaction.
+
+Interactive grants can be optional for incoming payments and quotes; however, they're enabled by default in Rafiki (the `LIST_ALL_ACCESS_INTERACTION` environment variable is `true`). When a grant request includes a `list-all` action for incoming payments and quotes, the request requires interaction. The `list-all` action is used when the client asks to list resources that it did not create.
+
+If `LIST_ALL_ACCESS_INTERACTION` is `false`, you can still force interactive grants for quotes and/or incoming payments by setting the respective variables to `true`.
+
+- `QUOTE_INTERACTION`
+- `INCOMING_PAYMENT_INTERACTION`
+
+See the Open Payments documentation for more information on grant negotiation and authorization.
+
+## Authorization servers
+
+Authorization servers grant permission to clients to access the Open Payments Resource APIs. This enables clients to create incoming payments, quotes, and outgoing payments against an account holder's account.
+
+Rafiki's [auth service](/v1-beta/integration/deployment/services/auth-service) provides you with a reference implementation of an Open Payments authorization server. You can use the service as an alternative to developing your own in-house service.
+
+Rafiki's authorization server also extends an [API](#interaction-endpoints) that provides interaction endpoints for your IdP.
+
+### Environment variables
+
+The following variables must be configured for the `auth` service.
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------------------ | ---------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `IDENTITY_SERVER_URL` | `auth.identityServer.domain` | N/A | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
+| `IDENTITY_SERVER_SECRET` | `auth.identityServer.secret` | N/A | A shared secret between the authorization server and the IdP server; the authorization server uses the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](#x-idp-secret-header) header. |
+| `INCOMING_PAYMENT_INTERACTION` | `auth.interaction.incomingPayment` | `false` | Indicates whether incoming payments grant requests are interactive. |
+| `INTERACTION_EXPIRY_SECONDS` | `auth.interactionExpirySeconds` | `600` | The time in seconds for which a user can interact with a grant request |
+| `INTERACTION_PORT` | `auth.port.interaction` | `3009` | The port number for the [interaction endpoints](#interaction-endpoints) |
+| `LIST_ALL_ACCESS_INTERACTION` | N/A | `true` | Specifies whether grant requests including a `list-all` action should require interaction. In these requests, the client asks to list resources that they themselves did not create. |
+| `QUOTE_INTERACTION` | `auth.interaction.quote` | `false` | When `true`, quote grants are interactive. |
+
+
+
+## Interaction endpoints
+
+The authorization server provided by Rafiki's `auth` service extends an API for an IdP server to use after a pending grant request is created.
+
+Each interaction with an endpoint is identified by an `id` and a `nonce`. Both are provided as query parameters when the authorization server redirects to the IdP server.
+
+The endpoints are tied to the auth server URL. For example, if your auth server URL is `https://auth.wallet.example.com`, then calling the `/interact/{id}/{nonce}` endpoint to start a user interaction session would look as follows:
+
+```
+https://auth.wallet.example.com/interact/{id}/{nonce}
+```
+
+### Interaction endpoints
+
+The endpoints are called in the sequence listed below.
+
+
+
+| Method | Endpoint | Purpose | Called by | Publicly exposed |
+| ----------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- | -------------------- | ---------------- |
+| | `/interact/{id}/{nonce}` | [Start user interaction session](#start-user-interaction-session) | Open Payments client | Yes |
+| | `/grant/{id}/{nonce}` | [Look up grant information](#look-up-grant-information) | Identity provider | No |
+| | `/grant/{id}/{nonce}/{choice}` | [Accept or reject grant](#accept-or-reject-grant) | Identity provider | No |
+| | `/interact/{id}/{nonce}/finish` | [Finish user interaction](#finish-interaction) | Identity provider | Yes |
+| | `/interact/{id}/{nonce}` | [Continue grant](#continue-grant) | Open Payments client | Yes |
+
+
+
+We also provide an OpenAPI specification that describes the endpoints. Note that the _Continue grant_ endpoint is not included in the spec because it's part of the Open Payments Auth Server API.
+
+#### Start user interaction session
+
+Called by the client to establish an interactive session with the authorization server. The authorization server automatically redirects the request, via the URL defined in the `IDENTITY_SERVER_URL` variable, to your IdP consent screen.
+
+#### Look up grant information
+
+Called by the IdP server to retrieve a list of access rights, requested by the client, from the authorization server. The request is secured with an [`x-idp-secret`](#x-idp-secret-header) header. The access rights are presented to the client's end-user on the consent screen. The authorization server's response is served on your configured `INTERACTION_PORT`.
+
+#### Accept or reject grant
+
+The IdP server communicates the choice made by the end-user on the consent screen (accept/reject) to the authorization server. The request is secured with an [`x-idp-secret`](#x-idp-secret-header) header. The authorization server responds to the IdP server, acknowledging that it received the request.
+
+#### Finish interaction
+
+Called by the IdP server to end the interaction. If a `finish` URI was provided in the original grant initialization request, the authorization server redirects the user to that URI.
+
+The `result` query parameter in the response indicates the success or failure of the grant authorization. The following are examples of the possible response types.
+
+
+
+| Response | Description | Example |
+| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
+| Rejected | The end-user rejected the interaction | `?result=grant_rejected` |
+| Invalid | The grant was not in a state where it could be accepted or rejected (for example, the grant was already approved) | `?result=grant_invalid` |
+| Success | The grant was successful with the following returned in the response:
A hash representing the SHA-256 hash of values provided by the client in the grant initialization request (`interact.finish.nonce`), and the values in the response returned from the authorization server (`interact.finish`).
The `interact_ref` that identifies the interaction on the authorization server alongside the hash
The URI of the grant initialization request (for example, `https://www.auth-server.com`)
+
+When successful, the SHA-256 hash of the interaction is sent in the response to the client, along with an `interact_ref` that identifies the interaction on the authorization server and the URI of the grant initialization request. The client must verify the hash before the client requests the grant to continue.
+
+#### Continue grant
+
+The client requests a grant from the authorization server for an accepted interaction. The authorization server responds with an access token.
+
+## X-idp-secret header
+
+The `x-idp-secret` header is specific to Rafiki's authorization server and is used for requests to the following endpoints:
+
+- `GET /grant/:id/:nonce`
+- `POST /grant/:id/:nonce/accept`
+- `POST /grant/:id/:nonce/reject`
+
+The header's purpose is to secure communications between the IdP and the authorization server. Its value should be a shared secret known to both entities. When the IdP server sends requests to the authorization server, the IdP must provide the secret via this header.
+
+:::note
+If you're running your own authorization server rather than using the server provided by Rafiki, you can add security in any way you see fit. You aren't required to use the `x-idp-secret` header.
+:::
+
+To set up the header, set the `IDENTITY_SERVER_SECRET` variable to a value that is also used to configure your IdP server's requests to the authorization server.
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/wallet-keys.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/wallet-keys.mdx
new file mode 100644
index 0000000000..7c46c7a7ed
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/open-payments/wallet-keys.mdx
@@ -0,0 +1,151 @@
+---
+title: Wallet address keys
+slug: v1-beta/integration/requirements/open-payments/wallet-keys
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+
+Creating a public-private key pair for each wallet address is not required when integrating with Rafiki.
+
+You only need to create key pairs for wallet addresses if you want to allow your account holders to use/be Open Payments clients under their wallet addresses. For more information, review the Open Payments documentation about clients and client keys.
+
+## Create a wallet address key pair
+
+Use the `createWalletAddressKey` GraphQL mutation to create a key pair and associate it with a wallet address.
+
+
+
+ ```graphql
+ mutation CreateWalletAddressKey($input: CreateWalletAddressKeyInput!) {
+ createWalletAddressKey(input: $input) {
+ code
+ message
+ success
+ walletAddressKey {
+ id
+ walletAddressId
+ revoked
+ jwk {
+ alg
+ crv
+ kid
+ kty
+ x
+ }
+ createdAt
+ }
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "jwk": {
+ "kid": "keyid-97a3a431-8ee1-48fc-ac85-70e2f5eba8e5",
+ "x": "ubqoInifJ5sssIPPnQR1gVPfmoZnJtPhTkyMXNoJF_8",
+ "alg": "EdDSA",
+ "kty": "OKP",
+ "crv": "Ed25519"
+ },
+ "walletAddressId": "695e7546-1803-4b45-96b6-6a53f4082018"
+ }
+ }
+ ```
+
+ The request is a standard request to create a JSON Web Key (JWK), which is a JSON data structure that represents a cryptographic key. Section 4 of the JWK specification describes the format and associated parameters `kty`, `alg`, and `kid`. Section 6 of the JSON Web Algorithms (JWA) specification describes the cryptographic algorithm for the keys and associated parameters `kty`, `crv`, and `x`.
+
+ Open Payments requires the following values.
+
+
+
+ | Parameter | Required value | Description |
+ | --------- | -------------- | ----------------------------------------------------------------------------- |
+ | `alg` | `EdDSA` | The algorithm used to generate the key pair |
+ | `kty` | `OKP` | The key type identifying the cryptographic algorithm family used with the key |
+ | `crv` | `Ed25519` | The cryptographic curve used with the key |
+
+
+
+ Additionally, the request must contain the `walletAddressId` of the wallet address that the key pair will be associated with.
+
+
+
+
+ ```json
+ {
+ "data": {
+ "createWalletAddressKey": {
+ "code": "200",
+ "message": "Added Key To Wallet Address",
+ "success": true,
+ "walletAddressKey": {
+ "id": "f2953571-f10c-44eb-ab41-4450a7ad6771",
+ "walletAddressId": "695e7546-1803-4b45-96b6-6a53f4082018",
+ "revoked": false,
+ "jwk": {
+ "alg": "EdDSA",
+ "crv": "Ed25519",
+ "kid": "keyid-97a3a431-8ee1-48fc-ac85-70e2f5eba8e5",
+ "kty": "OKP",
+ "x": "ubqoInifJ5sssIPPnQR1gVPfmoZnJtPhTkyMXNoJF_8"
+ },
+ "createdAt": "2023-03-03T09:26:41.424Z"
+ }
+ }
+ }
+ }
+ ```
+
+
+
+## Revoke a wallet address key
+
+Use the `revokeWalletAddressKey` GraphQL mutation to revoke a public key associated with a wallet address. Open Payments requests using this key for request signatures will be denied going forward.
+
+
+
+ ```graphql
+ mutation RevokeWalletAddressKey($input: RevokeWalletAddressKeyInput!) {
+ revokeWalletAddressKey(input: $input) {
+ walletAddressKey {
+ id
+ revoked
+ walletAddressId
+ createdAt
+ }
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "id": "e7532552-cff9-4ffe-883e-56613d3ae611"
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "data": {
+ "revokeWalletAddressKey": {
+ "walletAddressKey": {
+ "id": "f2953571-f10c-44eb-ab41-4450a7ad6771",
+ "revoked": true,
+ "walletAddressId": "695e7546-1803-4b45-96b6-6a53f4082018",
+ "createdAt": "2023-03-03T09:26:41.424Z"
+ }
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/overview.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/overview.mdx
new file mode 100644
index 0000000000..a97293cd70
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/overview.mdx
@@ -0,0 +1,74 @@
+---
+title: Requirements overview and integration checklist
+slug: v1-beta/integration/requirements/overview
+---
+
+import { Badge } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+You must meet the following requirements before you deploy Rafiki to a production environment.
+
+## Be an account servicing entity
+
+An account servicing entity (ASE) is an entity that provides and maintains payment accounts for its customers and is regulated within the jurisdictions it operates. Examples of ASEs include banks, digital wallet providers, and mobile money providers. The [account servicing entity](/v1-beta/overview/concepts/account-servicing-entity) page provides examples of an ASE's responsibilities and obligations.
+
+Rafiki should not be used in production by non-regulated entities.
+
+## Support at least one asset
+
+You must set up Rafiki to support at least one asset. An asset in Rafiki represents an item of value that can be transferred via the Interledger Protocol. Since the Interledger Protocol aims to create an internet of value, it allows for the transfer of any asset, not just currency. In practice, however, assets are usually denominated in a currency (fiat or branded currencies).
+
+[Set up your assets](/v1-beta/integration/requirements/assets)
+
+## Associate each user-facing payment account with a wallet address
+
+A wallet address is a publicly shareable standardized ID for a payment account. Each payment account belonging to your users (for example, your customers) must have at least one associated wallet address for the account to be able to send and/or receive payments via Open Payments and Interledger.
+
+[Set up your wallet addresses](/v1-beta/integration/requirements/wallet-addresses)
+
+## Expose a webhook endpoint and react to events accordingly
+
+The main communication channel between you and your Rafiki instance is composed of the Backend Admin API and a set of webhook events. Most of these events require you to interact with Rafiki. You must expose a webhook endpoint that listens for events dispatched by Rafiki, then react accordingly (for example, deposit or withdraw liquidity).
+
+[Specify your webhook endpoint and learn how to handle each event](/v1-beta/integration/requirements/webhook-events)
+
+## Expose an exchange rate endpoint
+
+If you plan to support cross-currency transactions, you must specify from where your Rafiki instance will fetch current exchange rates. Exchange rates are calculated as part of a payment's quote, which estimates the full cost of transferring value over the network.
+
+[Specify your exchange rate endpoint](/v1-beta/integration/requirements/exchange-rates)
+
+## Define your sending fees
+
+You have the option to charge a sending fee, on top of any estimated network fees, for facilitating transfers. Each asset you support can have a different fee structure.
+
+[Define your sending fees](/v1-beta/integration/requirements/sending-fees)
+
+## Add a peer to enable Interledger payments
+
+You must add one or more peers if you intend to enable Interledger payments on your accounts. A peer is another ASE that you connect with via Interledger and is likely running their own Rafiki instance.
+
+If you are using Rafiki solely for transfers between accounts on your ledger, peers are not required.
+
+[Add peers to your Rafiki instance](/v1-beta/integration/requirements/peers)
+
+## Integrate with an identity provider (IdP)
+
+An identity provider (IdP) is a system or service that stores and manages user identity information, authentication, and consent. Examples of IdPs include OpenID Connect and Okta.
+
+You must integrate with an IdP if you plan to use the authorization server provided through Rafiki's auth service. The authorization server requires consent be collected via an interactive grant before an outgoing payment request is issued. The purpose of the IdP is to handle the authentication and consent required to authorize the interactive grant request.
+
+[Integrate Rafiki with your IdP](/v1-beta/integration/requirements/open-payments/idp)
+
+## Integration checklist
+
+Ensure you've completed the following tasks before you deploy Rafiki to a production environment and join the Interledger network.
+
+- [ ] You are a licensed financial account servicing entity within the jurisdictions you operate in
+- [ ] You have added at least one asset, either through the Backend Admin API or the Rafiki Admin app
+- [ ] You have implemented a strategy for creating wallet addresses for your account holders
+- [ ] You have set up your webhook endpoint and understand how to handle each webhook event
+- [ ] You have set up your exchange rates endpoint
+- [ ] You have defined the sending fee you will charge, if any, for each asset, either through the Backend Admin API or the Rafiki Admin app
+- [ ] If supporting Open Payments outgoing payments, you have integrated with an IdP and configured the user consent screen and interaction flow
+- [ ] Your admin services are secured from external access
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/peers.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/peers.mdx
new file mode 100644
index 0000000000..40a4fbbc2d
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/peers.mdx
@@ -0,0 +1,263 @@
+---
+title: Peers
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/requirements/peers
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+import { Badge } from '@astrojs/starlight/components'
+
+To join the Interledger network and be able to send and receive payments, you must add one or more peers to your Rafiki instance. Peering establishes the connections needed for your Rafiki instance to interact with another account servicing entity (ASE). The purpose of this guide is to help you set up and manage peers.
+
+While this guide focuses on the conceptual and technical steps of adding and managing peers via the Backend Admin API, the Rafiki Admin application offers the same capabilities in a user-friendly interface.
+
+Refer to the [Rafiki Admin user guide](/v1-beta/admin/admin-user-guide#peers) for detailed instructions and examples of creating and managing peers through the application.
+
+:::tip
+Whether you are using the Backend Admin API or the Rafiki Admin application, the underlying configurations and requirements remain the same. Choose the interface that best suits your individual workflow.
+:::
+
+## Perform prerequisites
+
+:::note
+Peering is not required unless you want to participate in transactions with another ASE on the Interledger network. For foundational peering concepts, refer to the Peers section of [Interledger Concepts](/v1-beta/overview/concepts/interledger/#peers).
+:::
+
+Before adding a peer, you and the account servicing entity you intend to peer with must both:
+
+### Run an Interledger connector
+
+While you and your peer may run any implementation of an [Interledger connector](/v1-beta/integration/deployment/services/backend-service#interledger-connector) such as the TypeScript implementation, it is recommended to use Rafiki.
+
+### Agree on an asset
+
+Both you and your peer must agree on an [asset](/v1-beta/overview/concepts/accounting#assets) for the peering relationship. You can set up multiple peering relationships with the same peer based on different assets. At least one asset shared by you and your peer must be added to your Rafiki instance prior to setting up the peering relationship. For more information, refer to [Assets](/v1-beta/integration/requirements/assets/).
+
+### Exchange static Interledger (ILP) addresses
+
+Your ILP address is self-assigned during Rafiki setup and stored locally as the `ILP_ADDRESS` environment variable for the `backend` service.
+
+### Communicate a connection endpoint
+
+The connection endpoint will be a url that the other peer will send packets to.
+
+### Exchange auth tokens for the connection endpoint
+
+Incoming `authtokens` allow you to authenticate that packets sent from your peer originated from your peer's Interledger connector and were not tampered en route. The outgoing `authtoken` allows your peer to authenticate that received packets originated from your Interledger connector and were not tampered with en route. The use of auth tokens is not required when [autopeering with the Test Network](/v1-beta/integration/playground/autopeering).
+
+### Agree on a settlement mechanism
+
+The settlement mechanism you both agree to use is what facilitates the transfer of actual funds between you and your peer. Neither Interledger nor Rafiki provide a settlement mechanism.
+
+## Perform optional prerequisites
+
+### Deposit an initial liquidity for your peer
+
+While you may deposit an `initiaLiquidity` for your peer, you can deposit liquidity later using the `depositPeerLiquidity` mutation.
+
+### Define a maxPacketAmout value
+
+The `maxPacketAmount` specifies the maximum packet size you are willing to accept from the peer. Your peer's `maxPacketAmount` value does not need to match, as this value is independently set by each ASE. If omitted, payments will not be broken into smaller packets.
+
+## Set up peering in Rafiki
+
+The basic workflow of setting up a peering relationship starts with adding the agreed upon asset and then adding a peer.
+
+### Add an asset
+
+As mentioned in the prerequisites, you must add an asset to your Rafiki instance before creating a peering relationship. To learn how to add an asset, refer to [Assets](/v1-beta/integration/requirements/assets/).
+
+### Add a peer
+
+
+
+ ```graphql
+ mutation CreatePeer($input: CreatePeerInput!) {
+ createPeer(input: $input) {
+ code
+ success
+ message
+ peer {
+ id
+ asset {
+ code
+ scale
+ }
+ staticIlpAddress
+ name
+ }
+ }
+ }
+ ```
+
+
+
+ ```json wrap
+ {
+ "input": {
+ "staticIlpAddress": "g.othergreatwallet",
+ "name": "The Other Great Wallet"
+ "http": {
+ "incoming": {"authTokens": ["mytoken"]},
+ "outgoing": {"endpoint": "ilp.othergreatwallet.com", "authToken": "theirtoken"}
+ },
+ "assetId": "INSERT_ASSET_ID",
+ "initialLiquidity":
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreatePeerInput`](/v1-beta/apis/graphql/backend/#definition-CreatePeerInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "createPeer": {
+ "code": "200",
+ "success": true,
+ "message": "Created ILP Peer",
+ "peer": {
+ "id": "480ef339-7842-4501-a905-923fc1339cef",
+ "asset": {
+ "code": "USD",
+ "scale": 2
+ },
+ "staticIlpAddress": "g.othergreatwallet",
+ "name": "The Other Great Wallet"
+ }
+ }
+ }
+ }
+ ```
+
+
+
+## Manage peers
+
+Once a peer has been added to your Rafiki instance, there is minimal ongoing management required. Most peer interactions focus on monitoring liquidity and ensuring smooth payment flows. In rare cases, you may need to update a peer's configuration due to changes in their technical details or remove a peer created in error, as long as no payments have been exchanged. These actions help ensure your Rafiki instance stays up to date with operational changes.
+
+### Edit a peer
+
+Occasionally, you may need to adjust peering configurations or address any changes communicated by the peer. Some examples include updating new endpoints or tokens, technical settings like the maximum packet amount, or peer liquidity thresholds.
+
+In this example we will update the peer we just created. Rather than change any of the peering details, we can add some optional details that we didn't include when we created the peer. We will define the `maxPacketAmount` and the `liquidityThreshold`.
+
+
+
+ ```graphql
+ mutation UpdatePeer($input: UpdatePeerInput!) {
+ updatePeer(input: $input) {
+ peer {
+ id
+ name
+ http {
+ outgoing {
+ authToken
+ endpoint
+ }
+ }
+ maxPacketAmount
+ liquidityThreshold
+ }
+ }
+ }
+ ```
+
+
+
+ The input object for the update operation only requires that the `id` is present. All other variables are optional. For this example we will include the required `id` variable, as well as the optional variables of the fields we wish to update. In this case, `maxPacketAmount` and `liquidityThreshold`..
+
+ ```json
+ {
+ "input": {
+ "id": "480ef339-7842-4501-a905-923fc1339cef",
+ "maxPacketAmount": 1000,
+ "liquidityThreshold": 100
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`UpdatePeerInput`](/v1-beta/apis/graphql/backend/#definition-UpdatePeerInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "updatePeer": {
+ "code": "200",
+ "success": true,
+ "message": "Updated ILP Peer",
+ "peer": {
+ "id": "480ef339-7842-4501-a905-923fc1339cef",
+ "name": "The Other Great Wallet",
+ "http": {
+ "outgoing": {
+ "authToken": "test",
+ "endpoint": "http://peering-test:3002"
+ }
+ },
+ "maxPacketAmount": 1000,
+ "liquidityThreshold": 100
+ }
+ }
+ }
+ }
+ ```
+
+
+
+### Delete a peer
+
+Deleting a peer is an action that removes a peer from your Rafiki instance. There are specific rules and considerations to keep in mind before starting this irreversible operation.
+
+You can only delete a peer if no payments were sent to or received from that peer. This ensures that historical payment records are preserved. If you attempt to delete a peer with payment history, the backend throws an error, preventing the deletion.
+
+Deleting a peer is useful in situations where there were configuration errors when the peer was first created like an incorrect auth token or ILP address.
+
+:::danger
+Deleting a peer is permanent and cannot be reversed. If you delete a peer in error, you must create another new peer.
+:::
+
+
+
+ ```graphql
+ mutation DeletePeer($input: DeletePeerInput!) {
+ deletePeer(input: $input) {
+ success
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "input": {
+ "id": "480ef339-7842-4501-a905-923fc1339cef"
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`DeletePeerInput`](/v1-beta/apis/graphql/backend/#definition-DeletePeerInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "deletePeer": {
+ "success": true
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/sending-fees.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/sending-fees.mdx
new file mode 100644
index 0000000000..58a8f817e4
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/sending-fees.mdx
@@ -0,0 +1,78 @@
+---
+title: Sending fees
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/requirements/sending-fees
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { Badge } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+You have the option to charge sending fees, on top of any estimated network fees, for facilitating transfers. Each asset you support can have a different fee structure and you can specify both fixed and variable fees per asset. The fee amount is added on top of the quote that is generated after the ILP rate probe completes. You can define sending fees through the Backend Admin API or the [Rafiki Admin](/v1-beta/admin/admin-user-guide/#edit-asset) application.
+
+## Set sending fees
+
+
+
+ ```graphql
+ mutation SetFee($input: SetFeeInput!) {
+ setFee(input: $input) {
+ code
+ success
+ message
+ fee {
+ id
+ assetId
+ type
+ fixed
+ basisPoints
+ createdAt
+ }
+ }
+ }
+ ```
+
+
+
+ For this example, let's assume your asset scale is 2. You'll charge a fixed fee of 100 ($1.00) and a variable fee of 100 (1%).
+
+ ```json
+ {
+ "input": {
+ "assetId": "14863f6f-4bda-42ef-8715-bf4762898af8",
+ "type": "SENDING",
+ "fee": {
+ "fixed": 100,
+ "basisPoints": 100
+ }
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`SetFeeInput`](/v1-beta/apis/graphql/backend/#definition-SetFeeInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "setFee": {
+ "code": "200",
+ "success": true,
+ "message": "Fee set",
+ "fee": {
+ "id": "140fd9c0-8f14-4850-9724-102f04d97e69",
+ "assetId": "14863f6f-4bda-42ef-8715-bf4762898af8",
+ "type": "SENDING",
+ "fixed": "100",
+ "basisPoints": 100,
+ "createdAt": "2023-09-13T14:59:53.435Z"
+ }
+ }
+ }
+ }
+ ```
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/wallet-addresses.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/wallet-addresses.mdx
new file mode 100644
index 0000000000..aff22e360d
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/wallet-addresses.mdx
@@ -0,0 +1,119 @@
+---
+title: Wallet addresses
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/requirements/wallet-addresses
+---
+
+import { Tabs, TabItem } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+Each payment account belonging to your users (for example, your customers) must have at least one associated wallet address for the account to be able to send and receive payments over Interledger and Open Payments. A wallet address serves as a publicly shareable standardized ID for a payment account.
+
+:::note[Wallet address requirements]
+
+- Your Rafiki instance must be set up with at least one asset before wallet addresses can be created as each wallet address must have an asset assigned to it.
+- Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.
+ :::
+
+## Create wallet addresses
+
+There are a few ways in which you can create wallet addresses.
+
+- [Through a script](#create-wallet-addresses-through-a-script)
+- [In response to the `wallet_address.not_found` webhook event](#create-wallet-addresses-in-response-to-a-webhook-event)
+- [In the Rafiki Admin app](/v1-beta/admin/admin-user-guide#create-wallet-address)
+
+### Create wallet addresses through a script
+
+Writing your own script that loops through your list of account is one way to batch create wallet addresses for your existing account holders.
+
+Ensure your script calls the `createWalletAddress` GraphQL mutation.
+
+
+
+ ```graphql
+ mutation CreateWalletAddress($input: CreateWalletAddressInput!) {
+ createWalletAddress(input: $input) {
+ code
+ success
+ message
+ walletAddress {
+ id
+ createdAt
+ publicName
+ url
+ asset {
+ code
+ id
+ scale
+ }
+ }
+ }
+ }
+ ```
+
+ We strongly recommend you store at least the `walletAddress.id` in your internal database to be able to reference the account and wallet address later.
+
+
+
+
+ ```json
+ {
+ "input": {
+ "assetId": "0ddc0b7d-1822-4213-948e-915dda58850b",
+ "publicName": "Sarah Marshall",
+ "url": "https://example.wallet.com/sarah",
+ "additionalProperties": [
+ {
+ "key": "iban",
+ "value": "NL93 8601 1117 947",
+ "visibleInOpenPayments": false
+ },
+ {
+ "key": "nickname",
+ "value": "S Mar",
+ "visibleInOpenPayments": true
+ }
+ ]
+ }
+ }
+ ```
+
+ For more information about this mutation's input object, see [`CreateWalletAddressInput`](/v1-beta/apis/graphql/backend/#definition-CreateWalletAddressInput).
+
+
+
+
+ ```json
+ {
+ "data": {
+ "createWalletAddress": {
+ "code": "200",
+ "success": true,
+ "message": "Created wallet address",
+ "walletAddress": {
+ "id": "695e7546-1803-4b45-96b6-6a53f4082018",
+ "createdAt": "2023-03-03T09:07:01.107Z",
+ "publicName": "Sarah Marshall",
+ "url": "https://example.wallet.com/sarah",
+ "asset": {
+ "id": "0ddc0b7d-1822-4213-948e-915dda58850b",
+ "code": "USD",
+ "scale": 2
+ }
+ }
+ }
+ }
+ }
+ ```
+
+
+
+### Create wallet addresses in response to a webhook event
+
+The [`wallet_address.not_found`](/v1-beta/integration/requirements/webhook-events#wallet-address-not-found) event fires when a wallet address is requested through the Open Payments Get Wallet Address API, but Rafiki can't find the address.
+
+When you receive the event, look up the associated account in your system, then call the `createWalletAddress` mutation to create a wallet address for the account.
+
+The mutation and example JSON request/response is the same as what's given [above](#create-wallet-addresses-through-a-script).
diff --git a/packages/documentation/src/content/docs/v1-beta/integration/requirements/webhook-events.mdx b/packages/documentation/src/content/docs/v1-beta/integration/requirements/webhook-events.mdx
new file mode 100644
index 0000000000..eb27d7cfe7
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/integration/requirements/webhook-events.mdx
@@ -0,0 +1,583 @@
+---
+title: Webhook events
+tableOfContents:
+ maxHeadingLevel: 4
+slug: v1-beta/integration/requirements/webhook-events
+---
+
+import { Badge, Tabs, TabItem, Steps } from '@astrojs/starlight/components'
+import { Mermaid, LinkOut } from '@interledger/docs-design-system'
+
+The main communication channel between you and your Rafiki instance is composed of the Backend Admin API and a set of webhook events.
+
+Most events require you to interact with Rafiki to provide wallet address information or manage (deposit or withdraw) liquidity. This page describes how you should handle each webhook event.
+
+:::note
+Rafiki doesn't hold _user_ account balances. Instead, Rafiki keeps track of the liquidity within your instance's asset, peer, and payment accounts within its own database.
+:::
+
+## Specify your webhook endpoint
+
+For Rafiki to notify you about webhook events, you must expose a webhook endpoint that listens for the events dispatched by Rafiki. These events notify your system of time-sensitive status updates, warnings, and errors so that you can react accordingly.
+
+When an event occurs, the [`backend`](/v1-beta/integration/deployment/services/backend-service) service makes a request to your configured webhook endpoint. The `backend` service expects a `200` status in return.
+
+
+
+| Variable | Type | Description |
+| ------------- | --------- | ------------------------------------------------------------------- |
+| `WEBHOOK_URL` | `backend` | The endpoint to where requests are made when a webhook event occurs |
+
+
+
+## Webhook event request body
+
+Each webhook event is sent as a JSON payload with the following structure in the request body. The parameters within the `data` object vary depending on the event.
+
+
+
+| Attribute | Type | Description | Required |
+| --------- | ------ | --------------------------------------------------- | -------- |
+| `id` | String | UUID for the event | Y |
+| `type` | Enum | The `EventType` | Y |
+| `data` | Object | Additional data that coincides with the `EventType` | Y |
+
+
+
+:::tip[Duplicate events]
+The `id` in the webhook event payload is unique. Your system can use the ID to determine whether the event has was previously received, preventing duplicate event processing.
+:::
+
+
+ Expand for example JSON payloads
+
+
+
+ ```json
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "type": "incoming_payment.created",
+ "data": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "walletAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "client": "string",
+ "completed": true,
+ "incomingAmount": "string",
+ "receivedAmount": "string",
+ "metadata": {
+ "additionalProp1": {}
+ },
+ "createdAt": "2024-08-29T08:13:08.966Z",
+ "updatedAt": "2024-08-29T08:13:08.966Z",
+ "expiresAt": "2024-08-29T08:13:08.966Z"
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "type": "outgoing_payment.created",
+ "data": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "walletAddressId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "client": "string",
+ "state": "FUNDING",
+ "receiver": "https://example.com/",
+ "debitAmount": "string",
+ "sentAmount": "string",
+ "metadata": {
+ "additionalProp1": {}
+ },
+ "createdAt": "2024-08-29T11:07:56.090Z",
+ "updatedAt": "2024-08-29T11:07:56.090Z",
+ "expiresAt": "2024-08-29T11:07:56.090Z",
+ "error": "string",
+ "stateAttempts": 0,
+ "balance": "string",
+ "peerId": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "type": "asset.liquidity_low",
+ "data": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "asset": {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "code": "string",
+ "scale": 0
+ },
+ "liquidityThreshold": "string",
+ "balance": "string"
+ }
+ }
+ ```
+
+
+
+ ```json
+ {
+ "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
+ "type": "wallet_address.not_found",
+ "data": {
+ "walletAddressUrl": "string"
+ }
+ }
+ ```
+
+
+
+
+
+We provide an OpenAPI specification for the webhook events fired by Rafiki.
+
+Additionally, the [Local Playground](/v1-beta/integration/playground/overview) contains example payloads in the Bruno collection you can use to test your webhook service integration.
+
+## Verify webhook signatures
+
+To protect your endpoint from unauthorized or spoofed requests, Rafiki supports an optional, but highly recommended, webhook signature verification process. By enabling signature verification, you can ensure that webhook requests are genuinely from Rafiki, and have not been tampered with.
+
+Each webhook request includes a `Rafiki-Signature` header with a timestamp, version, and signature digest. If your instance is configured with both the `SIGNATURE_SECRET` (to generate the signature) and the `SIGNATURE_VERSION` (to set the version, defaults to v1) environment variables, you can verify the authenticity of each webhook request using the steps below.
+
+### Extract the timestamp and signature from the header
+
+The `Rafiki-Signature` header in each webhook request has the following format:
+
+```bash title="Rafiki-Signature header"
+Rafiki-Signature: t=, v=
+```
+
+- `t=`: The UNIX timestamp (in seconds) when the signature was generated.
+- `v=`: The versioned HMAC SHA-256 signature digest. The default version is `v1`.
+
+### Prepare the signed payload string
+
+To recreate the signed payload string, concatenate the following.
+
+- The timestamp extracted from the header
+- A period (.) character
+- The actual JSON payload from the request body, containing the `id`, `type`, and `data` attributes
+
+This string format is essential for accurate signature validation.
+
+### Generate the expected signature
+
+Use HMAC SHA-256 with the `SIGNATURE_SECRET` environment variable as the key and the signed payload string as the message.
+
+### Compare the signatures
+
+Finally, compare the signature in the header to the expected signature you generated. For security, use a constant-time comparison function to prevent timing attacks.
+
+### Example
+
+Below is an example in JavaScript to verify Rafiki's webhook signature:
+
+```js title="Verify webhook signature example"
+function verifyWebhookSignature(request: Request): boolean {
+ const signatureParts = request.headers['Rafiki-Signature'].split(', ')
+ const timestamp = signatureParts[0].split('=')[1]
+ const signatureVersionAndDigest = signatureParts[1].split('=')
+ const signatureVersion = signatureVersionAndDigest[0].replace('v', '')
+ const signatureDigest = signatureVersionAndDigest[1]
+ if (signatureVersion !== config['SIGNATURE_VERSION']) {
+ return false
+ }
+ const payload = `${timestamp}.${canonicalize(request.body)}`
+ const hmac = createHmac('sha256', config['SIGNATURE_SECRET'])
+ hmac.update(payload)
+ const digest = hmac.digest('hex')
+ return digest === signatureDigest
+}
+```
+
+## Event handling
+
+### Asynchronous handling
+
+If requests to credit/debit user accounts are lengthy processes, we recommend using a worker to process received events. The worker allows the server to process events at a rate suitable for your system and reduces the number of failed/retried events since your event listener can immediately reply with a successful `200` status.
+
+### Error handling
+
+If a non-200 status is returned, indicating an error, or the request times out, Rafiki retries the webhook request at increasing intervals until a `200` status is returned. The first retry occurs after 10 seconds. Additional retries occur after 20 more seconds, then after 30 more seconds, and so on.
+
+
+
+| Variable | Type | Description |
+| ------------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
+| `WEBHOOK_TIMEOUT` | `backend` | The amount of time, in milliseconds, after which a webhook request times out |
+| `WEBHOOK_MAX_RETRY` | `backend` | The maximum number of retries for a webhook event when a non-200 status is returned or if the request timed out |
+
+
+
+## Webhook events
+
+### Incoming payments
+
+
+
+| Event type | Description |
+| ----------------------------------------------------------- | --------------------------------------------------------------------------------- |
+| [`incoming_payment.created`](#incoming-payment-created) | An incoming payment was created |
+| [`incoming_payment.completed`](#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds |
+| [`incoming_payment.expired`](#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds |
+
+
+
+#### Incoming payment created
+
+
+ Expand for event sequence
+
+ >ASE: Fires incoming_payment.created event to webhook endpoint
+ ASE->>ASE: No action required
+
+`}
+/>
+
+
+
+The `incoming_payment.created` event indicates an incoming payment was created. At this point, the incoming payment has not received any funds.
+
+The incoming payment either completes or expires.
+
+#### Incoming payment completed
+
+
+ Expand for event sequences
+
+{' '}
+
+{' '}
+
+ An incoming payment
+of $10 was completed.
+
+ >ASE: Fires incoming_payment.completed event to webhook endpoint, receivedAmount: $10
+ ASE->>R: Backend Admin API call: createIncomingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Credit recipient's account with $10
+
+`}
+/>
+
+{' '}
+
+{' '}
+
+ An incoming payment
+of $10 was completed.
+
+ >ASE: Fires incoming_payment.completed event to webhook endpoint, receivedAmount: $10
+ ASE->>R: Backend Admin API call: createIncomingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Credit recipient's account with $10
+ ASE->>R: Backend Admin API call: postLiquidityWithdrawal
+ R-->>ASE: success: true
+ R->>R: Two-phase transfer completed
+
+`}
+/>
+
+
+
+The `incoming_payment.completed` event indicates the payment completed either automatically or manually, and that any funds received into the incoming payment should be withdrawn and then credited to the recipient's account on your ledger.
+
+#### Incoming payment expired
+
+
+ Expand for event sequence
+ $2.55 was received before the payment expired. The recipient is thus credited with $2.55.
+
+ >ASE: Fires incoming_payment.expired event to webhook endpoint, receivedAmount: $2.55
+ ASE->>R: Backend Admin API call: createIncomingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Credit recipient's account with $2.55
+
+`}
+/>
+
+
+
+The `incoming_payment.expired` event will only fire if funds were received for the incoming payment. The event signals the end of any additional payments.
+
+The primary use case for this event is to know when a streaming payment, such as one supported through Web Monetization, has expired. In response to the event, any funds already received for the payment should be withdrawn and credited to the recipient's account on your ledger.
+
+:::note
+In some scenarios, a sender may not have specified an `incomingAmount` when the incoming payment was created. Receiving an `incoming_payment.expired` event indicates that no further payments are expected.
+:::
+
+### Outgoing payments
+
+
+
+| Event type | Description |
+| ----------------------------------------------------------- | -------------------------------------------------- |
+| [`outgoing_payment.created`](#outgoing-payment-created) | An outgoing payment has been created |
+| [`outgoing_payment.completed`](#outgoing-payment-completed) | An outgoing payment has completed |
+| [`outgoing_payment.failed`](#outgoing-payment-failed) | An outgoing payment partially or completely failed |
+
+
+
+#### Outgoing payment created
+
+
+ Expand for event sequence
+ An outgoing payment for $12 was created.
+
+ >ASE: Fires outgoing_payment.created event to webhook endpoint, debitAmount: $12
+ ASE->>ASE: Checks that sender's account has sufficient funds
+ alt Account has sufficient funds
+ ASE->>ASE: Put hold of $12 on sender's account
+ ASE->>R: Backend Admin API call: depositOutgoingPaymentLiquidity
+ R-->>ASE: success: true
+ else Account has insufficient funds
+ ASE->>R: Backend Admin API call: cancelOutgoingPayment, Reason: insufficient funds
+ R-->>ASE: success: true
+ end
+
+`}
+/>
+
+
+
+The `outgoing_payment.created` event indicates an outgoing payment was created and is awaiting liquidity. Verify the sender's account balance and perform any other necessary verifications before funding the payment.
+
+If the sender has insufficient funds or if the payment should otherwise not be fulfilled, cancel the outgoing payment. Otherwise, put a hold on the sender's account and deposit the funds into Rafiki.
+
+#### Outgoing payment completed
+
+
+ Expand for event sequences
+
+{' '}
+
+{' '}
+
+ An outgoing payment
+for $12 is complete. $11.50 was sent. You choose to keep $0.50 as a service fee.
+
+ >ASE: Fires outgoing_payment.completed event to webhook endpoint, debitAmount: $12, sentAmount: $11.50
+ ASE->>R: Backend Admin API call: createOutgoingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Remove hold and deduct $12 from sender's account, credit your account with $0.50
+
+`}
+/>
+
+{' '}
+
+ An outgoing payment
+for $12 is complete. $11.50 was sent. You choose to keep $0.50 as a service fee.
+
+ >ASE: Fires outgoing_payment.completed event to webhook endpoint, debitAmount: $12, sentAmount: $11.50
+ ASE->>R: Backend Admin API call: createOutgoingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Remove hold and deduct $12 from sender's account, credit your account with $0.50
+ ASE->>R: Backend Admin API call: postLiquidityWithdrawal
+ R-->>ASE: success: true
+ R->>R: Two-phase transfer complete
+
+`}
+/>
+
+
+
+The `outgoing.payment_completed` event indicates that as much as possible has been sent to the recipient against their incoming payment.
+
+If there is excess liquidity in Rafiki due to differences between the sent and received amounts, withdraw the excess from the outgoing payment. What you choose to do with the excess is a business decision. One option is to return the excess to the sender. Another option is to retain the excess as a service fee. Lastly, remove the hold on your sender's account and debit their account on your ledger.
+
+#### Outgoing payment failed
+
+
+ Expand for event sequence
+ An outgoing payment for $12 failed. $8 was sent successfully.
+
+ >ASE: Fires outgoing_payment.failed event to webhook endpoint, debitAmount: $12, sentAmount: $8
+ ASE->>R: Backend Admin API call: createOutgoingPaymentWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Remove hold and deduct $8 from the sender's account
+
+`}
+/>
+
+
+
+The `outgoing_payment.failed` event indicates that an outgoing payment has either partially or completely failed and a retry was unsuccessful. Withdraw any remaining liquidity from the outgoing payment in Rafiki. If the payment failed completely (the `sentAmount` is `0`), remove the hold from your sender's account. If the payment partially failed, remove the hold from your sender's account, then debit the sender's account on your ledger with the amount that was sent successfully. Since there will be a discrepancy between the quoted amount and the actual sent amount, we suggest you refrain from taking a sending fee.
+
+### Wallet addresses
+
+
+
+| Event type | Description |
+| --------------------------------------------------------------------- | ------------------------------------------------------------------ |
+| [`wallet_address.not_found`](#wallet-address-not-found) | The requested wallet address was not found on this Rafiki instance |
+| [`wallet_address.web_monetization`](#wallet-address-web-monetization) | Web Monetization payments have been received via STREAM |
+
+
+
+#### Wallet address not found
+
+
+ Expand for event sequence
+ The wallet address, `https://wallet.example.com/carla_garcia` was requested but does not exist.
+
+ >ASE: Fires wallet_address.not_found event to webhook endpoint, wallet address: https://wallet.example.com/carla_garcia
+ ASE->>R: Backend Admin API call: createWalletAddress, url: https://wallet.example.com/carla_garcia, public name: Carla Eva Garcia
+ R-->>ASE: success: true
+
+`}
+/>
+
+
+
+The `wallet_address.not_found` event indicates that a wallet address was requested via the Open Payments wallet address API call, but the address doesn’t exist in your Rafiki instance.
+
+When you receive this event, look up the associated account in your system and create a wallet address for the account. The initial wallet address request will succeed if you create it within your configured `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` time frame.
+
+
+
+| Environment variable | Type | Description |
+| ---------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
+| `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | `backend` | The time in milliseconds that you have to create a missing wallet address before the initial request times out |
+
+
+
+#### Wallet address Web Monetization
+
+
+ Expand for event sequence
+ A wallet address received a Web Monetization payment of $0.33
+
+ >ASE: Fires wallet_address.web_monetization event to webhook endpoint, receivedAmount: $0.33
+ ASE->>R: Backend Admin API call: createWalletAddressWithdrawal
+ R-->>ASE: success: true
+ ASE->>ASE: Credit recipient's account with $0.33
+
+`}
+/>
+
+
+
+The `wallet_address.web_monetization` event indicates that a wallet address received Web Monetization payments via the ILP STREAM protocol. Withdraw the liquidity from the wallet address in Rafiki and credit the recipient's account on your ledger.
+
+### Low asset liquidity
+
+
+
+| Event type | Description |
+| --------------------------------------------- | ------------------------------------------------------------- |
+| [`asset.liquidity_low`](#asset-liquidity-low) | Your asset liquidity has dropped below your defined threshold |
+
+
+
+#### Asset liquidity low
+
+
+ Expand for event sequence
+ Your asset liquidity for USD (asset scale: 2) drops below $100.00.
+
+ >ASE: Fires asset.liquidity_low event to webhook endpoint, asset: USD (scale: 2, id: "abc")
+ ASE->>R: Backend Admin API call: depositAssetLiquidity
+ R-->>ASE: success: true
+
+`}
+/>
+
+
+
+The `asset.liquidity_low` event indicates that an asset's liquidity has dropped below your predefined liquidity threshold. Check if you already have, or can acquire, additional liquidity for that specific asset. If so, deposit it in Rafiki. Cross-currency transfers will fail if you don't increase the asset's liquidity.
+
+### Low peer liquidity
+
+
+
+| Event type | Description |
+| ------------------------------------------- | ------------------------------------------------------------ |
+| [`peer.liquidity_low`](#peer-liquidity-low) | Your peer liquidity has dropped below your defined threshold |
+
+
+
+#### Peer liquidity low
+
+
+ Expand for event sequence
+ The liquidity for your peer, Happy Life Bank, drops below $100.00 USD.
+
+ >ASE: Fires peer.liquidity_low event to webhook endpoint, peer: Happy Life Bank (asset: "USD", scale: 2, id: "abc")
+ ASE->>R: Backend Admin API call: depositPeerLiquidity
+ R-->>ASE: success: true
+
+`}
+/>
+
+
+
+The `peer.liquidity_low` event indicates that a peer's liquidity has dropped below your predefined liquidity threshold. Decide whether you want to extend the peer's credit line or if your peer must settle before you will extend a new line of credit. If you cannot or do not increase the peer liquidity in Rafiki, transfers to that peer will fail.
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/account-servicing-entity.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/account-servicing-entity.mdx
new file mode 100644
index 0000000000..912e7284b3
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/account-servicing-entity.mdx
@@ -0,0 +1,38 @@
+---
+title: Account servicing entity (ASE)
+slug: v1-beta/overview/concepts/account-servicing-entity
+---
+
+An account servicing entity (ASE) is a regulated entity that provides and maintains payment accounts for its customers. Examples of ASEs include banks, digital wallet providers, and mobile money providers.
+
+As regulated entities, ASEs are subject to the laws, rules, and regulations of their jurisdictions. As such, Rafiki should **not** be used in production environments by non-regulated entities.
+
+## Responsibilities and obligations
+
+A few examples of an ASE's responsibilities and obligations include:
+
+- Regulatory compliance
+- Account provisioning and maintenance
+- Transaction handling
+- Ledger management
+- Authentication and consent
+
+### Regulatory compliance
+
+ASEs must onboard account holders in compliance with regulatory requirements, such as performing Know Your Customer (KYC) checks, anti-money laundering (AML) processes, and sanctions screening.
+
+### Account provisioning and maintenance
+
+ASEs manage the creation, upkeep, and security of payment accounts. This includes providing channels for account holders (individuals or businesses) to interact with their accounts via mobile apps, websites, and other interfaces.
+
+### Transaction handling
+
+ASEs handle deposits and withdrawals through various external payment methods such as bank transfers, credit cards, and other payment services.
+
+### Ledger management
+
+ASEs maintain a ledger of account balances and transaction histories for their account holders.
+
+### Authentication and consent
+
+In the context of Open Payments, ASEs are responsible for authenticating resource owners (for example, account holders) and obtaining their consent when clients, such as mobile apps, request access to a resource (for example, an account).
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/accounting.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/accounting.mdx
new file mode 100644
index 0000000000..299d8517d4
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/accounting.mdx
@@ -0,0 +1,1322 @@
+---
+title: Accounting in Rafiki
+tableOfContents:
+ maxHeadingLevel: 3
+slug: v1-beta/overview/concepts/accounting
+---
+
+import {
+ LinkOut,
+ Mermaid,
+ StylishHeader
+} from '@interledger/docs-design-system'
+import { Steps } from '@astrojs/starlight/components'
+
+Rafiki uses double-entry accounting to record financial transactions. In this method of bookkeeping, a transaction recorded to one account results in an equal and opposite entry to another account. For example, a \$50 credit to one account results in a \$50 debit from another account.
+
+Transactions in Rafiki represent Interledger packet interactions, denominated in a given [asset](#assets). Packet interactions can be successful, fail, or be rejected. Rafiki's accounting layer processes the interactions and converts the activities into financial records, which are then written to your [accounting database](#accounting-databases).
+
+Accounts within Rafiki are your internal [liquidity](#liquidity-accounts) and [settlement](#settlement-accounts) accounts used to fund payments, not the accounts that you service for your customers. This distinction is crucial for understanding how Rafiki handles transactions and settlements.
+
+## Assets
+
+An asset represents a transferrable item of value. Although the Interledger Protocol (ILP) supports the transfer of any asset deemed to have value, assets are generally denominated in a currency. For example fiat currencies, central bank digital currencies, and branded currencies (such as merchant reward points).
+
+Part of Rafiki's [integration requirements](/v1-beta/integration/requirements/assets) include adding one or more assets that you support.
+
+An asset is made up of the following properties.
+
+
+
+| Property | Type | Description | Example |
+| ------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
+| `value` | BigInt | A numerical amount | `10000` |
+| `assetCode` | String | A code representing the asset. An ISO 4217 currency code should be used whenever possible. | `"USD"` |
+| `assetScale` | Integer | Difference in order of magnitude between the standard unit and a fractional unit | `2` |
+
+
+
+To convert an asset’s value into an amount that’s easier to interpret, apply the following formula.
+
+$\frac{value}{10^{assetScale}}$ = _currencyAmount_
+
+Using the example data from the preceding table, the formula looks like this:
+
+$\frac{10000}{10^2} =$100.00 USD
+
+## Accounts
+
+Rafiki uses a combination of liquidity and settlement accounts to track the amounts available to fund transactions. Rafiki does not physically hold funds in each account. Instead, it uses double-entry accounting to record the transactions. The actual settlement of amounts owed, in which funds are physically exchanged, occurs outside of both Rafiki and the Interledger Protocol.
+
+### Liquidity accounts
+
+Liquidity accounts track deposits, withdrawals, and transfers that occur during the course of a transaction. Rafiki provides liquidity accounts for assets, peers, and payments.
+
+Liquidity accounts hold either a zero or a positive balance. Rafiki ensures that the total debits to a liquidity account will not exceed the account's total credits.
+
+
+
+| Account type | What the account represents | Number of accounts |
+| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------ |
+| [Asset liquidity](#asset-liquidity-accounts) | The value, denominated in a given asset, that Rafiki has available to support cross-currency transactions | One per asset |
+| [Peer liquidity](#peer-liquidity-accounts) | The credit line, denominated in the asset of your peering relationship, that you extend to a peer | One per peer |
+| [Incoming payment liquidity](#incoming-payment-liquidity-accounts) | The value received from a completed incoming payment | One per incoming payment |
+| [Outgoing payment liquidity](#outgoing-payment-liquidity-accounts) | The value that Rafiki will attempt to send in an outgoing payment | One per outgoing payment |
+| [Wallet address liquidity](#wallet-address-liquidity-accounts) | The value that a wallet address received via SPSP | One per wallet address |
+
+
+
+#### Asset liquidity accounts
+
+Asset liquidity ensures Rafiki has enough liquidity, denominated in a given asset, to handle cross-currency (foreign exchange) transactions.
+
+An asset liquidity account represents the value that Rafiki has available for sending or forwarding ILP packets. You have one asset liquidity account for each asset you transact in.
+
+Whenever an outgoing payment/incoming payment is in a different asset than the peering relationship, the liquidity of asset accounts change depending on the FX direction. Any transaction that would result in a negative balance will fail.
+
+:::note
+If you and your peer transact in the same asset (there's no currency conversion) and you both provide your customers only with wallet addresses denominated in that asset, then there will be no movement into/from the corresponding asset's liquidity account.
+
+For example, you and your peer transact in USD and only provide your customers with USD wallet addresses. One of your customers sends $10 to your peer's customer. There's no movement from your USD asset liquidity account because there was no currency conversion. There is, however, an [outgoing payment liquidity account](/v1-beta/overview/concepts/accounting#outgoing-payment-liquidity-accounts) created to support the transaction.
+:::
+
+You can add a liquidity threshold for each asset liquidity account via the [`updateAsset`](https://rafiki.dev/apis/graphql/backend/mutations/#updateasset) mutation's `liquidityThreshold` input argument.
+
+When a threshold is entered, the [`asset.liquidity_low`](/v1-beta/integration/requirements/webhook-events#asset-liquidity-low) webhook event notifies you if an asset account's liquidity drops below the threshold.
+
+You should define and adjust asset liquidity based on your liquidity risk profile. You can deposit or withdraw asset liquidity as needed through [Rafiki Admin](/v1-beta/admin/admin-user-guide#edit-asset) or by using the [Backend Admin API](/v1-beta/admin/liquidity/asset-liquidity#manage-asset-liquidity-using-the-backend-admin-api).
+
+
+ Asset liquidity example - cross-currency transactions
+ Your Rafiki instance is configured for two assets: EUR and USD.
+
+- Rafiki holds an asset liquidity account for both EUR and USD.
+- You’ve set the asset scale of both currencies to 0.
+- Your starting EUR liquidity is 10 and your USD liquidity is 50.
+
+**Cross-currency transaction #1:**
+
+
+ 1. Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €10. 10 EUR move from the peer's liquidity account on your Rafiki instance to your EUR asset liquidity account. Your EUR liquidity increases to 20 (10 + 10).
+
+ 2. The EUR-to-USD exchange rate is applied, with €10 equating to \$12 USD. Since your starting USD liquidity is 50, your USD asset liquidity account can cover the transfer of \$12 USD to an incoming payment liquidity account. Your USD liquidity decreases to 38 (50 - 12).
+
+
+
+**Cross-currency transaction #2:**
+
+
+ 1. Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €50. Your EUR liquidity increases to 70 (20 + 50).
+
+ 2. The current EUR-to-USD exchange rate is applied, with €50 equating to \$55 USD. The transaction fails. Your USD liquidity account is 38, so you don't have enough liquidity to cover the transaction.
+
+ 3. Your EUR liquidity reduces back to 20 (70 - 50).
+
+
+
+
+#### Peer liquidity accounts
+
+Peer liquidity is the credit line you've extended to a peer. A peer liquidity account represents the amount of the line of credit that the peer still has available to them. You have one liquidity account for each peer and the account is denominated in the asset you both agreed to transact in.
+
+The amount of credit that you extend to a peer, the asset that you transact in, and the mechanism you use to settle are just a few items that should be defined in your respective peering agreements.
+
+:::note
+A peering agreement is a legal contract between the parties involved in a peering relationship. It defines terms such as the assets involved and other operational details. It is not configured or managed within Rafiki but is necessary for establishing the terms under which assets are exchanged.
+:::
+
+If a peer’s liquidity is insufficient (for example, they’ve used up their allotted credit line), payments will not be processed. Your peer should settle with you so that you can reset their liquidity.
+
+You can add a liquidity threshold for each peer liquidity account via the [`updatePeer`](https://rafiki.dev/apis/graphql/backend/mutations/#updatepeer) mutation's `liquidityThreshold` input argument.
+
+When a threshold is entered, the [`peer.liquidity_low`](/v1-beta/integration/requirements/webhook-events#peer-liquidity-low) webhook event notifies you if a peer's liquidity drops below the threshold.
+
+You should define and adjust each peer's liquidity based on your liquidity risk profile. You can deposit or withdraw peer liquidity as needed through [Rafiki Admin](/v1-beta/admin/admin-user-guide#edit-peer) or by using the [Backend Admin API](/v1-beta/admin/liquidity/peer-liquidity#manage-peer-liquidity-using-the-backend-admin-api).
+
+
+ Peer liquidity example
+ You and Cloud Nine Wallet are peers. You’ve agreed to extend Cloud Nine Wallet
+ a line of credit worth \$100.00 USD. This means Cloud Nine Wallet has \$100.00
+ in their peer liquidity account on your Rafiki instance. Your Rafiki instance can
+ receive packets that total up to \$100.00 from Cloud Nine Wallet. When the \$100.00
+ is used up, Cloud Nine Wallet settles with you by sending \$100.00 via the shared
+ settlement mechanism outlined in your peering agreement. When you receive the funds,
+ you reset their liquidity in Rafiki.
+
+
+#### Payment liquidity accounts
+
+Payment liquidity is the amount that's available because of an incoming or outgoing payment. Rafiki has three types of payment liquidity accounts.
+
+
+
+| Payment type | Purpose |
+| ---------------------------------------------------- | ------------------------------------------------------- |
+| [Incoming](#incoming-payment-liquidity-accounts) | For incoming payments created via the Open Payments API |
+| [Outgoing](#outgoing-payment-liquidity-accounts) | For outgoing payments created via the Open Payments API |
+| [Wallet address](#wallet-address-liquidity-accounts) | For payments sent via SPSP |
+
+
+
+##### Incoming payment liquidity accounts
+
+An incoming payment liquidity account represents the value received for an incoming payment. Incoming payments are created via Open Payments. When the first packet for an incoming payment is received, a corresponding liquidity account is automatically created. You will have one liquidity account per incoming payment.
+
+You are notified of created, completed, and expired incoming payments by listening for the appropriate [webhook events](/v1-beta/integration/requirements/webhook-events/#incoming-payments). Since Rafiki doesn't hold funds, anything you receive must be withdrawn and then credited to the recipient's account on your ledger.
+
+The liquidity account isn’t used again after the payment completes, but its record remains in your accounting database. When a new incoming payment occurs, a new liquidity account is created.
+
+##### Outgoing payment liquidity accounts
+
+An outgoing payment liquidity account represents the value available to send in an outgoing payment. When an outgoing payment is created via Open Payments, a corresponding liquidity account is automatically created. You will have one liquidity account per outgoing payment.
+
+You are notified of created, completed, and failed outgoing payments by listening for the appropriate [webhook events](/v1-beta/integration/requirements/webhook-events/#outgoing-payments). Liquidity must be deposited into the outgoing payment account before the payment can be processed.
+
+You may occasionally have excess liquidity, such as when an outgoing payment only partially completes and a portion of the send-amount remains. Since Rafiki doesn’t hold funds, any excess liquidity that remains after an outgoing payment completes must be withdrawn from the outgoing payment liquidity account. How you choose to handle the excess is up to you. You could, for example, refund the excess to the sender or take the amount as a fee.
+
+The account isn’t used again after the payment completes, but its record remains in your accounting database. When a new outgoing payment is created, a new liquidity account is created.
+
+##### Wallet address liquidity accounts
+
+A wallet address liquidity account contains the value received to a wallet address via [SPSP](/v1-beta/overview/concepts/interledger#simple-payment-setup-protocol-spsp). When an incoming payment is created, a corresponding liquidity account is automatically created. You will have one account per wallet address.
+
+Since Rafiki doesn’t hold funds, you must withdraw the liquidity when the payment completes and credit the funds to the recipient’s account on your ledger. You are notified to withdraw liquidity by listening for the appropriate [webhook event](/v1-beta/integration/requirements/webhook-events#wallet-addresses).
+
+Unlike the incoming and outgoing payment liquidity accounts, the same wallet address liquidity account will be used for future incoming SPSP payments.
+
+### Settlement accounts
+
+A settlement account represents the total funds, denominated in a single asset, that you have deposited into Rafiki. You have one settlement account for each asset you transact in.
+
+Settlement accounts hold either a zero or a negative balance. A negative balance on a settlement account means you've deposited more funds into Rafiki than you've withdrawn. The closer a settlement account's balance is to 0, the more likely it is you need to settle with your peer for the amount owed and then deposit the amount back into Rafiki.
+
+Rafiki ensures that the total credits to a settlement account do not exceed its total debits.
+
+
+ Settlement account example
+ You deposit \$10,000 into a peer's liquidity account, meaning you've extended a credit line of \$10,000 to your peer.
+
+Your peer liquidity account balance is \$10,000 and your USD settlement account balance is now -\$10,000.
+
+An incoming payment from your peer for \$100 is created, meaning your peer is using \$100 of their line of credit. Since Rafiki doesn't hold funds, you must withdraw the liquidity and credit the amount to the recipient's account on your ledger.
+
+Now, your peer liquidity account's balance is \$9,900 and your USD settlement account's balance is -\$9,900.
+
+
+
+## Accounting databases
+
+### TigerBeetle
+
+TigerBeetle is a high-performance distributed financial accounting database used by Rafiki’s [backend service](/v1-beta/integration/deployment/services/backend-service) to store account balance data. Both liquidity and settlement accounts in Rafiki correspond to TigerBeetle credit and debit accounts, respectively.
+
+TigerBeetle only holds balance data without any additional ILP packet metadata. For detailed information on TigerBeetle, including its consensus mechanism and its limitations, visit the official TigerBeetle documentation and blog. For more information about TigerBeetle in a production Rafiki environment, see [Running Rafiki in production](/v1-beta/integration/deployment/helm-k8s).
+
+### Postgres
+
+You can choose to use a separate Postgres database for accounting instead of using TigerBeetle. However, TigerBeetle is recommended due to its speed, efficiency, and dedicated design for handling double-entry/double-ledger accounting.
+
+## Transfers
+
+As with the accounts described above, Rafiki performs double-entry accounting for transfers, where increasing the total debits of one account increases the total credits of another account by the same amount, and vice versa.
+
+Transfers can complete in either a single phase or in two phases.
+
+### Single-phase transfer
+
+A single-phase transfer posts funds to accounts immediately when the transfer is created.
+
+**Example of successful single-phase incoming payment**
+
+>ASE: Fires webhook event when incoming payment completes
+ ASE->>R: Withdraws payment amount from incoming payment liquidity account
+ ASE->>ASE: Credits the recipient's account by the payment amount
+
+`}
+/>
+
+### Two-phase transfer
+
+A two-phase transfer moves funds in two stages.
+
+1. Reserve funds (`pending`)
+2. Resolve funds (`post`, `void`, or `expire`)
+
+**Example of successful two-phase incoming payment**
+
+>ASE: Fires webhook event when incoming payment completes
+ ASE->>Rafiki: Withdraws payment amount from incoming payment liquidity account (reserve funds pending)
+ ASE->>ASE: Credits the recipient's account by the payment amount
+ ASE->>Rafiki: Resolve funds (post)
+ Rafiki->>Rafiki: Two-phase transfer complete
+ `}
+/>
+
+The name two-phase transfer is a reference to the two-phase commit protocol for distributed transactions.
+
+You can [post and commit](/v1-beta/admin/liquidity/two-phase-transfers#post-and-commit-a-successful-transfer) a successful two-phase transfer and [void and roll back](/v1-beta/admin/liquidity/two-phase-transfers#void-and-roll-back-an-unsuccessful-transfer) an unsuccessful two-phase transfer by using the Backend Admin API.
+
+### Intra-Rafiki transfer examples
+
+Remember that a settlement account will always have a zero or negative balance and a liquidity account will always have a zero or positive balance.
+
+- [Deposits](#deposits)
+- [Withdrawals](#withdrawals)
+- [Payments in the same asset](#payments-in-the-same-asset)
+- [Cross currency payments](#cross-currency-payments)
+
+#### Deposits
+
+A deposit is the act of debiting the settlement account and crediting the liquidity account.
+
+**Example:** Depositing `100 USD` asset liquidity
+
+| Debit Account | Credit Account |
+| ------------- | --------------- |
+| Settlement | Asset liquidity |
+
+
+
+
+
+#### Payments in the same asset
+
+**Example:** Sender consented to a payment of `14 USD` but the quote promised to deliver `15 USD`. The send amount is less than the receive amount.
+
+| Debit Account | Credit Account |
+| ---------------- | ---------------- |
+| Outgoing payment | Incoming payment |
+| Asset liquidity | Incoming payment |
+
+
+
+
USD outgoing payment liquidity acct
+
USD asset liquidity acct
+
USD incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
14
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
1
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
15
+
+
+
+
+
+
+
+
+
+**Example:** Sender consented to a payment of `15 USD` but the quote promised to deliver `14 USD`. The send amount is more than the receive amount.
+
+| Debit Account | Credit Account |
+| ---------------- | ---------------- |
+| Outgoing payment | Incoming payment |
+| Outgoing payment | Asset liquidity |
+
+
+
+
+
+### Interledger transfer examples
+
+In these examples, the sender and receiver do not have wallet addresses at the same Rafiki instance.
+
+Remember that a settlement account will always have a zero or negative balance and a liquidity account will always have a zero or positive balance.
+
+- [Sending connector - same asset](#sending-connector---same-asset)
+- [Sending connector - cross currency](#sending-connector---cross-currency)
+- [Receiving connector - same asset](#receiving-connector---same-asset)
+- [Receiving connector - cross currency](#receiving-connector---cross-currency)
+- [Connector - same asset](#connector---same-asset)
+- [Connector - cross currency](#connector---cross-currency)
+
+#### Sending connector - same asset
+
+**Example:** Sender creates an outgoing payment for `100 USD` to an incoming payment in the same asset at a peer's Rafiki instance
+
+| Debit Account | Credit Account |
+| ---------------- | -------------- |
+| Outgoing payment | Peer liquidity |
+
+
+
+
USD outgoing payment liquidity acct
+
USD peer liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+#### Sending connector - cross currency
+
+**Example:** Sender creates an outgoing payment for `100 USD` to an incoming payment at a peer's Rafiki instance. The peering relationship is in EUR, so the payment is converted on the sending side.
+
+| Debit Account | Credit Account | Asset |
+| ---------------- | --------------- | ----- |
+| Outgoing payment | Asset liquidity | `USD` |
+| Asset Liquidity | Peer Liquidity | `EUR` |
+
+
+
+
USD outgoing payment liquidity acct
+
USD asset liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+{' '}
+
+
+
EUR asset liquidity acct
+
EUR peer liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
90
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
90
+
+
+
+
+
+
+
+
+
+#### Receiving connector - same asset
+
+**Example:** An incoming payment receives `100 USD` from an outgoing payment in the same asset at a peer's Rafiki instance.
+
+| Debit Account | Credit Account |
+| -------------- | ---------------- |
+| Peer liquidity | Incoming payment |
+
+
+
+
USD peer liquidity acct
+
USD incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
100
+
+
+
+
+
+
+
+
+
+#### Receiving connector - cross currency
+
+**Example:** A Rafiki instance receives `10 USD` from a peer (peering relationship in USD) to be deposited in an incoming payment liquidity account denominated in EUR. The payment is converted to EUR and deposited.
+
+| Debit Account | Credit Account | Asset |
+| --------------- | ---------------- | ----- |
+| Peer liquidity | Asset liquidity | `USD` |
+| Asset liquidity | Incoming payment | `EUR` |
+
+
+
+
USD peer liquidity acct
+
USD asset liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
10
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
10
+
+
+
+
+
+
+{' '}
+
+
+
EUR asset liquidity acct
+
EUR incoming payment liquidity acct
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
9
+
+
+
+
+
+
+
+
+
+
Debit
+
Credit
+
+
+
+
+
+
9
+
+
+
+
+
+
+
+
+
+#### Connector - same asset
+
+**Example:** Rafiki forwards `10 USD` from peer A to peer B.
+
+| Debit Account | Credit Account |
+| -------------- | -------------- |
+| Peer liquidity | Peer liquidity |
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/clearing-settlement.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/clearing-settlement.mdx
new file mode 100644
index 0000000000..4be4a875a7
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/clearing-settlement.mdx
@@ -0,0 +1,30 @@
+---
+title: Clearing and settlement
+slug: v1-beta/overview/concepts/clearing-settlement
+---
+
+## Clearing
+
+When a payment is made over traditional banking rails, the money doesn’t move instantly. First, there are checks to confirm that the money exists and can be transferred. Clearing networks are responsible for exchanging messages between ASEs to facilitate these checks. This process is called clearing. When a payment successfully clears, it means the payer’s ASE has an obligation to the payee’s ASE.
+
+The [Interledger Protocol (ILP)](/v1-beta/overview/concepts/interledger) is not a traditional clearing network, but does function in a similar way.
+
+- ASEs that implement the protocol must become [peers](/v1-beta/integration/requirements/peers) to transact with one another. This is comparable to traditional banking, where ASEs must use the same clearing network. An ASE can't use Interledger to transact with another ASE unless they have both implemented the protocol and have peered with one another.
+- Peered ASEs exchange ILP packets, which are packets of value that contain transaction information. ILP packets are akin to the messages exchanged during the traditional clearing process.
+- The successful exchange of ILP packets between peers creates obligations between them that must be settled. The receipt of a fulfilled ILP packet is basically a conditional IOU—a promise to pay—that affects the financial accounting balances between the peers.
+
+You can read more about clearing as it relates to ILP in the [Interledger developer docs](https://interledger.org/developers/rfcs/peering-clearing-settling/).
+
+Conceptually, Rafiki sits at the clearing level, but isn't a clearing network. It's software that makes implementing the Interledger protocol faster and easier. Rafiki uses ILP to [track liquidity](/v1-beta/overview/concepts/accounting) between assets, payments, and peers. An ASE must still connect Rafiki to their existing backend system and internal ledger for authentication, fetching exchange rates, and managing liquidity itself. For example, if an incoming payment completes in Rafiki, the ASE’s backend must credit the recipient’s account on their own system, however that might look.
+
+In any case, no movement of actual money has occurred yet.
+
+## Settlement
+
+In traditional banking, settlement is the fulfillment of an obligation between ASEs. It turns the promise of payment into a real payment by moving actual money. This occurs over a shared settlement network, such as Fedwire in the United States.
+
+When a payer’s ASE settles with the payee’s ASE, there’s a high chance that the ASE isn’t physically handing over cash. There’s more likely to be an intermediary, like a reserve bank or central bank, that maintains accounts for both ASEs. The intermediary moves funds from one account to the other, crediting and debiting the accounts as necessary.
+
+With Interledger, the concept of settlement is not that different. Each [peer](/v1-beta/integration/requirements/peers) must agree on a settlement system to use to fulfill their obligations with one another. However, ILP itself is not a settlement system. This means peers must have some other way to fulfill their obligations and exchange value. Examples can include using a real-time gross settlement system like Fedwire, an automated clearing house (ACH) network, a money transfer service, or some other payment channel. You can read more about settling as it relates to ILP in the [Interledger developer docs](https://interledger.org/developers/rfcs/settlement-engines/).
+
+Rafiki is also not a settlement system. It [keeps track of liquidity](/v1-beta/overview/concepts/accounting) through the use of liquidity and settlement accounts. Liquidity accounts track deposit, withdrawal, and transfer amounts. Settlement accounts track the availability of funds, denominated in a single asset. A negative balance means funds are available. The closer a settlement account’s balance is to zero, the more likely it is that one peer needs to settle with the other over their agreed-upon settlement system.
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/interledger.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/interledger.mdx
new file mode 100644
index 0000000000..0c29d1ba96
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/interledger.mdx
@@ -0,0 +1,74 @@
+---
+title: Interledger
+slug: v1-beta/overview/concepts/interledger
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Building and maintaining your own connector to participate on the Interledger network can be a time consuming and complex undertaking. As a reference implementation of the Interledger stack, Rafiki gives you all the tools you need to quickly and easily join the network and enable Interledger capabilities on your users’ accounts.
+
+## Packets
+
+At the core of Interledger is the Interledger Protocol (ILP). It’s a request/response protocol where requests and responses are ILP packets.
+
+These packets of data carry information about a payment. Typically, information about a single aggregate payment from sender to receiver is split into multiple ILP packets.
+
+Each packet represents a conditional IOU which affects financial accounting balances between peers. Amounts adjust based on the sender's asset, the receiver's asset, and Rafiki's configured [exchange rates service](/v1-beta/integration/requirements/exchange-rates). Then, the amounts are used to update account liquidity in your accounting database (TigerBeetle or Postgres).
+
+## Peers
+
+Interledger itself is a network of computers that enables sending payment messages across payment networks. Each computer on the network is a node.
+
+For two nodes on the Interledger network to exchange ILP packets with one another, the two nodes must be peers. There are a number of [requirements](/v1-beta/integration/requirements/peers#perform-prerequisites) that both you and your potential peer must meet to form a peering relationship.
+
+Since the purpose of peering is to facilitate payments, which often involves extending lines of credit, your peer should be someone you trust. We strongly recommend you and your potential peer define your expectations and outline your agreements in a legally binding document peering with one another.
+
+## Connectors
+
+Each node on the Interledger network can take on the role of sender, connector, or receiver, depending on the payment.
+
+- Sender - Originates the payment by sending ILP packets.
+- Connector - An intermediary between a sender and receiver that forwards ILP packets. Connectors can facilitate payments to or from anyone they’re peered with.
+- Receiver - The final recipient of the ILP packets and, as such, the payment.
+
+If the sender and receiver nodes are peers, then the payment flow is straightforward and no intermediary connector nodes are needed. However, if the sender and receiver aren’t peers, then the payment must route through one or more connectors. Rafiki’s [backend service](/v1-beta/integration/deployment/services/backend-service#interledger-connector) includes an Interledger connector for sending and receiving ILP packets.
+
+In the image below, the sender node (A) and the receiver node (C) share a common peer (B). In payments from the sender to the receiver, node B performs the role of connector to facilitate payments between the two.
+
+
+
+In reality, there can be multiple connectors between a sender node and a receiver node. As more nodes that support different assets peer with one another, the easier it becomes for payments to traverse the Interledger network.
+
+## Payment pointer
+
+A payment pointer is a type of wallet address that serves as an SPSP endpoint to facilitate sending and receiving ILP packets. Rafiki will assign each of your customers' accounts with a payment pointer.
+
+Payment pointers must resolve to an HTTPS URL and can be written out using the `$` shorthand (for example, `$wallet.example.com/alice`) or as a URL (for example, `https://wallet.example.com/alice`).
+
+See [Payment pointers and wallet addresses](/v1-beta/overview/concepts/payment-pointers) for more information.
+
+## Simple Payment Setup Protocol (SPSP)
+
+The Simple Payment Setup Protocol (SPSP) is an application layer protocol that uses HTTPS to exchange payment information.
+Every payment pointer issued by Rafiki serves as an SPSP endpoint by default (`ENABLE_SPSP_PAYMENT_POINTERS`).
+
+When a `GET` request is made to a payment pointer, the response contains the ILP address of the destination account and a shared secret. These details are needed to set up a STREAM connection between two counterparties to facilitate direct payments over Interledger.
+
+## STREAM Protocol
+
+The STREAM Protocol is the transport layer protocol in the Interledger Protocol stack. After SPSP communicates the ILP address of the destination account and the shared secret, the STREAM protocol uses these details to set up a STREAM connection between the
+counterparties.{' '}
+
+Rafiki’s `backend` service includes an Interledger connector for sending and receiving STREAM packets. STREAM packets are encoded, encrypted, and sent as the `data` field in ILP packets. The protocol uses the shared secret to authenticate and encrypt packets, and to generate conditions and fulfillments.
+
+A critical function of STREAM is to determine the path exchange rate and handle any changes in the rate. Rafiki’s exchange rates service allows you to provide exchange rate information.
+
+### STREAM receipts
+
+In a STREAM connection, a receipt can be generated by the party receiving funds for every fulfilled ILP packet. Each receipt contains a progressively higher amount, representing the total amount received over the STREAM connection.
+
+Rafiki’s Interledger connector keeps track of the total amount received for a STREAM connection in Redis to support STREAM receipts.
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/open-payments.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/open-payments.mdx
new file mode 100644
index 0000000000..fdc510b3a9
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/open-payments.mdx
@@ -0,0 +1,39 @@
+---
+title: Open Payments
+tableOfContents:
+ maxHeadingLevel: 2
+slug: v1-beta/overview/concepts/open-payments
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Rafiki follows the Open Payments standard to enable third-party clients to securely retrieve account information and initiate payments from your customers’ accounts with their consent. The standard describes a uniform way to create and manage grants and resources for incoming payments, quotes, and outgoing payments.
+
+### Example use case - retrieve account information
+
+Some of your customers use a third-party application that allows them to create budgets and monitor their spending. The application can call the Open Payments APIs, enabling it to communicate with any account servicing entity that implements the Open Payments standard. When your customers give the app permission to retrieve their transaction history, the app communicates with your Rafiki instance via the Open Payments APIs to obtain grants from your authorization server and transaction history from your resource server.
+
+### Further reading
+
+We strongly encourage you to familiarize yourself with the Open Payments standard. Extensive documentation is available on the Open Payments website. We recommend you start by reviewing all the pages within the _Intro to Open Payments_ section. Here are a few links to get you started.
+
+-
+ Getting started with Open Payments
+
+-
+ Client keys
+
+-
+ HTTP message signatures
+
+-
+ Grant negotiation and authorization
+
+
+## Rafiki's backend service
+
+Rafiki’s [`backend`](/v1-beta/integration/deployment/services/backend-service) service is the main service for handling business logic and external communication. The service is responsible for, among other things, exposing the endpoints of the Open Payments APIs for clients to perform account management tasks. Every request and response is validated against the Open Payments specification.
+
+## Rafiki's auth service
+
+Rafiki’s [`auth`](/v1-beta/integration/deployment/services/auth-service) service is a reference implementation of an opinionated Open Payments authorization server. The authorization server is responsible for delegating authorization (via grants) to clients to use the Open Payments APIs, resolving clients’ public keys to authenticate and authorize incoming requests, and creating payments and quotes on the backend. Open Payments leverages the Grant Negotiation and Authorization Protocol (GNAP) for delegating authorization. You can learn more about the protocol by reviewing its specification.
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/payment-pointers.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/payment-pointers.mdx
new file mode 100644
index 0000000000..4055344220
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/payment-pointers.mdx
@@ -0,0 +1,56 @@
+---
+title: Payment pointers and wallet addresses
+slug: v1-beta/overview/concepts/payment-pointers
+---
+
+## Payment pointers
+
+A payment pointer is a standardized identifier for a payment account that supports Interledger payments. Each payment pointer must resolve to an HTTPS URL that serves as an [SPSP](/v1-beta/overview/concepts/interledger#simple-payment-setup-protocol-spsp) endpoint to facilitate sending and receiving ILP packets.
+
+You can determine whether a URL is a payment pointer by sending a `GET` request to the URL with an `accept: application/spsp4+json` header.
+
+```http title="Example request"
+curl --request GET \
+ --url https://wallet.example.com/alice/ \
+ --header 'accept: application/spsp4+json'
+```
+
+A response from an SPSP server means the URL is a payment pointer.
+
+```http title="Example response"
+{
+ "destination_account":"example.0.cloudnine.ind.alice.cdfa5e16-e759",
+ "shared_secret":"7h0s7EpQDqcgzqmX-mwrNHFHinPvJq8Jw",
+}
+```
+
+Payment pointers are often written out using the `$` shorthand. For example, `$wallet.example.com/alice`, which resolves to `https://wallet.example.com/alice/`.
+
+Rafiki assigns each of your customers' accounts with a payment pointer. This payment pointer is also a wallet address because Rafiki supports both Interledger and Open Payments.
+
+## Wallet addresses
+
+A wallet address is a secure, unique URL for a payment account that supports Open Payments. It acts as an entry point into the Open Payments APIs, facilitating interactions like sending and receiving payments.
+
+You can determine whether a URL is a wallet address by sending a `GET` request to the URL with an `accept: application/json` header.
+
+```http title="Example request"
+curl --request GET \
+ --url https://wallet.example.com/alice \
+ --header 'accept: application/json'
+```
+
+A valid response means the URL is a wallet address.
+
+```http title="Example response"
+{
+ "id": "https://wallet.example.com/alice",
+ "publicName": "Alice",
+ "assetCode": "USD",
+ "assetScale": 2,
+ "authServer": "https://auth.wallet.example.com",
+ "resourceServer": "https://wallet.example.com",
+}
+```
+
+Rafiki assigns each of your customers' accounts with a wallet address. This wallet address is also a payment pointer because Rafiki supports Open Payments and Interledger. See the integration requirements for [wallet addresses](/v1-beta/integration/requirements/wallet-addresses) for more information.
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/concepts/telemetry.mdx b/packages/documentation/src/content/docs/v1-beta/overview/concepts/telemetry.mdx
new file mode 100644
index 0000000000..4da3f43ccd
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/concepts/telemetry.mdx
@@ -0,0 +1,265 @@
+---
+title: Telemetry
+slug: v1-beta/overview/concepts/telemetry
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+The objective of the telemetry feature is to gather metrics and establish an infrastructure for visualizing valuable network insights. Some of the metrics that we at the Interledger Foundation collect include:
+
+- The total amount of money transferred via packet data within a specified time frame (daily, weekly, monthly)
+- The number of transactions that have been at least partially successful
+- The number of ILP packets flowing through the network
+- The average amount of money held within the network per transaction
+- The average time it takes for an outgoing payment to complete
+
+Our goals are to:
+
+- Track the growth of the network in terms of transaction sizes and the number of transactions processed
+- Use the data for our own insights
+- Enable you to gain your own insights
+
+### Privacy and optionality
+
+Privacy is a paramount concern for the Interledger Foundation. Rafiki’s telemetry feature is designed to provide valuable network insights without violating privacy or aiding malicious ASEs. Review the [Privacy](#privacy) section below for more information.
+
+The telemetry feature is currently enabled by default on test environments (environments not dealing with real money). When active, the feature transmits metrics to the testnet collector. You can opt in to sharing your metrics with a livenet collector when operating in a production livenet environment (with real money). Regardless of environment, you can also opt-out of telemetry completely. Review the [telemetry environment variables](#telemetry-environment-variables) for more information.
+
+### Architecture
+
+
+
+### OpenTelemetry (OTEL)
+
+The Interledger Foundation has adopted OpenTelemetry (OTEL) to ensure compliance with a standardized framework that is compatible with a variety of tool suites. OTEL allows you to use your preferred tools for data analysis, while Rafiki is instrumented and observable through a standardized metrics format.
+
+### Telemetry Elastic Container Service (ECS) cluster
+
+The Telemetry Replica service is hosted on AWS ECS Fargate and is configured for availability and load balancing of custom ADOT (AWS Distro for OpenTelemetry) Collector ECS tasks.
+
+When you opt for telemetry, metrics are sent to our Telemetry service. To enable you to build your own telemetry solutions, instrumented Rafiki can send data to multiple endpoints. This allows for the integration of a local OTEL Collector container that can support custom requirements. Metrics communication is facilitated through gRPC.
+
+### OTEL SDK - Rafiki instrumentation
+
+The OTEL SDK is integrated into Rafiki to create, collect, and export metrics. The SDK integrates seamlessly with the OTEL Collector.
+
+### Prometheus - AMP
+
+The Interledger Foundation uses Amazon Managed Service for Prometheus (AMP) to collect data from the telemetry cluster.
+
+:::note
+AMP offers limited configuration options and cannot crawl data outside of AWS. This limitation led us to adopt a push model, using `prometheusRemoteWrite`, instead of a pull model. For future development, we may consider hosting our own Prometheus.
+:::
+
+### Grafana - Grafana Cloud
+
+Grafana Cloud is used for data visualization dashboards and offers multiple tools that extend Prometheus Promql.
+
+:::note
+The Interledger Foundation initially used Amazon-hosted Grafana which did not meet our needs for embedding dashboards. Grafana Cloud offers a feature called _public dashboards_ which allows us to share dashboards. However, embedding may still pose a challenge.
+:::
+
+### Exchange rates
+
+For telemetry purposes, all amounts collected by instrumented Rafiki should be converted to a base currency.
+
+:::caution[Privacy reasoning]
+If only two ASEs are peered over a non-USD currency and we collect data in that currency, it would be easy to determine the volumes moved between those two ASEs. To maintain privacy, we convert all amounts to a base currency.
+:::
+
+If an ASE does not provide the necessary exchange rate for a transaction, the telemetry solution still converts the amount to the base currency using external exchange rates. A Lambda function on AWS retrieves and stores the external exchange rates. The function is triggered by a daily `CloudWatch` event and stores the rates in a public S3 bucket. The S3 bucket does not have versioning, and the data is overwritten daily to further ensure privacy.
+
+### Instrumentation
+
+Rafiki has the following metrics. All data points (counter increases) are exported to collection endpoints at a configurable interval. The default interval is 15 seconds.
+
+
+
+| Metric | Type | Description | Behavior |
+| ------------------------- | --------- | ------------------------------------------ | ------------------------------------------------------------------------------ |
+| `transactions_total` | Counter | Count of funded outgoing transactions | Increases by 1 for each successfully funded outgoing payment resource |
+| `packet_count_prepare` | Counter | Count of ILP Prepare packets that are sent | Increases by 1 for each Prepare packet that's sent |
+| `packet_count_fulfill` | Counter | Count of ILP Fulfill packets | Increases by 1 for each Fulfill packet that's received |
+| `packet_count_reject` | Counter | Count of ILP Reject packets | Increases by 1 for each Reject packet that's received |
+| `packet_amount_fulfill` | Counter | Amount sent through the network | Increases by the amount sent in each ILP packet |
+| `transaction_fee_amounts` | Counter | Fee amount sent through network | Increases by the amount sent minus the amount received for an outgoing payment |
+| `ilp_pay_time_ms` | Histogram | Time to complete an ILP payment | Records the time taken to make an ILP payment |
+
+
+
+The current implementation only collects metrics on the SENDING side of a transaction. Metrics for external Open Payments transactions RECEIVED by a Rafiki instance in the network are not collected.
+
+## Privacy
+
+Rafiki telemetry is designed with a strong emphasis on privacy. The system anonymizes user data and refrains from collecting identifiable information. Since transactions can originate from any user to a Rafiki instance, the privacy measures are implemented directly at the source (each Rafiki instance). This means that at the individual level, the data is already anonymous as single Rafiki instances service transactions for multiple users.
+
+### Differential privacy and local differential privacy (LDP)
+
+Differential privacy is a system for publicly sharing information about a dataset by describing the patterns of groups within the dataset while withholding information about individuals in the dataset. Local differential privacy (LDP) is a variant of differential privacy where noise is added to each individual’s data point before the data point is sent to the server. This ensures that the server never sees the actual data, providing a strong privacy guarantee.
+
+### Rounding technique and bucketing
+
+Rafiki’s telemetry implementation uses a rounding technique that essentially aggregates multiple transactions into the same value, making them indistinguishable. This is achieved by dividing the transaction values into buckets and rounding the values to the nearest bucket.
+
+The bucket size is calculated based on the raw transaction value. For lower value transactions, which are expected to occur more frequently, the bucket sizes are determined linearly for higher granularity. However, after a certain threshold, the bucket size calculation switches to a logarithmic function to ensure privacy for higher value transactions (which are less frequent but pose greater privacy concerns).
+
+To handle outliers, a clipping technique is implemented, capping the buckets. Any value that exceeds a given threshold is placed in a single bucket. Conversely, any value that falls below a certain minimum is also placed in a single bucket. This ensures that both high and low outliers do not disproportionately affect the overall data, providing further privacy guarantees for these transactions.
+
+### Laplacian distribution
+
+The Laplacian distribution is often used in differential privacy due to its double exponential decay property. This property ensures that a small change in the data does not significantly affect the probability distribution of the output, providing a strong privacy guarantee.
+
+To achieve local differential privacy (LDP), noise is selected from the Laplacian distribution and added to the rounded values. The noise is generated based on a privacy parameter, which is calculated using the sensitivity of the function.
+
+The sensitivity of a function in differential privacy is the maximum amount that any single observation can change the output of the function. In this case, the sensitivity is considered to be the maximum of the rounded value and the bucket size.
+
+The privacy parameter is computed as one-tenth of the sensitivity. This parameter controls the trade-off between privacy and utility: a smaller privacy parameter means more privacy but less utility, and a larger privacy parameter means less privacy but more utility.
+
+The noise, selected from the Laplacian distribution, is then generated using this privacy parameter and added to the rounded value. If the resulting value is zero, the value is set to half the bucket size to ensure that the noise does not completely obscure the transaction value.
+
+### Currency conversion
+
+Another factor that obscures sensitive data is currency conversion. In cross-currency transactions, exchange rates are provided by you, as the ASE, internally. As such, the exchange rates cannot be correlated to an individual transaction. If you don’t or can’t provide the necessary rates, an external API for exchange rates is used. The obtained exchange rates are overwritten frequently in this case, with no versioning or history access. This introduces an additional layer of noise and further protects the privacy of the transactions.
+
+### Experimental transaction values when using the algorithm
+
+The following table shows the values in the algorithm when running transactions for different amounts. The raw value increases as you move down the rows of the table. All values are in scale 4.
+
+
+
+### References
+
+Rafiki’s telemetry solution is a combination of techniques described in various white papers on privacy-preserving data collection. More information can be found in the following papers:
+
+-
+ Local differential privacy for human-centered computing
+
+-
+ Collecting telemetry data privately
+
+-
+ RAPPOR: Randomized aggregatable privacy-preserving ordinal response
+
+
+## Deploy custom telemetry
+
+Rafiki allows you to build your own telemetry solution based on the OpenTelemetry (OTEL) standardized metrics format that Rafiki exposes.
+
+You must deploy your own OTEL Collector that acts as a sidecar container to Rafiki, then provide the OTEL Collector’s ingest endpoint so that Rafiki can begin sending metrics to the collector.
+
+### Telemetry environment variables
+
+#### Required
+
+When the `ENABLE_TELEMETRY` variable is `true`, the following are required.
+
+
+
+| Variable name | Type | Description |
+| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `INSTANCE_NAME` | String | Your Rafiki instance's name used to communicate for telemetry and auto-peering. For telemetry, it's used to distinguish between the different instances pushing data to the telemetry collector. |
+
+
+
+#### Optional
+
+
+
+| Variable name | Type | Description |
+| -------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ENABLE_TELEMETRY` | Boolean | Enables the telemetry service on Rafiki. Defaults to `true`. |
+| `LIVENET` | Boolean | Determines where to send metrics. Defaults to `false`, resulting in metrics being sent to the testnet OTEL Collector.
Set to `true` on production environments dealing with real money.
|
+| `OPEN_TELEMETRY_COLLECTOR_URLS` | String | A CSV of URLs for OTEL Collectors (e.g., `http://otel-collector-NLB-e3172ff9d2f4bc8a.elb.eu-west-2.amazonaws.com:4317,http://happy-life-otel-collector:4317`). |
+| `OPEN_TELEMETRY_EXPORT_INTERVAL` | Number | Indicates, in milliseconds, how often the instrumented Rafiki instance should send metrics. Defaults to`15000`. |
+| `TELEMETRY_EXCHANGE_RATES_URL` | String |
Defines the endpoint Rafiki queries for exchange rates. Used as a fallback if/when [exchange rates](/v1-beta/integration/requirements/exchange-rates) aren’t provided.
When set, the response format of the external exchange rates API should be of type `rates`, as is expected by the rate service.
Defaults to `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json`, which points to a public S3 that has the previously mentioned required format, updated daily.
|
+
+
+
+### Example Docker OTEL Collector image and configuration
+
+Below is an example of a Docker OTEL Collector image and configuration that integrates with Rafiki and sends data to a Prometheus remote write endpoint.
+
+You can test the configuration in our [Local Playground](/v1-beta/integration/playground/overview) by providing the environment variables in the preceding table to `happy-life-backend` in the `docker-compose.yml` file.
+
+#### Docker Compose config
+
+```yaml
+# Serves as example for optional local collector configuration
+happy-life-otel-collector:
+ image: otel/opentelemetry-collector-contrib:latest
+ command: ['--config=/etc/otel-collector-config.yaml', '']
+ environment:
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID-''}
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY-''}
+ volumes:
+ - ../collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml
+ networks:
+ - rafiki
+ expose:
+ - 4317
+ ports:
+ - '13132:13133' # health_check extension
+```
+
+#### OTEL Collector config
+
+Supplemental documentation is available in OTEL’s Collector Configuration documentation.
+
+```yaml wrap
+# Serves as example for the configuration of a local OpenTelemetry Collector that sends metrics to an AWS Managed Prometheus Workspace
+# Sigv4auth required for AWS Prometheus Remote Write access (USER with access keys needed)
+
+extensions:
+ sigv4auth:
+ assume_role:
+ arn: 'arn:aws:iam::YOUR-ROLE:role/PrometheusRemoteWrite'
+ sts_region: 'YOUR-REGION'
+
+receivers:
+ otlp:
+ protocols:
+ grpc:
+ http:
+ cors:
+ allowed*origins:
+ - http://*
+ - https://\_
+
+processors:
+ batch:
+
+exporters:
+ logging:
+ verbosity: 'normal'
+ prometheusremotewrite:
+ endpoint: 'https://aps-workspaces.YOUR-REGION.amazonaws.com/workspaces/ws-YOUR-WORKSPACE-IDENTIFIER/api/v1/remote_write'
+ auth:
+ authenticator: sigv4auth
+
+service:
+ telemetry:
+ logs:
+ level: 'debug'
+ metrics:
+ level: 'detailed'
+ address: 0.0.0.0:8888
+ extensions: [sigv4auth]
+ pipelines:
+ metrics:
+ receivers: [otlp]
+ processors: [batch]
+ exporters: [logging, prometheusremotewrite]
+```
diff --git a/packages/documentation/src/content/docs/v1-beta/overview/overview.mdx b/packages/documentation/src/content/docs/v1-beta/overview/overview.mdx
new file mode 100644
index 0000000000..23d6fc6594
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/overview/overview.mdx
@@ -0,0 +1,45 @@
+---
+title: Overview
+slug: v1-beta/overview/overview
+---
+
+import { LinkOut, Tooltip } from '@interledger/docs-design-system'
+import { Card, CardGrid } from '@astrojs/starlight/components'
+
+Implementing and maintaining the [Interledger Protocol (ILP)](#interledger) stack on your own can be difficult and time-consuming. Rafiki makes it easy to integrate with the Interledger network without needing to develop and maintain your own implementations.
+
+Rafiki is open-source software maintained by a dedicated team and freely available to any licensed [account servicing entity](/v1-beta/overview/concepts/account-servicing-entity) (ASE) wanting to implement Interledger and [Open Payments](#open-payments) on users' accounts.
+
+:::tip[Try it out]
+The [Local Playground](/v1-beta/integration/playground/overview) allows you to test Rafiki by running two mock account servicing entities that automatically peer with one another.
+:::
+
+## Use cases
+
+### Peer-to-peer payments between ASEs
+
+In the context of Rafiki, a peer is another ASE with whom you transact. Forming a peering relationship requires you to both agree on the currency in which you will transact, on a settlement mechanism and cadence, and other details. Interledger creates interoperability between different payment systems and currencies, making it easier for peers to directly transact with one another.
+
+### eCommerce payments
+
+If a merchant accepts Interledger or Open Payments as a payment method, then a customer can pay using their wallet address instead of entering, for example, a credit card number and other personal details on the merchant’s site. Rafiki’s implementation of Interledger and Open Payments means your account holders can use their wallet addresses for both one-time purchases and recurring purchases, such as subscriptions, anywhere Interledger or Open Payments is an accepted payment method.
+
+### Web Monetization
+
+With Web Monetization, site visitors can pay an amount of their choosing to a participating site with little to no interaction. Both the site and the site visitor must have an Open Payments-enabled wallet address to receive and send payments. Rafiki’s implementation of Interledger’s Simple Payment Setup Protocol (SPSP) and the Open Payments standard means you can assign one or more wallet addresses to your account holders’ accounts, making these accounts support incoming and outgoing Web Monetization payments right out of the box.
+
+## Interledger
+
+The Interledger network is a network of nodes that have implemented the Interledger Protocol (ILP) stack. Rafiki is a reference implementation if the ILP stack, enabling you to more easily become a node on the network and start sending and receiving payments.
+
+Interledger is designed to be a network on top of existing payment networks that serves as the interoperability layer between them all, forwarding payment messages (packets) while also taking care of currency conversion. Interledger ensures that packets take the fastest and cheapest route from one Interledger node to another.
+
+[Learn more about Interledger](/v1-beta/overview/concepts/interledger)
+
+## Open Payments
+
+Open Payments is an API standard and set of open RESTful APIs that facilitate interoperability in the setup and completion of payments. The standard provides a uniform way to create and manage grants and resources for incoming payments, quotes, and outgoing payments.
+
+By following the Open Payments standard, Rafiki allows your customers' accounts to become Open Payments-enabled. Clients, such as mobile apps, can then call the Open Payments APIs to securely retrieve transaction data and initiate payments from your customers' accounts with your customers' prior consent.
+
+[Learn more about Open Payments](/v1-beta/overview/concepts/open-payments)
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/architecture.mdx b/packages/documentation/src/content/docs/v1-beta/resources/architecture.mdx
new file mode 100644
index 0000000000..cee1c8fa11
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/architecture.mdx
@@ -0,0 +1,30 @@
+---
+title: Architecture
+slug: v1-beta/resources/architecture
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Rafiki is a collection of three services that run together. Each one can scale horizontally.
+
+- [Backend](/v1-beta/integration/deployment/services/backend-service) - The main service, responsible for handling business logic and external communication
+- [Auth](/v1-beta/integration/deployment/services/auth-service) - A reference implementation of an Open Payments authorization server, used for grant authorization and authentication
+- [Frontend](/v1-beta/integration/deployment/services/frontend-service) - An optional internal user interface, called the [Rafiki Admin](/v1-beta/admin/admin-user-guide), for you to manage your Rafiki instance
+
+These services rely on a number of databases.
+
+- A Postgres database used by the `auth` service for storing auth-related resources (grants, access tokens, and interactions)
+- A Redis database used by the `auth` service to store session data
+- A Postgres database used by the `backend` service for Open Payments resources and application data
+-
+ TigerBeetle
+
+ , used by the `backend` service for accounting balances
+- A Redis database used by the `backend` service as a cache to share STREAM connection details across processes
+
+An additional package for [token introspection](/v1-beta/integration/deployment/services/auth-service#token-introspection) is also included with Rafiki. This is an internal package that requires no action on your part if you’re using Rafiki’s `auth` service.
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/environment-variables.mdx b/packages/documentation/src/content/docs/v1-beta/resources/environment-variables.mdx
new file mode 100644
index 0000000000..076fec6971
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/environment-variables.mdx
@@ -0,0 +1,219 @@
+---
+title: Environment variables
+slug: v1-beta/resources/environment-variables
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+import VarWarn from '/src/partials/variables-warning.mdx'
+
+Environment variables are key value pairs used to configure how your Rafiki instance will run within your infrastructure and integrate with your systems.
+
+Each environment variable name is uppercase, followed by an equal sign and the value of the variable.
+
+```bash title='Environment variable example'
+WEBHOOKS_URL=http://my-business/webhooks
+```
+
+The environment variable in the preceding example specifies the HTTP endpoint at which you want your Rafiki instance to send you notifications of webhook events.
+
+To run Rafiki you must set the environment variables for the `backend`, `auth` and `frontend` services where listed as required below.
+
+
+
+## Backend
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| `AUTH_SERVER_GRANT_URL` | `backend.serviceUrls.AUTH_SERVER_GRANT_URL` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. |
+| `AUTH_SERVER_INTROSPECTION_URL` | `backend.serviceUrls.AUTH_SERVER_INTROSPECTION_URL` | _undefined_ | The endpoint on your Open Payments authorization server to introspect an access token. |
+| `DATABASE_URL` | `backend.postgresql.host`, `backend.postgresql.port`, `backend.postgresql.username`, `backend.postgresql.database`, `backend.postgresql.password` | `postgresql://postgres:password@localhost:5432/development` | The Postgres database URL of the database storing your resource data. For Helm, these components are provided individually. |
+| `EXCHANGE_RATES_URL` | `backend.serviceUrls.EXCHANGE_RATES_URL` | _undefined_ | The endpoint your Rafiki instance uses to request exchange rates. |
+| `ILP_ADDRESS` | `backend.ilp.address` | _undefined_ | The ILP address of your Rafiki instance. |
+| `ILP_CONNECTOR_URL` | `backend.ilp.connectorUrl` | _undefined_ | The ILP connector address where ILP packets are received. |
+| `KEY_ID` | `backend.key.id` | _undefined_ | Your Rafiki instance’s client key ID. |
+| `OPEN_PAYMENTS_URL` | `backend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | The public endpoint of your Open Payments resource server. |
+| `REDIS_URL` | `backend.redis.host`, `backend.redis.port` | `redis://127.0.0.1:6379` | The Redis URL of the database handling ILP packet data. For Helm, these components are provided individually. |
+| `USE_TIGERBEETLE` | `backend.use.tigerbeetle` | `true` | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
+| `WEBHOOK_URL` | `backend.serviceUrls.WEBHOOK_URL` | _undefined_ | Your endpoint that consumes webhook events. |
+| `AUTH_SERVICE_API_URL` | `backend.serviceUrls.AUTH_SERVICE_API_URL` | _undefined_ | The service-to-service api endpoint on your Open Payments authorization server. |
+
+
+
+### Conditionally required
+
+
+
+| Variable | Helm value name | Default | Description |
+| --------------- | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `INSTANCE_NAME` | `backend.instance.name` | _undefined_ | Your Rafiki instance's name used to communicate for auto-peering and/or [telemetry](/overview/concepts/telemetry). Required when auto-peering and/or telemetry is enabled |
+| `TRUST_PROXY` | `backend.trustProxy` | `false` | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| ----------------------------------------------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ADMIN_PORT` | `backend.port.admin` | `3001` | The port of your Backend Auth API server. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The TTL, in seconds, for which a request’s signature will be valid. |
+| `API_SECRET` | _undefined_ | _undefined_ | N/A |
+| `API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the request signing algorithm used to generate signatures. |
+| `AUTO_PEERING_SERVER_PORT` | `backend.autoPeering.serverPort` | `3005` | If auto-peering is enabled, the server will use this port. |
+| `CONNECTOR_PORT` | `backend.port.connector` | `3002` | The port of the ILP connector for sending packets via ILP over HTTP. |
+| `ENABLE_AUTO_PEERING` | `backend.enable.autoPeering` | `false` | When `true`, auto-peering is enabled. |
+| `ENABLE_MANUAL_MIGRATIONS` | `backend.enableManualMigrations` | `false` | When `true`, you must run the database manually with the command `npm run knex – migrate:latest –env production` |
+| `ENABLE_SPSP_PAYMENT_POINTERS` | `backend.enable.spspPaymentPointers` | `true` | When `true`, the SPSP route is enabled. |
+| `ENABLE_TELEMETRY` | _undefined_ | `false` | Enables the telemetry service on Rafiki. |
+| `ENABLE_TELEMETRY_TRACES` | _undefined_ | `false` | N/A |
+| `EXCHANGE_RATES_LIFETIME` | `backend.lifetime.exchangeRate` | `15_000` | The time, in milliseconds, the exchange rates you provide via the `EXCHANGE_RATES_URL` are valid. |
+| `GRAPHQL_IDEMPOTENCY_KEY_LOCK_MS` | `backend.idempotency.keyLockMs` | `2000` | The TTL, in milliseconds, for `idempotencyKey` concurrency lock on GraphQL mutations on the Backend Admin API. |
+| `GRAPHQL_IDEMPOTENCY_KEY_TTL_MS` | `backend.idempotency.keyTTL` | `86400000` (24 hours) | The TTL, in milliseconds, for `idempotencyKey` on GraphQL mutations on the Backend Admin API. |
+| `INCOMING_PAYMENT_CREATED_POLL_FREQUENCY_MS` | _undefined_ | `1000` | N/A |
+| `INCOMING_PAYMENT_CREATED_POLL_TIMEOUT_MS` | _undefined_ | `10000` | N/A |
+| `INCOMING_PAYMENT_EXPIRY_MAX_MS` | `backend.incomingPayment.expiryMaxMs` | `2592000000` (30 days) | The maximum into the future, in milliseconds, incoming payments expiry can be set to on creation. |
+| `INCOMING_PAYMENT_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `INCOMING_PAYMENT_WORKERS` will wait until checking an empty incoming payment request queue again. |
+| `INCOMING_PAYMENT_WORKERS` | `backend.workers.incomingPayment` | `1` | The number of workers processing incoming payment requests. |
+| `LOG_LEVEL` | `backend.logLevel` | `info` | Pino log level |
+| `MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS` | _undefined_ | `5` | Specifies how many times an outgoing payment is retried before failing completely |
+| `NODE_ENVIRONMENT` | `backend.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
+| `OPEN_PAYMENTS_PORT` | `backend.port.openPayments` | `3003` | The port of your Open Payments resource server. |
+| `OPEN_TELEMETRY_COLLECTOR_URLS` | _undefined_ | \*undefined | N/A |
+| `OPEN_TELEMETRY_EXPORT_INTERVAL` | _undefined_ | `15000` | N/A |
+| `OPEN_TELEMETRY_TRACE_COLLECTOR_URLS` | _undefined_ | _undefined_ | N/A |
+| `OUTGOING_PAYMENT_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `OUTGOING_PAYMENT_WORKERS` wait until they check an empty outgoing payment request queue again. |
+| `OUTGOING_PAYMENT_WORKERS` | `backend.workers.outgoingPayment` | `4` | The number of workers processing outgoing payment requests. |
+| `POLL_INCOMING_PAYMENT_CREATED_WEBHOOK` | _undefined_ | `false` | N/A |
+| `PRIVATE_KEY_FILE` | `backend.key.file` | _undefined_ | The path to your Rafiki instance’s client private key. |
+| `QUOTE_LIFESPAN` | `backend.lifetime.quote` | `5 * 60_000` (5 minutes) | The time, in milliseconds, an Open Payments quote is valid for. |
+| `REDIS_TLS_CA_FILE_PATH` | `backend.redis.tlsCaFile` | `''` | Redis TLS config |
+| `REDIS_TLS_CERT_FILE_PATH` | `backend.redis.tlsCertFile` | `''` | Redis TLS config |
+| `REDIS_TLS_KEY_FILE_PATH` | `backend.redis.tlsKeyFile` | `''` | Redis TLS config |
+| `SIGNATURE_SECRET` | `backend.quoteSignatureSecret` | _undefined_ | The secret to generate request header signatures for webhook event requests. |
+| `SIGNATURE_VERSION` | `backend.signatureVersion` | `1` | The version number to generate request header signatures for webhook events. |
+| `SLIPPAGE` | `backend.ilp.slippage` | `0.01` (1%) | The accepted ILP rate fluctuation. |
+| `STREAM_SECRET` | `backend.ilp.streamSecret` | _undefined_ | The seed secret to generate shared STREAM secrets. |
+| `TELEMETRY_EXCHANGE_RATES_LIFETIME` | _undefined_ | `86_400_000` | N/A |
+| `TELEMETRY_EXCHANGE_RATES_URL` | _undefined_ | `https://telemetry-exchange-rates.s3.amazonaws.com/exchange-rates-usd.json` | The endpoint Rafiki will query for exchange rates. Used as a fallback if/when [exchange rates](/integration/requirements/exchange-rates) aren’t provided. |
+| `TIGERBEETLE_CLUSTER_ID` | _undefined_ | `0` | The TigerBeetle cluster ID picked by the system that starts the TigerBeetle cluster to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES` | _undefined_ | `3004` | TigerBeetle replica addresses for all replicas in the cluster. The addresses are comma-separated IP addresses/ports, to create a TigerBeetle client. |
+| `TIGERBEETLE_REPLICA_ADDRESSES.SPLIT` | _undefined_ | `3004` | N/A |
+| `TIGERBEETLE_TWO_PHASE_TIMEOUT_SECONDS` | _undefined_ | `5` | N/A |
+| `WALLET_ADDRESS_DEACTIVATION_PAYMENT_GRACE_PERIOD_MS` | `backend.walletAddress.deactivationPaymentGratePeriodMs` | `86400000` (24 hours) | The time into the future, in milliseconds, to set expiration of Open Payments incoming payments when deactivating a wallet address. |
+| `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | `backend.walletAddress.lookupTimeoutMs` | `1500` | The time, in milliseconds, you have to create a missing wallet address before timeout. |
+| `WALLET_ADDRESS_POLLING_FREQUENCY_MS` | `backend.walletAddress.pollingFrequencyMs` | `100` | The frequency of polling while waiting for you to create a missing wallet address. |
+| `WALLET_ADDRESS_URL` | `backend.serviceUrls.WALLET_ADDRESS_URL` | `http://127.0.0.1:3001/.well-known/pay` | Your Rafiki instance’s internal wallet address. |
+| `WALLET_ADDRESS_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `WALLET_ADDRESS_WORKERS` wait until checking the empty wallet address request queue again. |
+| `WALLET_ADDRESS_WORKERS` | `backend.workers.walletAddress | `1` | The number of workers processing wallet address requests. |
+| `WEBHOOK_MAX_RETRY` | `backend.webhookMaxRetry` | `10` | The maximum number of times your Rafiki instance’s backend retries sending a certain webhook event to your configured `WEBHOOK_URL`. |
+| `WEBHOOK_TIMEOUT` | `backend.lifetime.webhook` | `2000` (2 seconds) | The time, in milliseconds, that your Rafiki instance will wait for a `200` response from your webhook endpoint. If a `200` response is not received, Rafiki will time out and try to send the webhook event again. |
+| `WEBHOOK_WORKER_IDLE` | `backend.workerIdle` | `200` | The time, in milliseconds, that `WEBHOOK_WORKERS` will wait until they check the empty webhook event queue again. |
+| `WEBHOOK_WORKERS` | `backend.workers.webhook` | `1` | The number of workers processing webhook events. |
+| `WITHDRAWAL_THROTTLE_DELAY` | `backend.withdrawalThrottleDelay` | _undefined_ | The delay in liquidity withdrawal processing. |
+
+
+
+## Auth
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `AUTH_DATABASE_URL` | `auth.postgresql.host`, `auth.postgresql.port`, `auth.postgresql.username`, `auth.postgresql.database`, `auth.postgresql.password` | `postgresql://postgres:password@localhost:5432/auth_development` | The URL of the Postgres database storing your Open Payments grant data. For Helm, these components are provided individually. |
+| `AUTH_SERVER_URL` | `auth.server.domain` | _undefined_ | The public endpoint for your Rafiki instance’s public Open Payments routes. |
+| `COOKIE_KEY` | `auth.cookieKey` | _undefined_ | The koa KeyGrip key that is used to sign cookies for an interaction session. |
+| `IDENTITY_SERVER_URL` | `auth.identityServer.domain` | _undefined_ | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
+| `IDENTITY_SERVER_SECRET` | `auth.identityServer.secret` | _undefined_ | A shared secret between the authorization server and the IdP server; the authorization server will use the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](/integration/requirements/open-payments/idp#x-idp-secret-header) header. |
+| `REDIS_URL` | `auth.redis.host`, `auth.redis.port` | `redis://127.0.0.1:6379` | The connection URL for Redis. For Helm, these components are provided individually. |
+
+
+
+### Conditionally required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------- | ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `TRUST_PROXY` | `auth.trustProxy` | `false` | Must be set to `true` when running Rafiki behind a proxy. When `true`, the `X-Forwarded-Proto` header is used to determine if connections are secure. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| --------------------------------- | ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ACCESS_TOKEN_DELETION_DAYS` | `auth.accessToken.deletionDays` | `30` | The days until expired and/or revoked access tokens are deleted. |
+| `ACCESS_TOKEN_EXPIRY_SECONDS` | `auth.accessToken.expirySeconds` | `600` (10 minutes) | The expiry time, in seconds, for access tokens. |
+| `ADMIN_API_SIGNATURE_VERSION` | `auth.adminApi.signatureVersion` | `1` | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | `auth.adminAPI.signatureTtlSeconds` | `30` | The TTL, in seconds, for which a request’s signature will be valid. |
+| `ADMIN_PORT` | `auth.port.admin` | `3003` | The port of your Rafiki Auth Admin API server. |
+| `AUTH_PORT` | `auth.port.auth` | `3006` | The port of your Open Payments authorization server. |
+| `DATABASE_CLEANUP_WORKERS` | `auth.workers.cleanup` | `1` | The number of workers processing expired or revoked access tokens. |
+| `ENABLE_MANUAL_MIGRATIONS` | `auth.enableManualMigrations` | `false` | When `true`, you must run the auth Postgres database manually with the command `npm run knex – migrate:latest –envproduction` |
+| `INCOMING_PAYMENT_INTERACTION` | `auth.interaction.incomingPayment` | `false` | When `true`, incoming Open Payments grant requests are interactive |
+| `INTERACTION_EXPIRY_SECONDS` | `auth.interactionExpirySeconds` | `600` (10 minutes) | The time, in seconds, for which a user can interact with a grant request before the request expires. |
+| `INTERACTION_PORT` | `auth.port.interaction` | `3009` | The port number of your Open Payments interaction-related APIs. |
+| `INTROSPECTION_PORT` | `auth.port.introspection` | `3007` | The port of your Open Payments access token introspection server. |
+| `SERVICE_API_PORT` | `auth.port.serviceAPIPort` | `3011` | The port to expose the internal service api. |
+| `LIST_ALL_ACCESS_INTERACTION` | `auth.interaction.listAll` | `true` | When `true`, grant requests that include a `list-all` action will require interaction. In these requests, the client asks to list resources that it did not create. |
+| `LOG_LEVEL` | `auth.logLevel` | `info` | Pino log level |
+| `NODE_ENV` | `auth.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
+| `QUOTE_INTERACTION` | `auth.interaction.quote` | `false` | When `true`, quote grants are interactive. |
+| `REDIS_TLS_CA_FILE_PATH` | `auth.redis.tlsCaFile` | `''` | Redis TLS config |
+| `REDIS_TLS_CERT_FILE_PATH` | `auth.redis.tlsCertFile` | `''` | Redis TLS config |
+| `REDIS_TLS_KEY_FILE_PATH` | `auth.redis.tlsKeyFile` | `''` | Redis TLS config |
+| `WAIT_SECONDS` | `auth.grant.waitSeconds` | `5` | The wait time, in seconds, included in a grant request response (`grant.continue`). |
+
+
+
+## Frontend
+
+### Required
+
+
+
+| Variable | Helm value name | Default | Description |
+| ------------------- | ---------------------------------------- | ----------- | -------------------------------------------- |
+| `GRAPHQL_URL` | `frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki’s GraphQL Auth Admin API |
+| `OPEN_PAYMENTS_URL` | `frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint |
+| `PORT` | `frontend.port` | _undefined_ | Port from which to host the Rafiki Remix app |
+
+
+
+### Conditionally required
+
+The following variables are required only when `AUTH_ENABLED` is set to `true`.
+
+
+
+| Variable | Helm value name | Default | Description |
+| ----------------------------- | ------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `KRATOS_ADMIN_URL` | `frontend.kratos.adminUrl` | _undefined_ | The admin endpoint/container address for Kratos |
+| `KRATOS_CONTAINER_PUBLIC_URL` | `frontend.kratos.containerPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from within the Docker network. This is used for backend calls to Kratos. |
+| `KRATOS_BROWSER_PUBLIC_URL` | `frontend.kratos.browserPublicUrl` | _undefined_ | The URL for you to access the Kratos Docker container from a browser outside of the Docker network. This is used for calls from a browser (what you see in the Rafiki Admin UI) to the Kratos server on the backend. |
+
+
+
+### Optional
+
+
+
+| Variable | Helm value name | Default | Description |
+| -------------------------------- | -------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `AUTH_ENABLED` | `frontend.authEnabled` | `true` | When `true`, only authenticated users can be granted access to Rafiki Admin by an administrator |
+| `SIGNATURE_SECRET` | `frontend.quoteSignatureSecret` | _undefined_ | The signature secret used to authenticate requests to the Backend Admin API. |
+| `SIGNATURE_VERSION` | `frontend.signatureVersion` | `1` | The signature version number used to authenticate requests to the Backend Admin API. |
+| `ENABLE_INSECURE_MESSAGE_COOKIE` | `frontend.enableInsecureMessageCookie` | `true` | When set to `true`, `t`, or `1`, cookie will be transmitted over insecure HTTP connection. Insecure message cookies are required for flash messages to work over HTTP. |
+| `NODE_ENV` | `frontend.nodeEnv` | `production` | The type of node environment: `development`, `test`, or `production`. |
+| `LOG_LEVEL` | `frontend.logLevel` | `info` | Pino log level |
+
+
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/get-involved.mdx b/packages/documentation/src/content/docs/v1-beta/resources/get-involved.mdx
new file mode 100644
index 0000000000..7c26e8258f
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/get-involved.mdx
@@ -0,0 +1,95 @@
+---
+title: Get involved
+slug: v1-beta/resources/get-involved
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+Welcome to the Rafiki community! Whether you're a seasoned developer or just getting started with open source, there are many ways to contribute to the Rafiki project. This guide will help you find your place in our vibrant ecosystem.
+
+## 🚀 Get started
+
+### New to Rafiki?
+
+- **Explore the project**: Browse the Rafiki repository to understand the codebase structure and recent activity.
+
+- **Join our community**:
+
+ - Connect with us on `interledger.slack.com` in the `#rafiki` channel for real-time discussions
+ - Start or join GitHub Discussions for deeper technical conversations
+
+- **Get hands-on**: Try [running the system locally](/v1-beta/integration/playground/overview) to familiarize yourself with Rafiki's functionality. Don't hesitate to ask questions in the `#rafiki` Slack channel!
+
+### Make your first contribution
+
+Ready to dive in? Here's how to review good first issues on GitHub if you are new to open source development.
+
+#### 1: Navigate to the issues tab
+
+Go to good first issues in the Rafiki repo.
+
+#### 2: Check prerequisites
+
+Make sure to review our comprehensive contribution guide
+
+Before claiming an issue, ensure you have:
+
+- Read the issue description and comments
+- Reviewed any linked documentation or related issues
+- Understood the expected deliverables
+- Confirmed the issue hasn't been resolved in a recent pull request
+
+## 👨💻 Coding
+
+- Contribute to Rafiki
+ coding or testing.
+
+## 📚 Documentation & learning
+
+### Contribute to our documentation
+
+- Improve existing documentation clarity and accuracy
+- Create step-by-step tutorials for common use cases
+- Develop troubleshooting guides and FAQ sections
+- Translate documentation to the following languages:
+ - Arabic
+ - Chinese
+ - French
+ - German
+ - Japanese
+ - Portuguese
+ - Spanish
+- Review translated and localized content
+
+### Content creation
+
+- Write blog posts about your Rafiki integration experiences
+- Create video tutorials or demos
+- Develop sample applications and code examples
+- Share best practices and patterns you've discovered
+
+## 🌟 Community building
+
+### Events and engagement
+
+- Attend the Interledger Summit and participate in our annual hackathon
+- Organize local meetups, coding sprints, and workshops
+- Host online webinars or demo sessions
+- Mentor new contributors and help them get started
+
+### Hackathons
+
+- Participate in existing hackathons using Rafiki
+- Develop innovative applications and integrations
+- Create proof-of-concept projects that showcase Rafiki's capabilities
+- Collaborate with others on experimental features
+
+## 🤝 Getting help
+
+Stuck on something? We're here to help:
+
+- Ask questions in the `#rafiki` Slack channel
+- Start a discussion on GitHub for broader topics
+- Join our community calls
+
+Remember, every contribution matters - whether it's a small documentation fix, a bug report, or a major feature implementation. We're excited to have you as part of the Rafiki community!
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/glossary.mdx b/packages/documentation/src/content/docs/v1-beta/resources/glossary.mdx
new file mode 100644
index 0000000000..04077ba69c
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/glossary.mdx
@@ -0,0 +1,98 @@
+---
+title: Glossary
+slug: v1-beta/resources/glossary
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+## Account servicing entity (ASE)
+
+An entity that provides and maintains a payment account for a payer and/or payee. An ASE is a regulated entity within the country or countries it operates. Examples include digital wallets, banks, and mobile money providers. Non-regulated entities should not use Rafiki in production environments due to the potential legal and compliance risks involved.
+
+## Auth service
+
+A reference implementation of an Open Payments authorization server in Rafiki. The `auth` service manages grant authorization and authentication, allowing clients (for example, third-party applications) to create payments and quotes. It issues access tokens and validates client access rights through communication with the resource server.
+
+## Authorization server
+
+A server that grants delegated authorization and privileges, via [GNAP](#grant-negotiation-and-authorization-protocol-gnap), to a particular instance of client software in the form of access tokens, allowing the client to call the Open Payments APIs.
+
+We’ve provided an opinionated version of a GNAP authorization server via the `auth` service, meaning that we’ve made certain decisions regarding the implementation and configuration of the server that may limit customization but ensure consistency and adherence to preferred practices.
+
+## Backend service
+
+The core service in Rafiki responsible for managing business logic and external communication. The `backend` service exposes Open Payments API endpoints for account management, operates an Interledger connector for STREAM packet exchange, and provides a GraphQL Backend Admin API for managing accounts and settings.
+
+## Client
+
+An application or service, such as a mobile or web app, that interacts with the authorization server to obtain grants and access tokens. Clients use tokens to access resource servers and perform actions, such as retrieving transaction history and setting up payments, on behalf of a user or system.
+
+## Frontend service
+
+An optional internal interface in Rafiki, known as the Rafiki Admin, used to manage your Rafiki instance. The `frontend` service communicates with the Backend Admin API through a Remix web app, facilitating administrative tasks within the Rafiki environment.
+
+## Grant Negotiation and Authorization Protocol (GNAP)
+
+The Grant Negotiation Authorization Protocol (GNAP) defines a mechanism for delegating authorization to a piece of software (client), and conveying the results and artifacts of that delegation to the software. This delegation can include access to a set of APIs as well as subject information passed directly to the software. For more information, refer to the GNAP specification.
+
+## Grant
+
+A delegation of authorization from a resource owner to a client, allowing the client to access protected resources or perform actions on the owner’s behalf. In Rafiki, this process is managed by the authorization server, which issues grants as access tokens. These grants permit clients to interact with Open Payments APIs to, for example, create payments and retrieve account information, based on the permissions granted by the resource owner.
+
+## Identity provider (IdP)
+
+A system or service that stores and manages user identity information, authentication, and consent. Due to Rafiki's implementation of the Open Payments standard, Rafiki requires integration with an IdP to support interactive Open Payments grants.
+
+## Incoming payment
+
+An object created by the recipient’s ASE, on their resource server, that represents a payment being received. The object contains information about the incoming payment, such as the amount, currency, receiver’s wallet address, and payment status. The object is used to track and manage payments that are expected to be or have been received.
+
+## Interledger Protocol (ILP)
+
+An open protocol stack designed to facilitate the transfer of value across different currencies, platforms, and payment networks. Rafiki is a reference implementation of the Interledger stack, allowing you to join the Interledger network and quickly enable Interledger capabilities on your users’ accounts. For more information, refer to the Interledger specification.
+
+## ILP packet
+
+A unit of data that carries payment information through the Interledger network. A single payment can be broken into smaller packets of value which are then routed across the network.
+
+## Open Payments
+
+An API standard and a set of APIs that allows clients to securely retrieve account information and initiate payments from your customers’ accounts with their consent. By adhering to this standard, Rafiki enables integration with external applications and supports the secure and uniform management of payments, quotes, and account data through the Open Payments APIs. For more information, visit the Open Payments documentation.
+
+## Outgoing payment
+
+An object created by the sender’s ASE, on their resource server, that represents a payment being sent. This object contains information about the outgoing payment, such as the amount, currency, receiver’s wallet address, and payment status.
+
+## Payment pointer
+
+A type of wallet address that serves as an SPSP endpoint to facilitate sending and receiving ILP packets. Payment pointers can be written out using the `$` shorthand (for example, `$wallet.example.com/alice`) or as a URL (for example, `https://wallet.example.com/alice`).
+
+## Peer
+
+A counterparty with whom you transact with over the Interledger network. Your Rafiki instance holds liquidity accounts for each of your peers.
+
+## Quote
+
+An object created by the sender’s ASE, on their resource server, that represents the total cost for the sender to send a payment. When a quote is created, it serves as a commitment from the sender's ASE to deliver the amount to the recipient's ASE and is only valid for a limited time.
+
+## Resource server
+
+A server that hosts and manages access to protected Open Payments resources for incoming payments, quotes, and outgoing payments.
+
+Rafiki's `backend` service runs an Open Payments resource server.
+
+## Simple Payment Setup Protocol (SPSP)
+
+An Interledger application layer protocol for exchanging payment information between two counterparties to facilitate direct payments over Interledger. The information is then used to set up a STREAM connection. You can read more about SPSP in its specification.
+
+## Streaming Transport for the Real-Time Exchange of Assets and Messages (STREAM)
+
+An Interledger transport layer protocol for sending and receiving authenticated ILP packets between peers and determining the path exchange rate. See the STREAM specification for more information.
+
+## Wallet address
+
+A secure, unique URL that identifies an Open Payments-enabled account. It acts as an entry point to the Open Payments APIs, facilitating interactions like sending and receiving payments. Similar to how an email address serves as a public identifier for an email account, a wallet address is publicly shareable and used to interact with the underlying payment account without compromising its security. Wallet address URLs are treated as case-insensitive, meaning that both lowercase and uppercase variations of the same address will be recognized as identical.
+
+## Web Monetization
+
+A browser API that allows websites to signal their ability to receive Web Monetization payments from their site visitors. Rafiki supports Web Monetization payment flows natively, enabling seamless integration for websites looking to implement the feature and monetize their content via the Interledger network. For more details, visit the Web Monetization website.
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/releases.mdx b/packages/documentation/src/content/docs/v1-beta/resources/releases.mdx
new file mode 100644
index 0000000000..021dc73929
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/releases.mdx
@@ -0,0 +1,26 @@
+---
+title: Releases
+slug: v1-beta/resources/releases
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+## Release notes
+
+Refer to the Rafiki releases page for the latest release notes.
+
+## Rafiki packages
+
+The latest sources for each of the Rafiki [services](/v1-beta/resources/architecture) are available through GitHub.
+
+-
+ Backend
+
+-
+ Auth
+
+-
+ Frontend
+
+
+Alternatively, if you want to run the latest Rafiki version using Docker Compose, then refer to this [guide](/v1-beta/integration/deployment/docker-compose).
diff --git a/packages/documentation/src/content/docs/v1-beta/resources/webhook-event-types.mdx b/packages/documentation/src/content/docs/v1-beta/resources/webhook-event-types.mdx
new file mode 100644
index 0000000000..8c065c497d
--- /dev/null
+++ b/packages/documentation/src/content/docs/v1-beta/resources/webhook-event-types.mdx
@@ -0,0 +1,25 @@
+---
+title: Webhook event types
+slug: v1-beta/resources/webhook-event-types
+---
+
+Webhooks notify you of specific events that occur within your Rafiki instance, allowing you to integrate Interledger payments with your system and business processes. For example, Rafiki can notify you when one of your account holders has received an Interledger payment, at which point you would credit their account on your ledger.
+
+The following is an enumeration of all of Rafiki's [webhook event](/v1-beta/integration/requirements/webhook-events) types along with their descriptions, which you must listen to and handle.
+
+
+
+| Value | Description |
+| ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| [`incoming_payment.created`](/v1-beta/integration/requirements/webhook-events/#incoming-payment-created) | An incoming payment was created. |
+| [`incoming_payment.completed`](/v1-beta/integration/requirements/webhook-events/#incoming-payment-completed) | An incoming payment is complete and will not accept any additional incoming funds. |
+| [`incoming_payment.expired`](/v1-beta/integration/requirements/webhook-events/#incoming-payment-expired) | An incoming payment expired and will not accept any additional incoming funds. |
+| [`outgoing_payment.created`](/v1-beta/integration/requirements/webhook-events/#outgoing-payment-created) | An outgoing payment was created. |
+| [`outgoing_payment.completed`](/v1-beta/integration/requirements/webhook-events/#outgoing-payment-completed) | An outgoing payment completed. |
+| [`outgoing_payment.failed`](/v1-beta/integration/requirements/webhook-events/#outgoing-payment-failed) | An outgoing payment partially or completely failed. |
+| [`wallet_address.not_found`](/v1-beta/integration/requirements/webhook-events/#wallet-address-not-found) | A requested wallet address was not found. |
+| [`wallet_address.web_monetization`](/v1-beta/integration/requirements/webhook-events/#wallet-address-web-monetization) | Web Monetization payments received via STREAM. |
+| [`asset.liquidity_low`](/v1-beta/integration/requirements/webhook-events/#asset-liquidity-low) | Asset liquidity has dropped below defined threshold. |
+| [`peer.liquidity_low`](/v1-beta/integration/requirements/webhook-events/#peer-liquidity-low) | Peer liquidity has dropped below defined threshold. |
+
+
+ The Auth Admin API allows you to get information about a
+ grant, such as its status, state, related payment details, and
+ the wallet address of the grantee’s account. The API also
+ allows you to revoke grants.
+
+ The ID scalar type represents a unique
+ identifier, often used to refetch an object or as key for a
+ cache. The ID type appears in a JSON response as a String;
+ however, it is not intended to be human-readable. When
+ expected as an input type, any string (such as
+ "4") or integer (such as
+ 4) input value will be accepted as an ID.
+
+ The String scalar type represents textual data,
+ represented as UTF-8 character sequences. The String type is
+ most often used by GraphQL to represent free-form
+ human-readable text.
+
+ The UInt64 scalar type represents unsigned
+ 64-bit whole numeric values. It is capable of handling
+ values that are larger than the JavaScript
+ Number type limit (greater than 2^53).
+
+ The Backend Admin API provides you with comprehensive
+ capabilities to manage your Rafiki instance. Core
+ functionality includes managing peering relationships, assets,
+ wallet addresses and their public keys, as well as liquidity
+ management through deposits and withdrawals. Another important
+ aspect of the Backend Admin API is to manage Open Payments
+ resources like payments and quotes.
+
+
+
+
+
API Endpoints
+
# Staging:
+https://staging.example.com/graphql
+
+
+
+
+
+
+ Queries
+
+
+
+ accountingTransfers
+
+
+
+
+
Description
+
+ Fetch a paginated list of accounting transfers for a given
+ account.
+
+ Retrieve an Open Payments incoming payment by receiver ID.
+ The receiver's wallet address can be hosted on this
+ server or a remote Open Payments resource server.
+
+ Create an internal or external Open Payments incoming
+ payment. The receiver has a wallet address on either this or
+ another Open Payments resource server.
+
+ Revoke a public key associated with a wallet address. Open
+ Payment requests using this key for request signatures will
+ be denied going forward.
+
+ If automatic withdrawal of funds received via Web
+ Monetization by the wallet address are disabled, this
+ mutation can be used to trigger up to
+ n withdrawal events.
+
+ Reason why this outgoing payment has been canceled.
+ This value will be publicly visible in the metadata
+ field if this outgoing payment is requested through
+ Open Payments.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Basis points fee is a variable fee charged based on
+ the total amount. Should be between 0 and 10000
+ (inclusive). 1 basis point = 0.01%, 100 basis points =
+ 1%, 10000 basis points = 100%.
+
+ Basis points fee is a variable fee charged based on
+ the total amount. Should be between 0 and 10000
+ (inclusive). 1 basis point = 0.01%, 100 basis points =
+ 1%, 10000 basis points = 100%.
+
+ The ID scalar type represents a unique
+ identifier, often used to refetch an object or as key for a
+ cache. The ID type appears in a JSON response as a String;
+ however, it is not intended to be human-readable. When
+ expected as an input type, any string (such as
+ "4") or integer (such as
+ 4) input value will be accepted as an ID.
+
+ The String scalar type represents textual data,
+ represented as UTF-8 character sequences. The String type is
+ most often used by GraphQL to represent free-form
+ human-readable text.
+
+ The UInt64 scalar type represents unsigned
+ 64-bit whole numeric values. It is capable of handling
+ values that are larger than the JavaScript
+ Number type limit (greater than 2^53).
+
+ The Auth Admin API allows you to get information about a
+ grant, such as its status, state, related payment details, and
+ the wallet address of the grantee’s account. The API also
+ allows you to revoke grants.
+
+ The ID scalar type represents a unique
+ identifier, often used to refetch an object or as key for a
+ cache. The ID type appears in a JSON response as a String;
+ however, it is not intended to be human-readable. When
+ expected as an input type, any string (such as
+ "4") or integer (such as
+ 4) input value will be accepted as an ID.
+
+ The String scalar type represents textual data,
+ represented as UTF-8 character sequences. The String type is
+ most often used by GraphQL to represent free-form
+ human-readable text.
+
+ The UInt64 scalar type represents unsigned
+ 64-bit whole numeric values. It is capable of handling
+ values that are larger than the JavaScript
+ Number type limit (greater than 2^53).
+
+ The Backend Admin API provides you with comprehensive
+ capabilities to manage your Rafiki instance. Core
+ functionality includes managing peering relationships, assets,
+ wallet addresses and their public keys, as well as liquidity
+ management through deposits and withdrawals. Another important
+ aspect of the Backend Admin API is to manage Open Payments
+ resources like payments and quotes.
+
+
+
+
+
API Endpoints
+
# Staging:
+https://staging.example.com/graphql
+
+
+
+
+
+
+ Queries
+
+
+
+ accountingTransfers
+
+
+
+
+
Description
+
+ Fetch a paginated list of accounting transfers for a given
+ account.
+
+ Retrieve an Open Payments incoming payment by receiver ID.
+ The receiver's wallet address can be hosted on this
+ server or a remote Open Payments resource server.
+
+ Create an internal or external Open Payments incoming
+ payment. The receiver has a wallet address on either this or
+ another Open Payments resource server.
+
+ Revoke a public key associated with a wallet address. Open
+ Payment requests using this key for request signatures will
+ be denied going forward.
+
+ If automatic withdrawal of funds received via Web
+ Monetization by the wallet address are disabled, this
+ mutation can be used to trigger up to
+ n withdrawal events.
+
+ Reason why this outgoing payment has been canceled.
+ This value will be publicly visible in the metadata
+ field if this outgoing payment is requested through
+ Open Payments.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Interval in seconds after a pending transfer's created
+ at which it may be posted or voided. Zero denotes a no
+ timeout single-phase posted transfer.
+
+ Basis points fee is a variable fee charged based on
+ the total amount. Should be between 0 and 10000
+ (inclusive). 1 basis point = 0.01%, 100 basis points =
+ 1%, 10000 basis points = 100%.
+
+ Basis points fee is a variable fee charged based on
+ the total amount. Should be between 0 and 10000
+ (inclusive). 1 basis point = 0.01%, 100 basis points =
+ 1%, 10000 basis points = 100%.
+
+ The ID scalar type represents a unique
+ identifier, often used to refetch an object or as key for a
+ cache. The ID type appears in a JSON response as a String;
+ however, it is not intended to be human-readable. When
+ expected as an input type, any string (such as
+ "4") or integer (such as
+ 4) input value will be accepted as an ID.
+
+ The String scalar type represents textual data,
+ represented as UTF-8 character sequences. The String type is
+ most often used by GraphQL to represent free-form
+ human-readable text.
+
+ The UInt64 scalar type represents unsigned
+ 64-bit whole numeric values. It is capable of handling
+ values that are larger than the JavaScript
+ Number type limit (greater than 2^53).
+
+
+
diff --git a/packages/documentation/src/partials/auth-variables-docker-compose.mdx b/packages/documentation/src/partials/auth-variables-docker-compose.mdx
index 4249734d5a..9386f2d8bc 100644
--- a/packages/documentation/src/partials/auth-variables-docker-compose.mdx
+++ b/packages/documentation/src/partials/auth-variables-docker-compose.mdx
@@ -4,16 +4,18 @@ import { LinkOut } from '@interledger/docs-design-system'
| Variable | Required | Description |
| --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `ADMIN_API_SECRET` | Y | Operator API secret used to sign Auth Admin API requests (HMAC SHA‑256). Set to a strong, random value. Synced to the operator tenant on startup. |
| `AUTH_DATABASE_URL` | Y | The URL of the Postgres database storing your Open Payments grant data. |
| `AUTH_SERVER_URL` | Y | The public endpoint for your Rafiki instance’s public Open Payments routes. |
| `COOKIE_KEY` | Y | The koa KeyGrip key that is used to sign cookies for an interaction session. |
| `IDENTITY_SERVER_SECRET` | Y | A shared secret between the authorization server and the IdP server; the authorization server will use the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](/integration/requirements/open-payments/idp#x-idp-secret-header) header. |
| `IDENTITY_SERVER_URL` | Y | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
+| `OPERATOR_TENANT_ID` | Y | The unique identifier of the operator. Must be a UUID v4 generated by the operator. |
| `REDIS_URL` | Y | The connection URL for Redis. |
| `ACCESS_TOKEN_DELETION_DAYS` | N | The days until expired and/or revoked access tokens are deleted. |
| `ACCESS_TOKEN_EXPIRY_SECONDS` | N | The expiry time, in seconds, for access tokens. |
-| `ADMIN_API_SIGNATURE_TTL_SECONDS` | N | The TTL, in seconds, for which a request’s signature will be valid. |
-| `ADMIN_API_SIGNATURE_VERSION` | N | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | N | The TTL, in seconds, for which an Auth Admin API request signature is valid. |
+| `ADMIN_API_SIGNATURE_VERSION` | N | The version of the HMAC SHA-256 request-signing algorithm used by the Auth Admin API. |
| `ADMIN_PORT` | N | The port of your Rafiki Auth Admin API server. |
| `AUTH_PORT` | N | The port of your Open Payments authorization server. |
| `INCOMING_PAYMENT_INTERACTION` | N | When `true`, incoming Open Payments grant requests are interactive. |
diff --git a/packages/documentation/src/partials/auth-variables.mdx b/packages/documentation/src/partials/auth-variables.mdx
index 399f5b38c5..a605b1597f 100644
--- a/packages/documentation/src/partials/auth-variables.mdx
+++ b/packages/documentation/src/partials/auth-variables.mdx
@@ -6,11 +6,13 @@ import { LinkOut } from '@interledger/docs-design-system'
| Variable | Helm value name | Default | Description |
| ------------------------ | --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `ADMIN_API_SECRET` | _undefined_ | _undefined_ | Operator API secret used to sign Auth Admin API requests (HMAC SHA‑256). Set to a strong, random value. Synced to the operator tenant on startup. |
| `AUTH_DATABASE_URL` | `config.auth.databaseUrl.value` or `config.auth.databaseUrl.secretKeyRef` | `postgresql://postgres:password@localhost:5432/auth_development` | The URL of the Postgres database storing your Open Payments grant data. Can be provided as a value or secret reference. |
| `AUTH_SERVER_URL` | `config.auth.authServerUrl` | _undefined_ | The public endpoint for your Rafiki instance's public Open Payments routes. |
| `COOKIE_KEY` | `config.auth.cookieKey.value` or `config.auth.cookieKey.secretKeyRef` | _undefined_ | The koa KeyGrip key that is used to sign cookies for an interaction session. |
| `IDENTITY_SERVER_URL` | `config.auth.identityServer.domain` | _undefined_ | The URL of your IdP's server, used by the authorization server to inform an Open Payments client of where to redirect the end-user to start interactions. |
| `IDENTITY_SERVER_SECRET` | `config.auth.identityServer.serverSecret.value` or `config.auth.identityServer.serverSecret.secretKeyRef` | _undefined_ | A shared secret between the authorization server and the IdP server; the authorization server will use the secret to secure its IdP-related endpoints. When the IdP server sends requests to the authorization server, the IdP server must provide the secret via an [`x-idp-secret`](/integration/requirements/open-payments/idp#x-idp-secret-header) header. |
+| `OPERATOR_TENANT_ID` | _undefined_ | _undefined_ | The unique identifier of the operator. Must be a UUID v4 generated by the operator. |
| `REDIS_URL` | `config.auth.redisUrl.value` or `config.auth.redisUrl.secretKeyRef` | `redis://127.0.0.1:6379` | The connection URL for Redis. Can be provided as a value or secret reference. |
@@ -33,8 +35,8 @@ import { LinkOut } from '@interledger/docs-design-system'
| --------------------------------- | ----------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ACCESS_TOKEN_DELETION_DAYS` | `config.auth.accessToken.deletionDays` | `30` | The days until expired and/or revoked access tokens are deleted. |
| `ACCESS_TOKEN_EXPIRY_SECONDS` | `config.auth.accessToken.expirySeconds` | `600` (10 minutes) | The expiry time, in seconds, for access tokens. |
-| `ADMIN_API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the request signing algorithm used to generate signatures. |
-| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The time to live (TTL), in seconds, for which a request's signature will be valid. |
+| `ADMIN_API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the HMAC SHA-256 request-signing algorithm used by the Auth Admin API. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The TTL, in seconds, for which an Auth Admin API request signature is valid. |
| `ADMIN_PORT` | `config.auth.port.admin` | `3003` | The port of your Rafiki Auth Admin API server. |
| `AUTH_PORT` | `config.auth.port.auth` | `3006` | The port of your Open Payments authorization server. |
| `DATABASE_CLEANUP_WORKERS` | `config.auth.workers.cleanup` | `1` | The number of workers processing expired or revoked access tokens. |
@@ -43,7 +45,7 @@ import { LinkOut } from '@interledger/docs-design-system'
| `INTERACTION_EXPIRY_SECONDS` | _undefined_ | `600` (10 minutes) | The time, in seconds, for which a user can interact with a grant request before the request expires. |
| `INTERACTION_PORT` | _undefined_ | `3009` | The port number of your Open Payments interaction-related APIs. |
| `INTROSPECTION_PORT` | `config.auth.port.introspection` | `3007` | The port of your Open Payments access token introspection server. |
-| `SERVICE_API_PORT` | _undefined_ | `3011` | The port to expose the internal service api. |
+| `INTERACTION_COOKIE_SAME_SITE` | `config.auth.interaction.cookieSameSite` | _undefined_ | The SameSite attribute for interaction cookies. Valid values: `lax`, `none`. |
| `LIST_ALL_ACCESS_INTERACTION` | _undefined_ | `true` | When `true`, grant requests that include a `list-all` action will require interaction. In these requests, the client asks to list resources that it did not create. |
| `LOG_LEVEL` | `config.auth.logLevel` | `info` | Pino log level |
| `NODE_ENV` | `config.auth.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
@@ -51,8 +53,7 @@ import { LinkOut } from '@interledger/docs-design-system'
| `REDIS_TLS_CA_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
| `REDIS_TLS_CERT_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
| `REDIS_TLS_KEY_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
+| `SERVICE_API_PORT` | _undefined_ | `3011` | The port to expose the internal service API for receiving tenant information changes. |
| `WAIT_SECONDS` | `config.auth.grant.waitSeconds` | `5` | The wait time, in seconds, included in a grant request response (`grant.continue`). |
-| `ADMIN_API_SECRET` | _undefined_ | _undefined_ | The secret for the auth admin API authentication. |
-| `INTERACTION_COOKIE_SAME_SITE` | `config.auth.interaction.cookieSameSite` | _undefined_ | The SameSite attribute for interaction cookies. Valid values: `lax`, `none`. |
diff --git a/packages/documentation/src/partials/backend-variables-docker-compose.mdx b/packages/documentation/src/partials/backend-variables-docker-compose.mdx
index 504ac4d3d8..36658cb1e5 100644
--- a/packages/documentation/src/partials/backend-variables-docker-compose.mdx
+++ b/packages/documentation/src/partials/backend-variables-docker-compose.mdx
@@ -4,6 +4,8 @@ import { LinkOut } from '@interledger/docs-design-system'
| Variable | Required | Description |
| ----------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ADMIN_API_SECRET` | Y | Operator API secret used to sign Backend Admin API requests (HMAC SHA‑256). Set to a strong, random value. Synced to the operator tenant on startup. |
+| `AUTH_SERVICE_API_URL` | Y | The service-to-service API endpoint for propagating tenant information changes to the `auth` server. |
| `AUTH_SERVER_GRANT_URL` | Y | The endpoint on your Open Payments authorization server to grant a request. |
| `AUTH_SERVER_INTROSPECTION_URL` | Y | The endpoint on your Open Payments authorization server to introspect an access token. |
| `DATABASE_URL` | Y | The Postgres database URL of the database storing your resource data. |
@@ -11,13 +13,13 @@ import { LinkOut } from '@interledger/docs-design-system'
| `ILP_ADDRESS` | Y | The ILP address of your Rafiki instance. |
| `ILP_CONNECTOR_URL` | Y | The ILP connector address where ILP packets are received. |
| `KEY_ID` | Y | Your Rafiki instance’s client key ID. |
+| `OPERATOR_TENANT_ID` | Y | The unique identifier of the operator. Must be a UUID v4 generated by the operator. |
| `OPEN_PAYMENTS_URL` | Y | The public endpoint of your Open Payments resource server. |
| `REDIS_URL` | Y | The Redis URL of the database handling ILP packet data. |
| `USE_TIGERBEETLE` | Y | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
| `WEBHOOK_URL` | Y | Your endpoint that consumes webhook events. |
| `ADMIN_PORT` | N | The port of your Backend Auth API server. |
-| `API_SECRET` | N | N/A |
-| `API_SIGNATURE_VERSION` | N | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_API_SIGNATURE_VERSION` | N | The version of the request signing algorithm used to generate signatures. |
| `AUTO_PEERING_SERVER_PORT` | N | If auto-peering is enabled, the server will use this port. |
| `CONNECTOR_PORT` | N | The port of the ILP connector for sending packets via ILP over HTTP. |
| `ENABLE_AUTO_PEERING` | N | When `true`, auto-peering is enabled. |
diff --git a/packages/documentation/src/partials/backend-variables.mdx b/packages/documentation/src/partials/backend-variables.mdx
index 72540a1f8b..dd975d73b7 100644
--- a/packages/documentation/src/partials/backend-variables.mdx
+++ b/packages/documentation/src/partials/backend-variables.mdx
@@ -4,19 +4,23 @@ import { LinkOut } from '@interledger/docs-design-system'
-| Variable | Helm value name | Default | Description |
-| ------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
-| `AUTH_SERVER_GRANT_URL` | `config.backend.auth.grantUrl` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. |
-| `AUTH_SERVER_INTROSPECTION_URL` | `config.backend.auth.introspectionUrl` | _undefined_ | The endpoint on your Open Payments authorization server to introspect an access token. |
-| `DATABASE_URL` | `config.backend.databaseUrl.value` or `config.backend.databaseUrl.secretKeyRef` | `postgresql://postgres:password@localhost:5432/development` | The Postgres database URL of the database storing your resource data. Can be provided as a value or secret reference. |
-| `EXCHANGE_RATES_URL` | `config.backend.rates.url` | _undefined_ | The endpoint your Rafiki instance uses to request exchange rates. |
-| `ILP_ADDRESS` | `config.backend.ilp.address` | _undefined_ | The ILP address of your Rafiki instance. |
-| `ILP_CONNECTOR_URL` | `config.backend.ilp.connector` | _undefined_ | The ILP connector address where ILP packets are received. |
-| `KEY_ID` | `config.backend.key.id` | _undefined_ | Your Rafiki instance's client key ID. |
-| `OPEN_PAYMENTS_URL` | `config.backend.ilp.host` | _undefined_ | The public endpoint of your Open Payments resource server. |
-| `REDIS_URL` | `config.backend.redisUrl.value` or `config.backend.redisUrl.secretKeyRef` | `redis://127.0.0.1:6379` | The Redis URL of the database handling ILP packet data. Can be provided as a value or secret reference. |
-| `USE_TIGERBEETLE` | `config.backend.useTigerbeetle` | `false` | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
-| `WEBHOOK_URL` | `config.backend.webhook.url` | _undefined_ | Your endpoint that consumes webhook events. |
+| Variable | Helm value name | Default | Description |
+| ------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `ADMIN_API_SECRET` | _undefined_ | _undefined_ | Operator API secret used to sign Backend Admin API requests (HMAC SHA‑256). Set to a strong, random value. Synced to the operator tenant on startup. |
+| `AUTH_SERVICE_API_URL` | _undefined_ | _undefined_ | The service-to-service API endpoint for propagating tenant information changes to the `auth` server. |
+| `AUTH_SERVER_GRANT_URL` | `config.backend.auth.grantUrl` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. |
+| `AUTH_SERVER_GRANT_URL` | `config.backend.auth.grantUrl` | _undefined_ | The endpoint on your Open Payments authorization server to grant a request. |
+| `AUTH_SERVER_INTROSPECTION_URL` | `config.backend.auth.introspectionUrl` | _undefined_ | The endpoint on your Open Payments authorization server to introspect an access token. |
+| `DATABASE_URL` | `config.backend.databaseUrl.value` or `config.backend.databaseUrl.secretKeyRef` | `postgresql://postgres:password@localhost:5432/development` | The Postgres database URL of the database storing your resource data. Can be provided as a value or secret reference. |
+| `EXCHANGE_RATES_URL` | `config.backend.rates.url` | _undefined_ | The default exchange rates endpoint. Used if a tenant-specific rates endpoint isn’t configured. |
+| `ILP_ADDRESS` | `config.backend.ilp.address` | _undefined_ | The ILP address of your Rafiki instance. |
+| `ILP_CONNECTOR_URL` | `config.backend.ilp.connector` | _undefined_ | The ILP connector address where ILP packets are received. |
+| `KEY_ID` | `config.backend.key.id` | _undefined_ | Your Rafiki instance's client key ID. |
+| `OPEN_PAYMENTS_URL` | `config.backend.ilp.host` | _undefined_ | The public endpoint of your Open Payments resource server. |
+| `OPERATOR_TENANT_ID` | _undefined_ | _undefined_ | The unique identifier of the operator. Must be a UUID v4 generated by the operator. |
+| `REDIS_URL` | `config.backend.redisUrl.value` or `config.backend.redisUrl.secretKeyRef` | `redis://127.0.0.1:6379` | The Redis URL of the database handling ILP packet data. Can be provided as a value or secret reference. |
+| `USE_TIGERBEETLE` | `config.backend.useTigerbeetle` | `false` | When `true`, a TigerBeetle database is used for accounting. When `false`, a Postgres database is used. |
+| `WEBHOOK_URL` | `config.backend.webhook.url` | _undefined_ | The default webhook endpoint. Used if a tenant-specific webhook URL isn’t configured. |
@@ -37,10 +41,9 @@ import { LinkOut } from '@interledger/docs-design-system'
| Variable | Helm value name | Default | Description |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `ADMIN_PORT` | `config.backend.port.admin` | `3001` | The port of your Backend Auth API server. |
-| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The time to live (TTL), in seconds, for which a request's signature will be valid. |
-| `API_SECRET` | _undefined_ | _undefined_ | N/A |
-| `API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the request signing algorithm used to generate signatures. |
+| `ADMIN_PORT` | `config.backend.port.admin` | `3001` | The port of your Backend Admin API server. |
+| `ADMIN_API_SIGNATURE_TTL_SECONDS` | _undefined_ | `30` | The TTL, in seconds, for which a Backend Admin API request signature is valid. |
+| `ADMIN_API_SIGNATURE_VERSION` | _undefined_ | `1` | The version of the HMAC SHA-256 request-signing algorithm used by the Backend Admin API. |
| `AUTO_PEERING_SERVER_PORT` | `config.backend.port.autoPeering` | `3005` | If auto-peering is enabled, the server will use this port. |
| `CONNECTOR_PORT` | `config.backend.port.connector` | `3002` | The port of the ILP connector for sending packets via ILP over HTTP. |
| `ENABLE_AUTO_PEERING` | `config.backend.autoPeering.enabled` | `false` | When `true`, auto-peering is enabled. |
@@ -57,6 +60,7 @@ import { LinkOut } from '@interledger/docs-design-system'
| `INCOMING_PAYMENT_WORKER_IDLE` | _undefined_ | `200` | The time, in milliseconds, that `INCOMING_PAYMENT_WORKERS` will wait until checking an empty incoming payment request queue again. |
| `INCOMING_PAYMENT_WORKERS` | _undefined_ | `1` | The number of workers processing incoming payment requests. |
| `LOG_LEVEL` | `config.backend.logLevel` | `info` | Pino log level |
+| `LIVENET` | `config.backend.telemetry.livenet` | `false` | When `true`, enables livenet mode for production deployments. |
| `MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS` | _undefined_ | `5` | Specifies how many times an outgoing payment is retried before failing completely. |
| `NODE_ENV` | `config.backend.nodeEnv` | `development` | The type of node environment: `development`, `test`, or `production`. |
| `OPEN_PAYMENTS_PORT` | `config.backend.port.openPayments` | `3000` | The port of your Open Payments resource server. |
@@ -70,6 +74,7 @@ import { LinkOut } from '@interledger/docs-design-system'
| `REDIS_TLS_CA_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
| `REDIS_TLS_CERT_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
| `REDIS_TLS_KEY_FILE_PATH` | _undefined_ | `''` | Redis TLS config |
+| `SEND_TENANT_WEBHOOKS_TO_OPERATOR` | _undefined_ | `false` | When `true`, webhook events for non-operator tenants are also sent to the operator. This allows the operator to monitor and manage events across all tenants. |
| `SIGNATURE_SECRET` | `config.backend.webhookSignatureSecret.value` or `config.backend.webhookSignatureSecret.secretKeyRef` | _undefined_ | The secret to generate request header signatures for webhook event requests. |
| `SIGNATURE_VERSION` | _undefined_ | `1` | The version number to generate request header signatures for webhook events. |
| `SLIPPAGE` | `config.backend.slippage` | `0.01` (1%) | The accepted ILP rate fluctuation. |
@@ -82,7 +87,7 @@ import { LinkOut } from '@interledger/docs-design-system'
| `WALLET_ADDRESS_LOOKUP_TIMEOUT_MS` | _undefined_ | `1500` | The time, in milliseconds, you have to create a missing wallet address before timeout. |
| `WALLET_ADDRESS_POLLING_FREQUENCY_MS` | _undefined_ | `100` | The frequency of polling while waiting for you to create a missing wallet address. |
| `WALLET_ADDRESS_REDIRECT_HTML_PAGE` | `config.backend.walletAddressRedirectHtmlPage` | _undefined_ | Custom HTML page for wallet address redirects. |
-| `WALLET_ADDRESS_URL` | `config.backend.ilp.host` | `http://127.0.0.1:3001/.well-known/pay` | Your Rafiki instance's internal wallet address. |
+| `WALLET_ADDRESS_URL` | `config.backend.ilp.host` | `http://127.0.0.1:3001/.well-known/pay` | Internal base wallet address URL used by the `backend` service. Each tenant’s wallet address base is configured via settings and cannot be updated once set; this variable does not override tenant settings. |
| `WALLET_ADDRESS_WORKER_IDLE` | _undefined_ | `200` | The time, in milliseconds, that `WALLET_ADDRESS_WORKERS` wait until checking the empty wallet address request queue again. |
| `WALLET_ADDRESS_WORKERS` | _undefined_ | `1` | The number of workers processing wallet address requests. |
| `WEBHOOK_MAX_RETRY` | _undefined_ | `10` | The maximum number of times your Rafiki instance's backend retries sending a certain webhook event to your configured `WEBHOOK_URL`. |
@@ -90,6 +95,5 @@ import { LinkOut } from '@interledger/docs-design-system'
| `WEBHOOK_WORKER_IDLE` | _undefined_ | `200` | The time, in milliseconds, that `WEBHOOK_WORKERS` will wait until they check the empty webhook event queue again. |
| `WEBHOOK_WORKERS` | _undefined_ | `1` | The number of workers processing webhook events. |
| `WITHDRAWAL_THROTTLE_DELAY` | `config.backend.withdrawalThrottleDelay` | _undefined_ | The delay in liquidity withdrawal processing, in milliseconds. |
-| `LIVENET` | `config.backend.telemetry.livenet` | `false` | When `true`, enables livenet mode for production deployments. |
-| Variable | Helm value name | Default | Description |
-| ------------------- | ----------------------------------------------- | ----------- | ---------------------------------------- |
-| `GRAPHQL_URL` | `config.frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki's GraphQL Auth Admin API. |
-| `OPEN_PAYMENTS_URL` | `config.frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint. |
+| Variable | Helm value name | Default | Description |
+| ------------------- | ----------------------------------------------- | ----------- | ------------------------------------------- |
+| `GRAPHQL_URL` | `config.frontend.serviceUrls.GRAPHQL_URL` | _undefined_ | URL for Rafiki's GraphQL Backend Admin API. |
+| `OPEN_PAYMENTS_URL` | `config.frontend.serviceUrls.OPEN_PAYMENTS_URL` | _undefined_ | Your Open Payments API endpoint. |
@@ -36,7 +36,6 @@ The following variables are required only when `AUTH_ENABLED` is set to `true`.
| `LOG_LEVEL` | `config.frontend.logLevel` | `info` | Pino log level. |
| `NODE_ENV` | `config.frontend.nodeEnv` | _undefined_ | The type of node environment: `development`, `test`, or `production`. |
| `PORT` | `config.frontend.port` | `3010` | Port from which to host the Rafiki Remix app. |
-| `SIGNATURE_SECRET` | _undefined_ | _undefined_ | The signature secret used to authenticate requests to the Backend Admin API. |
-| `SIGNATURE_VERSION` | _undefined_ | _undefined_ | The signature version number used to authenticate requests to the Backend Admin API. |
+| `SIGNATURE_VERSION` | _undefined_ | _undefined_ | The signature version number used when HMAC-signing requests to the Backend Admin API (HMAC SHA-256). |