diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index ab67eefc..6740301e 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -151,10 +151,24 @@ export default defineConfig({
translations: { es: 'Cómo empezar' },
link: '/overview/getting-started/'
},
+ {
+ label: 'Building with Open Payments',
+ link: '/overview/for-developers/'
+ },
+ {
+ label: 'Implementing Open Payments',
+ link: '/overview/for-ases/'
+ }
+ ]
+ },
+ {
+ label: 'For Developers',
+ translations: { es: 'Para desarrolladores' },
+ items: [
{
label: 'Concepts',
translations: { es: 'Conceptos' },
- collapsed: false,
+ collapsed: true,
items: [
{
label: 'Wallet addresses',
@@ -164,7 +178,25 @@ export default defineConfig({
{
label: 'Resources',
translations: { es: 'Recursos' },
- link: '/concepts/resources/'
+ collapsed: false,
+ items: [
+ {
+ label: 'Payment resource types',
+ link: '/concepts/resources/'
+ },
+ {
+ label: 'incoming-payment',
+ link: '/concepts/resources/#incoming-payment'
+ },
+ {
+ label: 'quote',
+ link: '/concepts/resources/#quote'
+ },
+ {
+ label: 'outgoing-payment',
+ link: '/concepts/resources/#outgoing-payment'
+ }
+ ]
},
{
label: 'Authorization',
@@ -185,225 +217,262 @@ export default defineConfig({
label: 'Open Payments flow',
translations: { es: 'Flujo de Open Payments' },
link: '/concepts/op-flow/'
- }
- ]
- },
- {
- label: 'Identity and access management',
- translations: { es: 'Gestión de la identidad y el acceso' },
- collapsed: true,
- items: [
+ },
{
- label: 'Grant negotiation and authorization',
+ label: 'Identity and access management',
translations: {
- es: 'Negociación y autorización de las concesiones'
+ es: 'Gestión de la identidad y el acceso'
},
- link: '/identity/grants/'
- },
- {
- label: 'Identity providers',
- translations: { es: 'Proveedores de identidad' },
- link: '/identity/idp/'
- },
- {
- label: 'Client keys',
- translations: { es: 'Claves de cliente' },
- link: '/identity/client-keys/'
- },
- {
- label: 'HTTP signatures',
- translations: { es: 'Firmas de mensajes HTTP' },
- link: '/identity/http-signatures/'
- },
- {
- label: 'Hash verification',
- translations: { es: 'Verificación de hash' },
- link: '/identity/hash-verification/'
+ collapsed: true,
+ items: [
+ {
+ label: 'Grant negotiation and authorization',
+ translations: {
+ es: 'Negociación y autorización de las concesiones'
+ },
+ link: '/identity/grants/'
+ },
+ {
+ label: 'Identity providers',
+ translations: { es: 'Proveedores de identidad' },
+ link: '/identity/idp/'
+ },
+ {
+ label: 'Client keys',
+ translations: { es: 'Claves de cliente' },
+ link: '/identity/client-keys/'
+ },
+ {
+ label: 'HTTP signatures',
+ translations: { es: 'Firmas de mensajes HTTP' },
+ link: '/identity/http-signatures/'
+ },
+ {
+ label: 'Hash verification',
+ translations: { es: 'Verificación de hash' },
+ link: '/identity/hash-verification/'
+ }
+ ]
}
]
- }
- ]
- },
- {
- label: 'SDKs',
- collapsed: true,
- items: [
- {
- label: 'Before you begin',
- link: '/sdk/before-you-begin'
},
{
- label: 'Wallet addresses',
+ label: 'SDKs',
collapsed: true,
items: [
{
- label: 'Get wallet address info',
- link: '/sdk/wallet-get-info'
+ label: 'Before you begin',
+ link: '/sdk/before-you-begin'
},
{
- label: 'Get keys bound to a wallet address',
- link: '/sdk/wallet-get-keys'
- }
- ]
- },
- {
- label: 'Grants',
- collapsed: true,
- items: [
+ label: 'Wallet addresses',
+ collapsed: true,
+ items: [
+ {
+ label: 'Get wallet address info',
+ link: '/sdk/wallet-get-info'
+ },
+ {
+ label: 'Get keys bound to a wallet address',
+ link: '/sdk/wallet-get-keys'
+ }
+ ]
+ },
{
- label: 'Create an incoming payment grant request',
- link: '/sdk/grant-create-incoming'
+ label: 'Grants',
+ collapsed: true,
+ items: [
+ {
+ label: 'Create an incoming payment grant request',
+ link: '/sdk/grant-create-incoming'
+ },
+ {
+ label: 'Create a quote grant request',
+ link: '/sdk/grant-create-quote'
+ },
+ {
+ label: 'Create an outgoing payment grant request',
+ link: '/sdk/grant-create-outgoing'
+ },
+ {
+ label: 'Continue a grant request',
+ link: '/sdk/grant-continue'
+ },
+ {
+ label: 'Cancel a grant request',
+ link: '/sdk/grant-revoke'
+ }
+ ]
},
{
- label: 'Create a quote grant request',
- link: '/sdk/grant-create-quote'
+ label: 'Incoming payments',
+ collapsed: true,
+ items: [
+ {
+ label: 'Create an incoming payment',
+ link: '/sdk/incoming-create/'
+ },
+ {
+ label: 'List incoming payments',
+ link: '/sdk/incoming-list/'
+ },
+ {
+ label: 'Get an incoming payment',
+ link: '/sdk/incoming-get/'
+ },
+ {
+ label: 'Complete an incoming payment',
+ link: '/sdk/incoming-complete/'
+ }
+ ]
},
{
- label: 'Create an outgoing payment grant request',
- link: '/sdk/grant-create-outgoing'
+ label: 'Quotes',
+ collapsed: true,
+ items: [
+ {
+ label: 'Create a quote',
+ link: '/sdk/quote-create/'
+ },
+ {
+ label: 'Get a quote',
+ link: '/sdk/quote-get/'
+ }
+ ]
},
{
- label: 'Continue a grant request',
- link: '/sdk/grant-continue'
+ label: 'Outgoing payments',
+ collapsed: true,
+ items: [
+ {
+ label: 'Create an outgoing payment',
+ link: '/sdk/outgoing-create/'
+ },
+ {
+ label: 'List outgoing payments',
+ link: '/sdk/outgoing-list/'
+ },
+ {
+ label: 'Get an outgoing payment',
+ link: '/sdk/outgoing-get/'
+ },
+ {
+ label:
+ 'Get spent amounts for current outgoing payment grant',
+ link: '/sdk/outgoing-grant-spent-amounts/'
+ }
+ ]
},
{
- label: 'Cancel a grant request',
- link: '/sdk/grant-revoke'
+ label: 'Tokens',
+ collapsed: true,
+ items: [
+ {
+ label: 'Rotate an access token',
+ link: '/sdk/token-rotate/'
+ },
+ {
+ label: 'Revoke an access token',
+ link: '/sdk/token-revoke/'
+ }
+ ]
}
]
},
{
- label: 'Incoming payments',
+ label: 'Guides',
+ translations: { es: 'Guías' },
collapsed: true,
items: [
{
- label: 'Create an incoming payment',
- link: '/sdk/incoming-create/'
- },
- {
- label: 'List incoming payments',
- link: '/sdk/incoming-list/'
+ label: 'Accept a one-time payment for an online purchase',
+ translations: {
+ es: 'Aceptar un pago único por una compra en línea'
+ },
+ link: '/guides/accept-otp-online-purchase/'
},
{
- label: 'Get an incoming payment',
- link: '/sdk/incoming-get/'
+ label: 'Send a remittance payment with a fixed debit amount',
+ translations: {
+ es: 'Enviar un pago de remesa con un monto de débito fijo'
+ },
+ link: '/guides/onetime-remittance-fixed-debit'
},
{
- label: 'Complete an incoming payment',
- link: '/sdk/incoming-complete/'
- }
- ]
- },
- {
- label: 'Quotes',
- collapsed: true,
- items: [
- {
- label: 'Create a quote',
- link: '/sdk/quote-create/'
+ label:
+ 'Send a remittance payment with a fixed receive amount',
+ translations: {
+ es: 'Enviar una remesa con un monto de recepción fijo'
+ },
+ link: '/guides/onetime-remittance-fixed-receive/'
},
{
- label: 'Get a quote',
- link: '/sdk/quote-get/'
- }
- ]
- },
- {
- label: 'Outgoing payments',
- collapsed: true,
- items: [
- {
- label: 'Create an outgoing payment',
- link: '/sdk/outgoing-create/'
+ label:
+ 'Set up recurring payments with a fixed incoming amount',
+ translations: {
+ es: 'Configurar pagos recurrentes con un monto entrante fijo'
+ },
+ link: '/guides/recurring-subscription-incoming-amount/'
},
{
- label: 'List outgoing payments',
- link: '/sdk/outgoing-list/'
+ label: 'Send recurring remittances with a fixed debit amount',
+ translations: {
+ es: 'Enviar remesas recurrentes con un monto de débito fijo'
+ },
+ link: '/guides/recurring-remittance-fixed-debit/'
},
{
- label: 'Get an outgoing payment',
- link: '/sdk/outgoing-get/'
+ label:
+ 'Send recurring remittances with a fixed receive amount',
+ translations: {
+ es: 'Enviar remesas recurrentes con un monto de recepción fijo'
+ },
+ link: '/guides/recurring-remittance-fixed-receive/'
},
{
- label: 'Get spent amounts for current outgoing payment grant',
- link: '/sdk/outgoing-grant-spent-amounts/'
- }
- ]
- },
- {
- label: 'Tokens',
- collapsed: true,
- items: [
- {
- label: 'Rotate an access token',
- link: '/sdk/token-rotate/'
+ label: 'Split an incoming payment',
+ translations: { es: 'Dividir un pago entrante' },
+ link: '/guides/split-payments/'
},
{
- label: 'Revoke an access token',
- link: '/sdk/token-revoke/'
+ label: 'Get an outgoing payment grant for future payments',
+ translations: {
+ es: 'Obtener una concesión de pago saliente para pagos futuros'
+ },
+ link: '/guides/outgoing-grant-future-payments/'
}
]
}
]
},
{
- label: 'Guides',
- translations: { es: 'Guías' },
+ label: 'For ASEs',
+ translations: { es: 'Para ASEs' },
collapsed: true,
items: [
{
- label: 'Accept a one-time payment for an online purchase',
- translations: {
- es: 'Aceptar un pago único por una compra en línea'
- },
- link: '/guides/accept-otp-online-purchase/'
+ label: 'Overview',
+ link: '/implement/'
},
{
- label: 'Send a remittance payment with a fixed debit amount',
- translations: {
- es: 'Enviar un pago de remesa con un monto de débito fijo'
- },
- link: '/guides/onetime-remittance-fixed-debit'
+ label: 'Wallet address architecture',
+ link: '/implement/wallet-address-architecture/'
},
{
- label: 'Send a remittance payment with a fixed receive amount',
- translations: {
- es: 'Enviar un pago de remesa con un monto de recepción fijo'
- },
- link: '/guides/onetime-remittance-fixed-receive/'
- },
- {
- label: 'Set up recurring payments with a fixed incoming amount',
- translations: {
- es: 'Configurar pagos recurrentes con un monto entrante fijo'
- },
- link: '/guides/recurring-subscription-incoming-amount/'
- },
- {
- label: 'Send recurring remittances with a fixed debit amount',
- translations: {
- es: 'Enviar remesas recurrentes con un monto de débito fijo'
- },
- link: '/guides/recurring-remittance-fixed-debit/'
+ label: 'Resource server',
+ link: '/implement/resource-server/'
},
{
- label: 'Send recurring remittances with a fixed receive amount',
- translations: {
- es: 'Enviar remesas recurrentes con un monto de recepción fijo'
- },
- link: '/guides/recurring-remittance-fixed-receive/'
+ label: 'Authorization server',
+ link: '/implement/auth-server/'
},
{
- label: 'Split an incoming payment',
- translations: { es: 'Dividir un pago entrante' },
- link: '/guides/split-payments/'
+ label: 'Identity provider integration',
+ link: '/implement/identity-provider/'
},
{
- label: 'Get an outgoing payment grant for future payments',
- translations: {
- es: 'Obtener una concesión de pago saliente para pagos futuros'
- },
- link: '/guides/outgoing-grant-future-payments/'
+ label: 'Security',
+ link: '/implement/security/'
}
]
},
diff --git a/docs/src/content/docs/concepts/amounts.mdx b/docs/src/content/docs/concepts/amounts.mdx
index 3263c011..382953d5 100644
--- a/docs/src/content/docs/concepts/amounts.mdx
+++ b/docs/src/content/docs/concepts/amounts.mdx
@@ -10,6 +10,10 @@ import {
} from '@interledger/docs-design-system'
import { Steps } from '@astrojs/starlight/components'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
Amounts in Open Payments define monetary values using asset codes and asset scales. They provide a standardized way to represent money across different currencies and payment scenarios.
:::
diff --git a/docs/src/content/docs/concepts/auth.mdx b/docs/src/content/docs/concepts/auth.mdx
index 69e5756d..ddba7bd6 100644
--- a/docs/src/content/docs/concepts/auth.mdx
+++ b/docs/src/content/docs/concepts/auth.mdx
@@ -5,6 +5,10 @@ parent: Concepts
import { LinkOut, Tooltip } from '@interledger/docs-design-system'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
Authorization in Open Payments refers to the process by which a client obtains permission from a resource owner to access and perform operations on protected resources.
:::
@@ -15,6 +19,10 @@ Open Payments leverages the Create Incoming Payment API. The resource is created on the recipient's account.
diff --git a/docs/src/content/docs/concepts/wallet-addresses.mdx b/docs/src/content/docs/concepts/wallet-addresses.mdx
index 67e207d6..e7f819f0 100644
--- a/docs/src/content/docs/concepts/wallet-addresses.mdx
+++ b/docs/src/content/docs/concepts/wallet-addresses.mdx
@@ -3,6 +3,10 @@ title: Wallet addresses
parent: Concepts
---
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
A wallet address is a secure, sharable identifier for an Open Payments-enabled account. Each wallet address acts a service endpoint into the APIs, allowing clients to interact with the underlying account.
:::
@@ -50,6 +54,10 @@ When setting up a payment, the client must obtain the wallet address for both th
A wallet address server is an API endpoint that retrieves public information about a wallet address, including its associated public keys. For more technical details, visit the [wallet address server API docs](/apis/wallet-address-server/operations/get-wallet-address/).
+:::note[For ASEs deploying a wallet address server]
+ASEs should refer to the [wallet address architecture page](/implement/wallet-address-architecture/) under the For ASEs section.
+:::
+
## Privacy and security
A wallet address acts as a proxy identifier (alias) for an underlying financial account. If permitted by an ASE, a single account can have multiple wallet addresses. Allowing account holders to generate unique wallet addresses for every client they interact with can help prevent a single address from becoming a tracking vector.
diff --git a/docs/src/content/docs/identity/client-keys.mdx b/docs/src/content/docs/identity/client-keys.mdx
index c31fdc00..c955d15a 100644
--- a/docs/src/content/docs/identity/client-keys.mdx
+++ b/docs/src/content/docs/identity/client-keys.mdx
@@ -8,6 +8,10 @@ import {
MermaidWrapper
} from '@interledger/docs-design-system'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
Client keys are unique identifiers that allow clients to sign Open Payments requests and verify the client's identity.
:::
@@ -18,14 +22,14 @@ All client requests in Open Payments are signed using a unique key that identifi
A key registry is a list of keys that are generated and stored by the client for when the client requires access to protected Open Payment resources. Grant requests are completed over multiple signed HTTP requests. As such, it's important that the client provides a way to consistently identify itself to an authorization server. The key registry allows the authorization server to verify that the client is who it says it is.
-Clients that identify themselves with a [wallet address](/concepts/wallet-addresses/) make their key registry publicly accessible via a `jwks.json` endpoint. An authorization server can retrieve the client's key registry by accessing `WALLET_ADDRESS/jwks.json`.
+Clients identify themselves with a [wallet address](/concepts/wallet-addresses/) and expose their public keys through a JWKS endpoint hosted by the ASE at `WALLET_ADDRESS/jwks.json`. Authorization servers fetch that registry to verify signed requests.
```http title="Example"
https://wallet.example.com/alice/jwks.json
```
:::note
-Clients using directed identity for non-interactive grant requests provide their public key directly in the request body and do not require a key registry. See [Client requests](#client-requests) for more information.
+Directed identity is supported only for certain non-interactive grants. In those cases, the client provides a `jwk` in the request body instead of using a wallet-address-hosted JWKS endpoint. Refer to [Client requests](#client-requests).
:::
### Registry structure
diff --git a/docs/src/content/docs/identity/grants.mdx b/docs/src/content/docs/identity/grants.mdx
index 370dc3d4..3dfd6914 100644
--- a/docs/src/content/docs/identity/grants.mdx
+++ b/docs/src/content/docs/identity/grants.mdx
@@ -4,6 +4,10 @@ title: Grant negotiation and authorization
import { CodeBlock, LinkOut, Tooltip } from '@interledger/docs-design-system'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
A grant in Open Payments allows a client to obtain authorization from a resource owner to access and perform operations on protected resources. Open Payments uses the Grant Negotiation and Authorization Protocol (GNAP) to facilitate this process, allowing clients to securely interact with the API.
:::
@@ -30,6 +34,10 @@ An authorization server grants delegated privileges to a client in the form of a
An authorization server is uniquely identified by its grant endpoint URI, which is an absolute URI that a client calls to initiate a grant request.
+:::note[For ASEs deploying an authorization server]
+ASEs should refer to the [authorization server page](/implement/auth-server/) under the For ASEs section.
+:::
+
### Key registries
A key registry is a list of keys associated with clients requiring access to protected Open Payments resources. Key registries are publicly exposed via a `jwks.json` endpoint and allows an authorization server to verify that a client is who it says it is.
diff --git a/docs/src/content/docs/identity/hash-verification.mdx b/docs/src/content/docs/identity/hash-verification.mdx
index 24f6c147..b9339f93 100644
--- a/docs/src/content/docs/identity/hash-verification.mdx
+++ b/docs/src/content/docs/identity/hash-verification.mdx
@@ -4,6 +4,10 @@ title: Hash verification
import { LinkOut, Tooltip } from '@interledger/docs-design-system'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
After a resource owner allows a client to access their account, the client must verify that the resource owner provided their consent. The client verifies consent by calculating the hash issued by the authorization server.
:::
diff --git a/docs/src/content/docs/identity/http-signatures.mdx b/docs/src/content/docs/identity/http-signatures.mdx
index ffc3bfd1..1b68a9fc 100644
--- a/docs/src/content/docs/identity/http-signatures.mdx
+++ b/docs/src/content/docs/identity/http-signatures.mdx
@@ -4,6 +4,10 @@ title: HTTP message signatures
import { LinkOut } from '@interledger/docs-design-system'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
HTTP message signatures secure communications in Open Payments by verifying message authenticity and protecting against tampering.
:::
diff --git a/docs/src/content/docs/identity/idp.mdx b/docs/src/content/docs/identity/idp.mdx
index 5868525d..d17786c6 100644
--- a/docs/src/content/docs/identity/idp.mdx
+++ b/docs/src/content/docs/identity/idp.mdx
@@ -5,6 +5,10 @@ title: Identity providers
import { CodeBlock, LinkOut } from '@interledger/docs-design-system'
import Interactive from '/src/content/docs/partials/_diagram-interactive-grant.mdx'
+:::note[For client developers]
+This page describes Open Payments from the client developer's perspective. ASEs should refer to the [For ASEs](/implement/) section.
+:::
+
:::tip[Summary]
An identity provider (IdP) is a system or service that manages user authentication and consent during the interactive grant process.
:::
diff --git a/docs/src/content/docs/implement/auth-server.mdx b/docs/src/content/docs/implement/auth-server.mdx
new file mode 100644
index 00000000..60b46b05
--- /dev/null
+++ b/docs/src/content/docs/implement/auth-server.mdx
@@ -0,0 +1,92 @@
+---
+title: Authorization server
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+The authorization server processes grant requests under GNAP, issues access tokens, and coordinates user consent for interactive grants. This page describes what the ASE's authorization server must do.
+:::
+
+## What the authorization server does
+
+An ASE's authorization server is the single entry point for clients seeking permission to call the resource server. It:
+
+- Accepts grant requests at a single grant endpoint URI.
+- Verifies the client's identity and the signature on each request.
+- Decides whether the requested access is permitted, including whether interaction with the resource owner is required.
+- Issues, rotates, and revokes access tokens.
+- Coordinates the interactive flow with the ASE's identity provider when consent is needed.
+
+The wallet address response returns the grant endpoint URI in the `authServer` field. Clients send all grant requests to that URI. The GNAP specification uses a single endpoint per authorization server rather than a different endpoint per grant type.
+
+## Grants and access tokens
+
+The authorization server's job is to translate a **grant**, an authorization issued by the resource owner, into one or more **access tokens** that the client uses against the resource server. From the authorization server's perspective:
+
+- A grant is durable state held by the authorization server. It records what the resource owner consented to and is the basis on which tokens are issued.
+- An access token is a short-lived credential bound to a grant. The authorization server must be able to validate it when the resource server asks.
+- The authorization server validates the grant each time the client uses its access token. Revoking the grant must revoke every token bound to it.
+
+ASEs decide the token lifetime and whether tokens are opaque (validated by callback to the authorization server) or self-contained (validated locally by the resource server using shared key material).
+
+## Grant request processing
+
+Every grant request must be signed by the client. The authorization server's first job is to verify the signature. Refer to [Security](/implement/security/) for the verification rules.
+
+After signature verification, the authorization server inspects the request to determine the grant type and whether interaction is required.
+
+### incoming-payment grants
+
+Non-interactive. The authorization server may issue an access token immediately if the request is well-formed and the client is trusted.
+
+The client may include directed identity (a public key in the request body rather than a wallet address). The authorization server uses the embedded key for signature verification and does not require a wallet address or key registry lookup for this grant type.
+
+The authorization server may issue a single grant whose access token covers multiple incoming payments at this ASE, as long as the additional incoming payments are for accounts that belong to this ASE.
+
+### quote grants
+
+Non-interactive. Same handling as incoming-payment grants. Directed identity is also permitted.
+
+The authorization server may issue a single grant whose access token covers multiple quotes at this ASE, subject to the same single-ASE constraint.
+
+### outgoing-payment grants
+
+**Interactive.** The authorization server must not issue an access token until the resource owner has explicitly consented to the outgoing payment.
+
+When the authorization server receives an outgoing-payment grant request:
+
+1. Verify the request signature and the client's identity.
+2. Persist the grant in a `pending` state, including the requested limits (amounts, expiry).
+3. Return an `interact.redirect` URI (the redirect to the start of the interaction flow) and a `continue` URI with a continuation token. The client uses the redirect URI to send the resource owner into the consent flow.
+4. When the resource owner reaches the redirect URI, start the interaction session and forward the resource owner to the identity provider (IdP). Refer to [Identity provider integration](/implement/identity-provider/).
+5. When the IdP returns the resource owner's decision, finalize the session. If consent was granted, advance the grant from `pending` to `approved`.
+6. Redirect the resource owner back to the client's `interact.finish` URI with an `interact_ref` and a hash computed from the original nonce values and the grant URI. Refer to [Security](/implement/security/) for the hash generation rules.
+7. When the client posts to the continuation URI with the continuation token, issue the access token if the grant is `approved`. If the grant is still `pending` or was denied, return the appropriate GNAP error.
+
+## Continuation and polling
+
+In headless or non-browser scenarios, the client may not have a return URI for the authorization server to redirect to. The authorization server must allow the client to poll the continuation URI to check whether interaction has completed. ASEs define a sensible minimum polling interval and return it to the client in the continuation response.
+
+## Token rotation and revocation
+
+ASEs must support:
+
+- **Rotation**: The client may request a new access token bound to the same grant. The authorization server issues a new token and invalidates the previous one. Tokens bound to grants that have themselves expired must not be rotatable.
+- **Revocation**: The client (or, in some flows, the resource owner) may revoke an access token. After revocation, the resource server must reject any subsequent use of the token. If tokens are validated locally at the resource server, the authorization server must propagate revocation quickly enough that stale tokens cannot be used for unauthorized operations.
+
+The relevant client-facing endpoints are Rotate access token and Revoke access token.
+
+## Token validation for the resource server
+
+When the resource server receives a request from a client, it must validate the access token. ASEs choose one of two patterns:
+
+- **Introspection**: The resource server calls the authorization server for each request to check the token. Simple to reason about; cost scales with traffic.
+- **Self-contained tokens**: The authorization server signs tokens that the resource server can validate locally using shared key material. Cheaper at scale; requires careful handling of revocation propagation.
+
+Either way, the authorization server is the source of truth for whether a token is currently valid.
+
+## Reference implementation
+
+Rafiki provides an open-source
+implementation of an Open Payments authorization server.
diff --git a/docs/src/content/docs/implement/identity-provider.mdx b/docs/src/content/docs/implement/identity-provider.mdx
new file mode 100644
index 00000000..8d9b6a77
--- /dev/null
+++ b/docs/src/content/docs/implement/identity-provider.mdx
@@ -0,0 +1,65 @@
+---
+title: Identity provider integration
+---
+
+import { CodeBlock, LinkOut } from '@interledger/docs-design-system'
+import Interactive from '/src/content/docs/partials/_diagram-interactive-grant.mdx'
+
+:::tip[Summary]
+ASEs must integrate their authorization server with an identity provider (IdP) to authenticate the resource owner and collect explicit consent during interactive grants.
+:::
+
+## When the IdP is involved
+
+Open Payments requires an interactive grant for outgoing payments. An interactive grant cannot be approved without explicit consent from the resource owner (typically the client's user). The ASE's authorization server is responsible for orchestrating consent collection, but it must not collect consent itself. It delegates that to an IdP so that the resource owner authenticates against the same system that controls their underlying account.
+
+The following diagram illustrates the full interactive grant flow, including the IdP.
+
+
+
+## What the authorization server passes to the IdP
+
+When the authorization server forwards the resource owner to the IdP, it must pass enough information for the IdP to display a meaningful consent screen and route the decision back. The exact mechanism is an ASE implementation detail (a signed redirect URL, a short-lived consent token, a back channel call), but the information the IdP needs includes:
+
+- The **grant identifier**, so the IdP can return its decision to the right grant.
+- The **resource owner identity** (or whatever the IdP uses to start authentication), so the IdP can prompt the correct user to log in.
+- The **requested access**: resource types, actions, limits (debit amount, receive amount, expiry, interval for recurring access).
+- The **client identity**: at minimum a display name.
+- The **return path** to the authorization server once a decision is made.
+
+## What the IdP must display
+
+The IdP is the only system in the flow that the resource owner is authenticated against, so it is also the only system that can credibly ask for consent. The IdP must show:
+
+- Who the client is (display name; optionally additional client attestation).
+- What the client is asking permission to do, in plain language (for example, "send up to $50 USD from your account").
+- Any time or velocity limits attached to the request.
+- A clear accept/deny choice.
+
+ASEs decide the exact UI, but the consent prompt must accurately reflect what the authorization server will encode in the resulting grant. If the IdP under-represents the requested access (for example, by omitting a `interval`), the grant must be denied or the access scaled down to what the user actually saw and accepted.
+
+## How consent flows back
+
+After the resource owner makes a choice, the IdP communicates it back to the authorization server. Common patterns:
+
+- A signed redirect from the IdP to a callback on the authorization server, carrying the grant identifier and the decision.
+- A back channel API call from the IdP to the authorization server.
+
+The authorization server must verify the integrity of the incoming decision, both that it came from the ASE's IdP and that it has not been altered. On accept, the authorization server moves the grant to `approved` and unlocks token issuance. On deny, the grant must be moved to a terminal denied state so that subsequent continuation requests return the appropriate error.
+
+## Redirect and hash mechanics
+
+Once the authorization server has the IdP's decision, it redirects the resource owner back to the client's `interact.finish` URI (if one was provided in the original grant request) and includes:
+
+- An `interact_ref`.
+- A `hash` computed from the original `nonce` values and the grant URI.
+
+The client verifies the hash to confirm that the redirect actually came from the authorization server, then calls the continuation URI to retrieve the access token. The exact hash construction is described in [Security](/implement/security/).
+
+## Reference implementation
+
+Rafiki provides a reference
+
+ authorization service
+
+implementation that includes support for integration with an IdP.
diff --git a/docs/src/content/docs/implement/index.mdx b/docs/src/content/docs/implement/index.mdx
new file mode 100644
index 00000000..e0b41322
--- /dev/null
+++ b/docs/src/content/docs/implement/index.mdx
@@ -0,0 +1,40 @@
+---
+title: For ASEs
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+These pages describe what an account servicing entity (ASE) must build to make its accounts Open Payments-enabled. The rest of the Open Payments documentation describes what client developers need to know to call the APIs; this guide describes what ASEs need to build to serve them.
+:::
+
+## What is an ASE?
+
+An account servicing entity (ASE) provides and maintains payment accounts for senders and recipients, and is a regulated entity in the countries it operates. Examples of ASEs include banks, digital wallet providers, and mobile money providers.
+
+When an ASE adopts the Open Payments standard, the customers' financial accounts at that ASE become Open Payments-enabled. Clients (apps, services, and other software) can then call the Open Payments APIs against those accounts to retrieve account details and issue payment instructions, without needing a custom integration per ASE.
+
+## What ASEs must operate
+
+To make accounts Open Payments-enabled, ASEs must operate three servers and integrate with an identity provider:
+
+- **Wallet address server**: Returns public information about each Open Payments-enabled account at a stable HTTPS URL (the wallet address).
+- **Resource server**: Hosts the `incoming-payment`, `quote`, and `outgoing-payment` APIs that clients call to set up payments.
+- **Authorization server**: Processes grant requests under the Grant Negotiation and Authorization Protocol (GNAP), issues access tokens, and coordinates user consent for interactive grants.
+- **Identity provider (IdP) integration**: Authenticates the resource owner and collects explicit consent during interactive grants (required for outgoing payments).
+
+The ASE is also responsible for the actual movement of funds. Open Payments only carries the payment **instruction**. Settlement happens between ASEs over a shared payment rail.
+
+## Relationship to client developers
+
+Client developers do not need to know how an ASE structures its accounts, validates tokens, integrates its IdP, or settles funds. From the client developer's perspective, an ASE exposes:
+
+- A wallet address URL that returns public account details.
+- A resource server that accepts payment-related requests and returns resources.
+- An authorization server that issues access tokens after evaluating grant requests.
+
+:::note
+This section covers the server-side concerns that sit behind those interfaces. The pages assume the reader is implementing Open Payments at an ASE. The language is intentionally addressed to ASEs in the third person so a client developer who lands here can tell that the content is not intended for them.
+:::
+
+The pages in this section describe the standard and protocol-level requirements. For a concrete, open-source reference implementation of an Open Payments-compatible server stack, refer to Rafiki.
diff --git a/docs/src/content/docs/implement/resource-server.mdx b/docs/src/content/docs/implement/resource-server.mdx
new file mode 100644
index 00000000..c457a73b
--- /dev/null
+++ b/docs/src/content/docs/implement/resource-server.mdx
@@ -0,0 +1,96 @@
+---
+title: Resource server
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+The resource server hosts the `incoming-payment`, `quote`, and `outgoing-payment` APIs that clients use to set up payments. This page describes what an ASE's resource server must do when each resource type is created and how it hands payments off to settlement.
+:::
+
+## What the resource server does
+
+An ASE's resource server hosts three sets of APIs, one per resource type. For each request, the resource server validates the access token presented by the client with the ASE's authorization server before performing the operation.
+
+### incoming-payment
+
+When the resource server receives a Create Incoming Payment request, it must:
+
+- Generate unique payment details that the sender's ASE will use to address payments to this incoming payment.
+- Persist the incoming payment resource so it can be retrieved, listed, and completed via subsequent API calls.
+- Track the running `receivedAmount` as payments arrive.
+
+If the request specifies an `incomingAmount`, the resource server must enforce it as a maximum total. One or more payments can be associated with the incoming payment, but the sum across them must not exceed `incomingAmount`. The resource server should mark the incoming payment as completed once the running total reaches the cap.
+
+If `incomingAmount` is absent, the resource server cannot determine completion on its own. It must accept payments until a client issues a Complete an Incoming Payment request, or until the incoming payment expires. ASEs can define an expiration window. The `expiresAt` must be returned to the client in such cases so that the client knows when the resource will stop accepting payments.
+
+### quote
+
+When the resource server receives a Create a Quote request, it must:
+
+- Compute the `debitAmount`, `receiveAmount`, and fee for the requested payment, including any applicable exchange rate when the sender's and recipient's wallet addresses use different `assetCode` values.
+- Commit to deliver the quoted `receiveAmount` to the recipient's ASE if an outgoing payment is created against the quote within the quote's validity window.
+- Assign the quote a `quoteId` and persist it.
+- Set a validity window after which the quote is no longer honored. ASEs define this window; it must be short enough that rate and fee exposure is bounded and long enough that the client can collect interactive consent before it expires.
+
+A quote represents a binding commitment from the sender's ASE. If the sender's ASE cannot honor the quote at execution time, that is an ASE-side failure, not a protocol error.
+
+### outgoing-payment
+
+When the resource server receives a Create Outgoing Payment request, it must:
+
+- Validate that the access token presented carries a grant that authorizes this specific outgoing payment, including the limits collected during the interactive consent flow.
+- If a `quoteId` is present, look up the quote, confirm it has not expired, and use the quoted amounts.
+- If no `quoteId` is present (for example, in Web Monetization-style use cases), use the `incomingPayment` and `debitAmount` from the request to scope the outgoing payment.
+- Persist the outgoing payment resource and initiate settlement.
+
+The outgoing-payment resource is the ASE's instruction to itself to move funds. Returning a `201` does not mean money has moved; it means the resource server has accepted and durably stored the instruction.
+
+## Payment method details
+
+The `methods` array in the incoming payment response tells the sender's ASE how to deliver funds. ASEs are responsible for populating this array with valid payment method data for each rail they support.
+
+When using Interledger (ILP) as the payment method, the resource server must include the following in the `methods` object:
+
+- `type` set to `ilp`.
+- The recipient ASE's ILP address, so packets routed over the Interledger network reach the recipient's ASE.
+- A `sharedSecret`: a cryptographically generated secret used to secure the STREAM connection between the sender's and recipient's ASEs.
+
+```http title="incoming-payment methods object (ILP)"
+"methods": [
+ {
+ "type": "ilp",
+ "ilpAddress": "g.ilp.iwuyge987y.98y08y",
+ "sharedSecret": "1c7eaXa4rd2fFOBl1iydvCT1tV5TbM3RW1WLCafu_JA"
+ }
+ ]
+```
+
+ASEs must generate a fresh `sharedSecret` per incoming payment and must not reuse secrets across resources. ILP is currently the only payment method that integrates with Open Payments. ASEs that intend to settle over a different rail must wait for that rail's payment method to be defined and integrated.
+
+## Application layer vs. settlement layer
+
+Open Payments is a protocol that operates in the application layer. The resource server stores the agreement between the two ASEs ("ASE A will deliver X to ASE B against this incoming payment") and exposes it through the APIs.
+
+Actual movement of funds happens on the settlement layer:
+
+- Once the outgoing payment is created and the corresponding incoming payment is open, the sender's ASE is obligated to settle with the recipient's ASE.
+- Settlement runs over a shared payment rail outside of Open Payments.
+- Open Payments does not touch funds, hold balances, or execute transfers. The ASE is fully responsible for that.
+
+ASEs must keep the two layers in sync. As funds arrive at the recipient's ASE, the resource server must update the `receivedAmount` on the corresponding incoming payment so clients have an accurate view of payment progress.
+
+## Token validation
+
+Every request to the resource server carries an access token issued by the ASE's authorization server. Before serving the request, the resource server must validate that the token:
+
+- Is well-formed and not revoked.
+- Covers the requested action (`create`, `read`, `list`, `complete`) and resource type (`incoming-payment`, `quote`, `outgoing-payment`).
+- Is scoped to the wallet address the request targets, where applicable.
+
+ASEs decide whether the resource server validates tokens locally (for example, against a shared cache) or by calling the authorization server. The protocol does not mandate one approach. Whichever pattern an ASE chooses, the resource server must refuse requests with insufficient tokens. For more details on token issuance and lifecycle, refer to [Authorization server](/implement/auth-server/).
+
+## Reference implementation
+
+Rafiki provides an open-source
+implementation of an Open Payments resource server.
diff --git a/docs/src/content/docs/implement/security.mdx b/docs/src/content/docs/implement/security.mdx
new file mode 100644
index 00000000..d7a16f60
--- /dev/null
+++ b/docs/src/content/docs/implement/security.mdx
@@ -0,0 +1,99 @@
+---
+title: Security
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+ASEs operate the verification side of every security mechanism in Open Payments: signature validation on incoming requests, client key resolution and verification, and the interaction hash generation that lets clients prove a redirect came from the authorization server.
+:::
+
+## Security posture
+
+Open Payments places three cryptographic checkpoints between a client and the resource server:
+
+1. **HTTP message signatures** on every request, so the ASE can authenticate the sender and detect tampering.
+2. **Client keys** registered to a wallet address (or provided directly via directed identity), so the ASE knows which key to verify against.
+3. **Interaction hashes** during interactive grants, so the client can verify that a redirect emanated from the ASE's authorization server.
+
+ASEs are responsible for the verification side of all three mechanisms. Failure to enforce any one of them weakens the others.
+
+## HTTP signature validation
+
+Every request a client sends to an ASE's authorization or resource server is signed using the HTTP message signatures profile defined by GNAP, with the Ed25519 variant of EdDSA.
+
+For every signed incoming request, ASEs must:
+
+1. Read the `Signature-Input` header to identify the `keyId` and the list of covered components.
+2. Reconstruct the signature base from those components exactly as the client did.
+3. Resolve the public key (refer to [Client key resolution](#client-key-resolution) below).
+4. Verify the `Signature` header against the reconstructed signature base using Ed25519.
+5. Reject the request if any of the following are true:
+ - Required components (method, target URI, content digest where applicable, authorization header where applicable) are missing.
+ - The signature does not validate.
+ - The signature is older than the maximum age the ASE permits.
+ - The `Content-Digest` header is present but does not match the body.
+
+ASEs set their own maximum signature age. Open Payments does not bound it, but very long limits increase replay risk and very short limits can cause failures for clients on slow networks.
+
+Bearer tokens are not supported. A token alone is never sufficient: every request must carry both an access token (where applicable) and a valid signature.
+
+## Client key resolution
+
+The authorization server needs the client's public key to verify signatures. Open Payments supports two modes:
+
+### Wallet address-identified clients
+
+The client's grant request body includes `client.walletAddress`. ASEs must provide a key registration flow for clients and host each client's JWKS at `WALLET_ADDRESS/jwks.json`. The authorization server:
+
+1. Extracts the client's domain from the wallet address.
+2. Issues a `GET` to `{walletAddress}/jwks.json` to retrieve the client's key registry.
+3. Locates the key whose `kid` matches the `keyId` in the `Signature-Input` header.
+4. Confirms the key uses the expected algorithm (`EdDSA`), key type (`OKP`), and curve (`Ed25519`).
+5. Binds the resolved key to the grant. On subsequent requests against the same grant, the authorization server fetches the bound key from the grant's stored domain rather than the request body.
+
+ASEs are responsible for keeping the hosted JWKS in sync with client key lifecycle events such as key rotation and key revocation.
+
+### Directed-identity clients
+
+For non-interactive grants (incoming-payment, quote), the client may provide its key directly in the request body as a `jwk`. The authorization server uses that key for signature verification on the initial request and binds it to the resulting grant. No JWKS endpoint lookup is required, and the client's wallet address is never exposed.
+
+ASEs must reject directed identity on interactive grant requests.
+
+## Interaction hash generation
+
+During interactive grants, the authorization server redirects the resource owner back to the client's `interact.finish` URI with a `hash` parameter. The client uses this hash to confirm that the redirect actually emanated from the authorization server.
+
+ASEs must generate the hash by concatenating four values in order, separated by single newlines (`\n`), with no padding, no surrounding whitespace, and no trailing newline:
+
+1. The `nonce` the client sent in the initial grant request.
+2. The `nonce` the authorization server returned in its response to the initial grant request.
+3. The `interact_ref` the authorization server is about to return on this redirect.
+4. The grant endpoint URI the client used for its initial request.
+
+The resulting string is hashed with `sha-256` (the only hashing algorithm Open Payments currently supports), and the digest is Base64-encoded with no padding. The result is sent as the `hash` parameter on the redirect.
+
+```http title="Example hash base"
+VJLO6A4CATR0KRO
+MBDOFXG4Y5CVJCX821LH
+4IFWWIKYB2PQ6U56NL1
+https://server.example.com/tx
+```
+
+```http title="Resulting hash"
+x-gguKWTj8rQf7d7i3w3UhzvuJ5bpOlKyAlVpLxBffY
+```
+
+ASEs must use the exact concatenation order and separator above. Any deviation breaks the client's verification.
+
+## Putting the three together
+
+The three mechanisms reinforce each other:
+
+- HTTP signatures authenticate every request and prevent tampering in flight.
+- Client keys give the authorization server a stable identity to attach grants to.
+- The interaction hash closes the loop on interactive grants by letting the client cryptographically confirm the round-trip through the IdP returned to the same authorization server.
+
+If an ASE skips signature validation, it cannot trust the client identity. If it skips key resolution, it cannot detect a forged signature. If it skips hash generation, clients cannot trust the redirect. ASEs must implement all three.
+
+For the client-side details (signing requests, generating keys, verifying the hash), refer to [HTTP signatures](/identity/http-signatures/), [Client keys](/identity/client-keys/), and [Hash verification](/identity/hash-verification/).
diff --git a/docs/src/content/docs/implement/wallet-address-architecture.mdx b/docs/src/content/docs/implement/wallet-address-architecture.mdx
new file mode 100644
index 00000000..cb3fcdd8
--- /dev/null
+++ b/docs/src/content/docs/implement/wallet-address-architecture.mdx
@@ -0,0 +1,87 @@
+---
+title: Wallet address architecture
+---
+
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+A wallet address is the public HTTPS endpoint that identifies an Open Payments-enabled account and exposes the information clients need to interact with it. ASEs operate a wallet address server that returns this information and define the policy that maps wallet addresses to underlying accounts.
+:::
+
+## URL requirements
+
+ASEs must enforce the following constraints on any URL that is exposed as a wallet address:
+
+- The URL must use the `https` protocol.
+- The URL must not contain a `user-info`, `port`, `query string`, or `fragment` component.
+- The server handling HTTP requests at that URL must support the Open Payments protocol.
+
+URLs that do not meet these constraints are not valid wallet addresses. ASEs should refuse to issue or accept them.
+
+## Wallet address response
+
+A wallet address server returns public information about the underlying account in response to a `GET` request with `Accept: application/json`:
+
+```http
+HTTP/1.1 200 Success
+Content-Type: application/json
+
+{
+ "id": "https://wallet.example.com/alice",
+ "publicName": "Alice",
+ "assetCode": "USD",
+ "assetScale": 2,
+ "authServer": "https://auth.wallet.example.com",
+ "resourceServer": "https://wallet.example.com/op"
+}
+```
+
+Each field is part of the ASE's externally observable contract:
+
+- `id`: The canonical wallet address URL. ASEs must return the same identifier the client used to reach the endpoint.
+- `publicName`: A human-readable label intended for display by clients and identity providers. ASEs decide what to expose here and must consider that this field is publicly visible.
+- `assetCode`: The ISO 4217 currency code of the underlying account.
+- `assetScale`: The number of decimal places used for that currency. Refer to [Amounts](/concepts/amounts/) for the value/scale relationship clients rely on.
+- `authServer`: The grant endpoint URI of the authorization server that issues tokens for this wallet address. Clients send all grant requests here.
+- `resourceServer`: The base URL of the resource server that hosts the `incoming-payment`, `quote`, and `outgoing-payment` resources for this wallet address.
+
+The full request and response schema is defined by the wallet address server API. ASEs must also expose the keys bound to a wallet address. Refer to Get keys bound to wallet address.
+
+## Multi-currency considerations
+
+A wallet address supports a single `assetCode` and `assetScale`. ASEs that wish to support multi-currency accounts have two common patterns:
+
+- Issue one wallet address per supported currency for the same underlying account. Each wallet address returns its own `assetCode` and `assetScale`.
+- Issue a single wallet address in the primary currency and perform currency conversion on the ASE side when payments arrive in a different currency. In this pattern, the wallet address still returns only one `assetCode`, and the recipient is credited in that currency regardless of the sender's denomination.
+
+In both patterns, the asset code returned by the wallet address response is the currency in which the underlying account will be credited. Conversion fees and exchange rates are an ASE policy decision and must be surfaced to the sender via the quote.
+
+## Mapping wallet addresses to accounts
+
+The relationship between wallet addresses and underlying accounts is intentionally loose. Common mapping models include:
+
+- **1:1**: Each account has exactly one wallet address. Simplest model; reuse and tracking risks are highest.
+- **1:many**: One account has many wallet addresses. Lets account holders generate per-client or per-context addresses to avoid being tracked across services.
+- **Many:1**: Many wallet addresses route to the same underlying account; ASEs may use this for shared accounts or rotation.
+
+ASEs define which models they support and the policy around:
+
+- Whether account holders can create new wallet addresses on demand.
+- Whether wallet addresses can be disabled or relinked to a different account.
+- Whether GET requests to a disabled wallet address return `404 Not Found`, or whether the URL may be reassigned to another account.
+- Whether previously granted access via one wallet address transfers to another wallet address on the same account.
+
+Open Payments treats any two distinct wallet addresses as distinct accounts even when they share an underlying account. ASEs must enforce this at the authorization layer: permission granted via one wallet address must not implicitly grant access via another.
+
+## Why URLs
+
+ASEs should treat the choice of URL-as-identifier as a design decision, not an accident of the protocol:
+
+- URLs are both an identifier and a service endpoint, so a client can discover everything it needs to transact (auth server, resource server, asset code) by dereferencing the address.
+- URLs avoid the need to overload identifiers such as email addresses or MSISDNs, which lack a standard interaction mechanism and require a separate registry to map an identifier to an account provider.
+- URLs let ASEs control the entire surface area of an Open Payments-enabled account through their own domain, including key rotation, address lifecycle, and content negotiation.
+
+## Reference implementation
+
+Rafiki provides an open-source
+implementation of a wallet address server.
diff --git a/docs/src/content/docs/overview/for-ases.mdx b/docs/src/content/docs/overview/for-ases.mdx
new file mode 100644
index 00000000..757ab02d
--- /dev/null
+++ b/docs/src/content/docs/overview/for-ases.mdx
@@ -0,0 +1,71 @@
+---
+title: Implementing Open Payments
+---
+
+import { LinkCard, CardGrid } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+Implementing Open Payments means standing up the servers that make a financial institution's accounts Open Payments-enabled. This page is the entry point for engineers at banks, digital wallet providers, and mobile money operators who are building that server stack.
+:::
+
+This page is the starting point for account servicing entity (ASE) implementors.
+
+## What ASEs build
+
+To make accounts Open Payments-enabled, ASEs operate three servers and integrate with an identity provider:
+
+- A **wallet address server** that returns public information about each Open Payments-enabled account.
+- A **resource server** that hosts the `incoming-payment`, `quote`, and `outgoing-payment` APIs.
+- An **authorization server** that processes grant requests, issues access tokens, and coordinates user consent.
+- An **identity provider** integration that authenticates the resource owner and collects explicit consent for outgoing-payment grants.
+
+ASEs are also responsible for settlement. Open Payments only carries the payment instruction; actual movement of funds happens between ASEs over a shared payment rail.
+
+## What to read
+
+ASEs should refer to the [For ASEs](/implement/) section for server-side details. This section is organized as follows:
+
+
+
+
+
+
+
+
+
+
+## The rest of the docs
+
+The Developer Concepts pages (under Overview → Concepts and Identity and access management) describe what client developers need to know to call the APIs. ASEs are welcome to read them. They describe the same protocol from the other side, but they are not the authoritative source for ASE implementation requirements. The For ASEs section is.
+
+## Reference implementation
+
+Rafiki is an open-source
+implementation of an Open Payments-compatible server stack. The ASE
+implementation guide describes the standard- and protocol-level requirements;
+Rafiki's documentation describes one specific implementation.
diff --git a/docs/src/content/docs/overview/for-developers.mdx b/docs/src/content/docs/overview/for-developers.mdx
new file mode 100644
index 00000000..28041963
--- /dev/null
+++ b/docs/src/content/docs/overview/for-developers.mdx
@@ -0,0 +1,72 @@
+---
+title: Building with Open Payments
+---
+
+import { LinkCard, CardGrid } from '@astrojs/starlight/components'
+import { LinkOut } from '@interledger/docs-design-system'
+
+:::tip[Summary]
+Building with Open Payments means writing a client that calls the Open Payments APIs to set up payments between Open Payments-enabled accounts. The accounts themselves, and the servers that expose them, are operated by account servicing entities (ASEs).
+:::
+
+This page is the starting point for developers building applications, services, or other software that consume the Open Payments APIs.
+
+## What you'll build
+
+A client in Open Payments is software that:
+
+- Discovers an Open Payments-enabled account from its [wallet address](/concepts/wallet-addresses/).
+- Requests [grants](/identity/grants/) from the account's authorization server.
+- Creates `incoming-payment`, `quote`, and `outgoing-payment` resources to set up a payment.
+- Signs every request with a key the authorization server can verify.
+- Handles the interactive consent flow for outgoing payments.
+
+A client never moves money. The ASEs on each side of the payment are responsible for settlement.
+
+## What to read
+
+If you're new to Open Payments, follow this path:
+
+
+
+
+
+
+
+
+
+
+
+## What's intentionally not on the developer path
+
+Some Open Payments documentation describes the responsibilities of the ASE, not the client such as how the wallet address server is structured, how the authorization server processes grants, how the resource server hands payments off to settlement, how the identity provider is integrated. Those pages live under the [For ASEs](/implement/) section and are intended for the engineers deploying the server stack.
+
+If you find yourself reading an ASE page and wondering whether you need to implement something on the client side, the answer is almost always no.
diff --git a/docs/src/content/docs/overview/getting-started.mdx b/docs/src/content/docs/overview/getting-started.mdx
index 40704a74..7abdbd4a 100644
--- a/docs/src/content/docs/overview/getting-started.mdx
+++ b/docs/src/content/docs/overview/getting-started.mdx
@@ -2,6 +2,7 @@
title: Getting started
---
+import { LinkCard, CardGrid } from '@astrojs/starlight/components'
import { LinkOut } from '@interledger/docs-design-system'
:::tip[Summary]
@@ -10,6 +11,23 @@ Open Payments is an API standard for banks, mobile money providers, and other ac
Developers can choose to interact with the Open Payments API directly or use the provided SDKs for a more streamlined integration experience.
:::
+## Choose your path
+
+Open Payments documentation serves two audiences. Pick the path that matches your role:
+
+
+
+
+
+
Handling payments is a crucial part of many online applications. Whether it's an eCommerce site selling products, a fundraising platform accepting donations, a streaming service charging for content, or a subscription service with monthly fees, digital payments are central to their operations. Many application developers rely on third-party payment gateways to handle these transactions, which can introduce additional expenses and limit control over the user experience.
Open Payments is an open RESTful API and an API standard that enables clients to interface with Open Payments-enabled accounts. In this context, a client is an application, such as a mobile or web app, that consumes one or more Open Payments resources, typically requiring access privileges from one or several authorization servers. The [Open Payments SDKs](/sdk/before-you-begin) simplify this process by providing pre-built functions for these interactions.