From 40b0efd0ffc9f2bffe7f258932243a42e597979e Mon Sep 17 00:00:00 2001
From: Estandar <143508382+EstandarMustaq@users.noreply.github.com>
Date: Sat, 18 Jul 2026 13:49:58 +0200
Subject: [PATCH] docs: align public API usage with owner contracts
---
.github/workflows/pages.yml | 2 +-
.../MAVULA-API-v1.postman_collection.json | 13 +++-
openapi/identity-access.public.v1.yaml | 52 +++++++++++----
openapi/ledger-core.public.v1.yaml | 63 ++++++++++---------
openapi/workbench.public.v1.yaml | 59 +++++++++++++----
sources.lock.json | 6 +-
.../v1/getting-started/authentication.mdx | 40 +++++++++---
.../docs/v1/getting-started/quickstart.mdx | 8 +--
.../v1/getting-started/roles-permissions.mdx | 9 +--
.../docs/v1/guides/account-lifecycle.mdx | 4 +-
src/content/docs/v1/guides/account-setup.mdx | 6 +-
src/content/docs/v1/guides/configuration.mdx | 2 +-
.../docs/v1/guides/financial-adjustments.mdx | 4 +-
src/content/docs/v1/guides/legacy-batches.mdx | 20 +++++-
src/content/docs/v1/guides/payment-jobs.mdx | 4 +-
src/content/docs/v1/index.mdx | 7 +--
.../docs/v1/resources/code-examples.mdx | 3 +-
test/contracts.test.mjs | 4 +-
18 files changed, 209 insertions(+), 97 deletions(-)
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index d9a0339..6e0ab57 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -5,7 +5,7 @@ on:
permissions:
contents: read
concurrency:
- group: pages
+ group: pages-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
diff --git a/examples/postman/MAVULA-API-v1.postman_collection.json b/examples/postman/MAVULA-API-v1.postman_collection.json
index fbb37de..b947316 100644
--- a/examples/postman/MAVULA-API-v1.postman_collection.json
+++ b/examples/postman/MAVULA-API-v1.postman_collection.json
@@ -1,13 +1,16 @@
{
"info": {
+ "_postman_id": "46219cb0-ef74-4fb4-8b79-6f4e38aa4c8b",
"name": "MAVULA API v1",
"description": "Public identity, ledger and workbench integration workflows.",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"variable": [
- { "key": "identity_base_url", "value": "https://identity.mavula.dev" },
- { "key": "ledger_base_url", "value": "https://ledger.mavula.dev" },
- { "key": "workbench_base_url", "value": "https://workbench.mavula.dev" },
+ { "key": "identity_base_url", "value": "https://identity.mavula.io" },
+ { "key": "ledger_base_url", "value": "https://ledger.mavula.io" },
+ { "key": "workbench_base_url", "value": "https://workbench.mavula.io" },
+ { "key": "client_id", "value": "" },
+ { "key": "client_assertion", "value": "" },
{ "key": "access_token", "value": "" },
{ "key": "account_id", "value": "" },
{ "key": "request_id", "value": "" }
@@ -17,6 +20,7 @@
"name": "Identity",
"item": [
{
+ "id": "b5087a27-65f4-42ae-943c-559b0af9ce81",
"name": "Effective identity",
"request": {
"method": "GET",
@@ -30,6 +34,7 @@
"name": "Accounts",
"item": [
{
+ "id": "4ce12ccf-e45b-47dc-bf4d-8c67dfbdc421",
"name": "Create account",
"request": {
"method": "POST",
@@ -44,6 +49,7 @@
}
},
{
+ "id": "096c1163-bf05-4e72-8b31-627dfeb29c6f",
"name": "Submit freeze request",
"request": {
"method": "POST",
@@ -63,6 +69,7 @@
"name": "Workbench",
"item": [
{
+ "id": "bb96c02f-4210-4f78-b8ed-f540c466f835",
"name": "Read platform status",
"request": {
"method": "GET",
diff --git a/openapi/identity-access.public.v1.yaml b/openapi/identity-access.public.v1.yaml
index 7434d0f..5108fa4 100644
--- a/openapi/identity-access.public.v1.yaml
+++ b/openapi/identity-access.public.v1.yaml
@@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- - url: https://identity.mavula.dev
+ - url: https://identity.mavula.io
description: Identity Access public endpoint
tags:
- name: Discovery
@@ -33,14 +33,14 @@ paths:
application/json:
schema: { $ref: '#/components/schemas/OpenIdConfiguration' }
example:
- issuer: https://identity.mavula.dev
- authorization_endpoint: https://identity.mavula.dev/auth
- token_endpoint: https://identity.mavula.dev/token
- jwks_uri: https://identity.mavula.dev/jwks
- revocation_endpoint: https://identity.mavula.dev/token/revocation
+ issuer: https://identity.mavula.io
+ authorization_endpoint: https://identity.mavula.io/auth
+ token_endpoint: https://identity.mavula.io/token
+ jwks_uri: https://identity.mavula.io/jwks
+ revocation_endpoint: https://identity.mavula.io/token/revocation
response_types_supported: [code]
grant_types_supported: [authorization_code, refresh_token, client_credentials]
- token_endpoint_auth_methods_supported: [client_secret_basic, client_secret_post]
+ token_endpoint_auth_methods_supported: [none, private_key_jwt]
id_token_signing_alg_values_supported: [PS256]
/jwks:
get:
@@ -72,7 +72,7 @@ paths:
'302':
description: Redirect to the operator interaction or registered callback URI
headers:
- Location: { schema: { type: string, format: uri } }
+ Location: { schema: { type: string } }
'400': { $ref: '#/components/responses/OAuthError' }
/token:
post:
@@ -93,7 +93,14 @@ paths:
authorization_code:
value: { grant_type: authorization_code, code: auth_code_value, redirect_uri: 'https://app.example/callback', code_verifier: pkce_verifier }
client_credentials:
- value: { grant_type: client_credentials, client_id: service_client, client_secret: redacted, scope: internal.worker }
+ value:
+ grant_type: client_credentials
+ client_id: service_client
+ client_assertion_type: urn:ietf:params:oauth:client-assertion-type:jwt-bearer
+ client_assertion: signed_private_key_jwt
+ resource: urn:mavula:ledger-core
+ tenant_id: tenant_01
+ scope: internal.worker
responses:
'200':
description: OAuth token response
@@ -154,6 +161,15 @@ components:
openid: Authenticate an operator.
profile: Read effective identity claims.
finance.read: Read tenant-scoped financial resources.
+ finance.write: Submit tenant-scoped financial operations.
+ finance.approve: Approve controlled financial operations.
+ configuration.write: Manage tenant product configuration.
+ compliance.manage: Manage regulatory interoperability operations.
+ audit.read: Read authorized audit evidence.
+ identity.admin: Manage institutional identity configuration.
+ workbench.read: Read Workbench operational state.
+ workbench.jobs.write: Submit supported Workbench jobs.
+ observability.read: Read authorized operational status.
clientCredentials:
tokenUrl: /token
scopes:
@@ -199,6 +215,10 @@ components:
redirect_uri: { type: string, format: uri }
code_verifier: { type: string }
client_id: { type: string }
+ client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
+ client_assertion: { type: string }
+ resource: { type: string }
+ tenant_id: { type: string }
RefreshTokenRequest:
type: object
additionalProperties: false
@@ -208,15 +228,22 @@ components:
refresh_token: { type: string }
client_id: { type: string }
scope: { type: string }
+ client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
+ client_assertion: { type: string }
+ resource: { type: string }
+ tenant_id: { type: string }
ClientCredentialsTokenRequest:
type: object
additionalProperties: false
- required: [grant_type]
+ required: [grant_type, client_id, client_assertion_type, client_assertion, resource]
properties:
grant_type: { const: client_credentials }
client_id: { type: string }
- client_secret: { type: string, format: password }
scope: { type: string }
+ client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
+ client_assertion: { type: string }
+ resource: { type: string }
+ tenant_id: { type: string }
RevokeTokenRequest:
type: object
additionalProperties: false
@@ -225,7 +252,8 @@ components:
token: { type: string }
token_type_hint: { type: string, enum: [access_token, refresh_token] }
client_id: { type: string }
- client_secret: { type: string, format: password }
+ client_assertion_type: { type: string, const: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' }
+ client_assertion: { type: string }
TokenResponse:
type: object
required: [access_token, token_type, expires_in]
diff --git a/openapi/ledger-core.public.v1.yaml b/openapi/ledger-core.public.v1.yaml
index 91d0e56..ffa68b8 100644
--- a/openapi/ledger-core.public.v1.yaml
+++ b/openapi/ledger-core.public.v1.yaml
@@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- - url: https://ledger.mavula.dev
+ - url: https://ledger.mavula.io
description: Ledger Core public endpoint
security: [{ bearerAuth: [] }]
tags:
@@ -717,8 +717,8 @@ components:
required: [account_code]
properties:
account_code: { type: string, minLength: 1, maxLength: 64 }
- debit_amount: { type: string }
- credit_amount: { type: string }
+ debit_amount: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
+ credit_amount: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
AccountPosting:
type: object
additionalProperties: false
@@ -726,7 +726,7 @@ components:
properties:
account_id: { type: string }
direction: { type: string, enum: [DEBIT, CREDIT] }
- amount: { type: string }
+ amount: { type: string, pattern: '^(?:[1-9][0-9]*(?:\.[0-9]{1,2})?|0\.(?:0[1-9]|[1-9][0-9]?))$' }
currency: { type: string, pattern: '^[A-Z]{3}$' }
reference: { type: string, maxLength: 160 }
FinancialCorrection:
@@ -737,12 +737,15 @@ components:
type: object
required: [amount, currency]
properties:
- amount: { type: string }
+ amount: { type: string, pattern: '^(?:[1-9][0-9]*(?:\.[0-9]{1,2})?|0\.(?:0[1-9]|[1-9][0-9]?))$' }
currency: { type: string, pattern: '^[A-Z]{3}$' }
allocation:
type: object
required: [principal, interest, fees]
- properties: { principal: { type: string }, interest: { type: string }, fees: { type: string } }
+ properties:
+ principal: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
+ interest: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
+ fees: { type: string, pattern: '^(?:0|[1-9][0-9]*)(?:\.[0-9]{1,2})?$' }
journal:
type: object
required: [ledger_lines]
@@ -760,28 +763,32 @@ components:
reason: { type: string, minLength: 1, maxLength: 500 }
correction: { $ref: '#/components/schemas/FinancialCorrection' }
FinancialAdjustmentRequest:
- allOf:
- - $ref: '#/components/schemas/CreateFinancialAdjustment'
- - type: object
- required: [id, status, requested_by, correlation_id, created_at]
- properties:
- id: { type: string }
- status: { $ref: '#/components/schemas/DecisionStatus' }
- target_transaction_id: { type: string }
- target_journal_entry_id: { type: string }
- target_loan_id: { type: string }
- requested_by: { type: string }
- decided_by: { type: string }
- decision_reason: { type: string }
- failure_reason: { type: string }
- correlation_id: { type: string }
- reversal_transaction_id: { type: string }
- reversal_journal_entry_id: { type: string }
- replacement_transaction_id: { type: string }
- replacement_journal_entry_id: { type: string }
- created_at: { type: string, format: date-time }
- decided_at: { type: string, format: date-time }
- applied_at: { type: string, format: date-time }
+ type: object
+ additionalProperties: false
+ required: [id, target_type, target_id, adjustment_type, status, reason, requested_by, correlation_id, created_at]
+ properties:
+ id: { type: string }
+ target_type: { type: string, enum: [TRANSACTION, JOURNAL_ENTRY] }
+ target_id: { type: string, minLength: 1, maxLength: 128 }
+ adjustment_type: { type: string, enum: [REVERSAL, CORRECTION] }
+ status: { $ref: '#/components/schemas/DecisionStatus' }
+ reason: { type: string, minLength: 1, maxLength: 500 }
+ correction: { $ref: '#/components/schemas/FinancialCorrection' }
+ target_transaction_id: { type: string }
+ target_journal_entry_id: { type: string }
+ target_loan_id: { type: string }
+ requested_by: { type: string }
+ decided_by: { type: string }
+ decision_reason: { type: string }
+ failure_reason: { type: string }
+ correlation_id: { type: string }
+ reversal_transaction_id: { type: string }
+ reversal_journal_entry_id: { type: string }
+ replacement_transaction_id: { type: string }
+ replacement_journal_entry_id: { type: string }
+ created_at: { type: string, format: date-time }
+ decided_at: { type: string, format: date-time }
+ applied_at: { type: string, format: date-time }
FinancialAdjustmentPage:
type: object
required: [items]
diff --git a/openapi/workbench.public.v1.yaml b/openapi/workbench.public.v1.yaml
index 7cd1255..2bfcb0e 100644
--- a/openapi/workbench.public.v1.yaml
+++ b/openapi/workbench.public.v1.yaml
@@ -9,7 +9,7 @@ info:
name: GNU Affero General Public License v3.0 only
identifier: AGPL-3.0-only
servers:
- - url: https://workbench.mavula.dev
+ - url: https://workbench.mavula.io
description: Workbench public endpoint
security: [{ bearerAuth: [] }]
tags:
@@ -27,6 +27,9 @@ paths:
description: Submit tenant-scoped payment work. The tenant is always derived from the access token.
tags: [Jobs]
x-mavula-permissions: [workbench.jobs.write]
+ parameters:
+ - { $ref: '#/components/parameters/IdempotencyKey' }
+ - { $ref: '#/components/parameters/CorrelationId' }
requestBody:
required: true
content:
@@ -35,9 +38,7 @@ paths:
examples:
payment_capture:
value:
- queue: payments
type: PAYMENT_CAPTURE
- max_attempts: 3
payload:
idempotency_key: payment_capture_20260715_001
correlation_id: checkout_20260715_001
@@ -52,6 +53,8 @@ paths:
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
+ '409': { $ref: '#/components/responses/Conflict' }
+ '503': { $ref: '#/components/responses/ServiceUnavailable' }
/api/jobs/{jobId}:
get:
operationId: getJob
@@ -289,18 +292,40 @@ components:
additionalProperties: false
properties: { limit: { type: integer, minimum: 1, maximum: 500 } }
CreateJob:
+ oneOf:
+ - $ref: '#/components/schemas/CreatePaymentCaptureJob'
+ - $ref: '#/components/schemas/CreatePaymentDisbursementJob'
+ - $ref: '#/components/schemas/CreatePaymentSettlementJob'
+ - $ref: '#/components/schemas/CreatePaymentReconciliationJob'
+ discriminator: { propertyName: type }
+ CreatePaymentCaptureJob:
type: object
additionalProperties: false
required: [type, payload]
properties:
- type: { type: string, enum: [PAYMENT_CAPTURE, PAYMENT_DISBURSEMENT, PAYMENT_SETTLEMENT, PAYMENT_RECONCILIATION] }
- queue: { type: string, const: payments, default: payments }
- payload:
- oneOf:
- - $ref: '#/components/schemas/PaymentStartPayload'
- - $ref: '#/components/schemas/PaymentSettlementPayload'
- - $ref: '#/components/schemas/PaymentReconciliationPayload'
- max_attempts: { type: integer, minimum: 1, default: 3 }
+ type: { type: string, const: PAYMENT_CAPTURE }
+ payload: { $ref: '#/components/schemas/PaymentStartPayload' }
+ CreatePaymentDisbursementJob:
+ type: object
+ additionalProperties: false
+ required: [type, payload]
+ properties:
+ type: { type: string, const: PAYMENT_DISBURSEMENT }
+ payload: { $ref: '#/components/schemas/PaymentStartPayload' }
+ CreatePaymentSettlementJob:
+ type: object
+ additionalProperties: false
+ required: [type, payload]
+ properties:
+ type: { type: string, const: PAYMENT_SETTLEMENT }
+ payload: { $ref: '#/components/schemas/PaymentSettlementPayload' }
+ CreatePaymentReconciliationJob:
+ type: object
+ additionalProperties: false
+ required: [type, payload]
+ properties:
+ type: { type: string, const: PAYMENT_RECONCILIATION }
+ payload: { $ref: '#/components/schemas/PaymentReconciliationPayload' }
WorkerJob:
type: object
required: [id, queue, type, tenant_id, payload, status, attempts, max_attempts, created_at, updated_at]
@@ -397,12 +422,19 @@ components:
payload: { type: object, additionalProperties: true }
PlatformStatus:
type: object
+ additionalProperties: false
+ required: [status, service, version, environment, started_at, uptime_seconds, dependencies, worker, schedules, queues]
properties:
service: { type: string }
status: { type: string, enum: [ok, degraded, down] }
- timestamp: { type: string, format: date-time }
+ version: { type: string }
+ environment: { type: string }
+ started_at: { type: string, format: date-time }
+ uptime_seconds: { type: integer, minimum: 0 }
worker: { type: object, additionalProperties: true }
dependencies: { type: object, additionalProperties: true }
+ schedules: { type: array, items: { $ref: '#/components/schemas/ScheduledJob' } }
+ queues: { type: array, items: { $ref: '#/components/schemas/QueueStats' } }
HttpError:
type: object
required: [statusCode, message]
@@ -432,3 +464,6 @@ components:
Conflict:
description: Idempotency key, state, or delivery reference conflicts with existing state
content: { application/json: { schema: { $ref: '#/components/schemas/HttpError' } } }
+ ServiceUnavailable:
+ description: Durable receipt storage or queue execution is temporarily unavailable
+ content: { application/json: { schema: { $ref: '#/components/schemas/HttpError' } } }
diff --git a/sources.lock.json b/sources.lock.json
index 6b860c4..ec41afe 100644
--- a/sources.lock.json
+++ b/sources.lock.json
@@ -5,19 +5,19 @@
"file": "identity-access.public.v1.yaml",
"owner": "mavulahq/identity-access",
"source": "contracts/openapi/identity-access.public.v1.yaml",
- "sha256": "a126f53455b05bd8377fa12b81f814ae5d677acbbb0e8df33a706a5a0cf60a38"
+ "sha256": "2652296a97abed2726df27f23348101fe700cffee84fe4294aee524c957db30a"
},
{
"file": "ledger-core.public.v1.yaml",
"owner": "mavulahq/ledger-core",
"source": "contracts/openapi/ledger-core.public.v1.yaml",
- "sha256": "115cda1bff1aaea6a7aaad81577a0918e4ced2ec222cefc4081433a08778f2a1"
+ "sha256": "dcae9e81479b62ac5641eb20a8a231628bfab9fc6edec3477b1e128a7acdc3e7"
},
{
"file": "workbench.public.v1.yaml",
"owner": "mavulahq/workbench",
"source": "contracts/openapi/workbench.public.v1.yaml",
- "sha256": "1b6e8a3fdb3c882c87932e6966612ea71706902adabd5f6ddd3bf356762e95d9"
+ "sha256": "14053111b3b8b94a5f0fbd9ead1d38b0f6fc491e1accace4568a87e2e135abd8"
}
]
}
diff --git a/src/content/docs/v1/getting-started/authentication.mdx b/src/content/docs/v1/getting-started/authentication.mdx
index f1e4939..8ea2c79 100644
--- a/src/content/docs/v1/getting-started/authentication.mdx
+++ b/src/content/docs/v1/getting-started/authentication.mdx
@@ -20,7 +20,7 @@ Identity Access publishes OpenID Provider metadata at `/.well-known/openid-confi
Generate a random `code_verifier`, derive its SHA-256 `code_challenge`, then redirect the operator to `/auth`. The `state` and `nonce` values must be unique and verified on return.
```text
-GET https://identity.mavula.dev/auth
+GET https://identity.mavula.io/auth
?response_type=code
&client_id=operator-console
&redirect_uri=https%3A%2F%2Fops.example.com%2Fcallback
@@ -34,7 +34,7 @@ GET https://identity.mavula.dev/auth
Exchange the returned code once:
```bash
-curl -sS -X POST https://identity.mavula.dev/token \
+curl -sS -X POST https://identity.mavula.io/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=operator-console' \
@@ -48,23 +48,45 @@ curl -sS -X POST https://identity.mavula.dev/token \
```bash
- curl -sS -X POST https://identity.mavula.dev/token \
+ curl -sS -X POST https://identity.mavula.io/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id=YOUR_CLIENT_ID' \
- --data-urlencode 'client_secret=YOUR_CLIENT_SECRET' \
- --data-urlencode 'scope=internal.worker'
+ --data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
+ --data-urlencode "client_assertion=$CLIENT_ASSERTION" \
+ --data-urlencode 'scope=internal.worker' \
+ --data-urlencode 'resource=urn:mavula:workbench' \
+ --data-urlencode 'tenant_id=YOUR_TENANT_ID'
```
```ts
+ import { importJWK, SignJWT } from 'jose';
+ import { randomUUID } from 'node:crypto';
+
+ const clientId = process.env.MAVULA_CLIENT_ID!;
+ const tokenEndpoint = 'https://identity.mavula.io/token';
+ const privateJwk = JSON.parse(process.env.MAVULA_PRIVATE_JWK_JSON!);
+ const signingKey = await importJWK(privateJwk, 'PS256');
+ const clientAssertion = await new SignJWT({})
+ .setProtectedHeader({ alg: 'PS256', kid: privateJwk.kid })
+ .setIssuer(clientId)
+ .setSubject(clientId)
+ .setAudience(tokenEndpoint)
+ .setJti(randomUUID())
+ .setIssuedAt()
+ .setExpirationTime('2m')
+ .sign(signingKey);
const body = new URLSearchParams({
grant_type: 'client_credentials',
- client_id: process.env.MAVULA_CLIENT_ID!,
- client_secret: process.env.MAVULA_CLIENT_SECRET!,
+ client_id: clientId,
+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+ client_assertion: clientAssertion,
scope: 'internal.worker',
+ resource: 'urn:mavula:workbench',
+ tenant_id: process.env.MAVULA_TENANT_ID!,
});
- const response = await fetch('https://identity.mavula.dev/token', {
+ const response = await fetch(tokenEndpoint, {
method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body,
});
if (!response.ok) throw new Error(`token request failed: ${response.status}`);
@@ -73,7 +95,7 @@ curl -sS -X POST https://identity.mavula.dev/token \
-Client credentials represent an approved internal worker identity. They do not replace operator authorization for Ledger Core or compliance operations.
+Client credentials represent an approved internal worker identity. Identity Access accepts only registered public RSA keys with `private_key_jwt` and `PS256`; assertions must be short-lived and use a unique `jti`. Service credentials do not replace operator authorization for Ledger Core or compliance operations.
## Refresh and revoke
diff --git a/src/content/docs/v1/getting-started/quickstart.mdx b/src/content/docs/v1/getting-started/quickstart.mdx
index af439c1..8023f04 100644
--- a/src/content/docs/v1/getting-started/quickstart.mdx
+++ b/src/content/docs/v1/getting-started/quickstart.mdx
@@ -12,7 +12,7 @@ This flow uses OAuth 2.0 authorization code with PKCE for an institutional opera
1. **Exchange the authorization code**
```bash
- curl -sS -X POST https://identity.mavula.dev/token \
+ curl -sS -X POST https://identity.mavula.io/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'client_id=operator-console' \
@@ -24,7 +24,7 @@ This flow uses OAuth 2.0 authorization code with PKCE for an institutional opera
2. **Inspect the effective identity**
```bash
- curl -sS https://identity.mavula.dev/api/v1/me \
+ curl -sS https://identity.mavula.io/api/v1/me \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN"
```
@@ -33,7 +33,7 @@ This flow uses OAuth 2.0 authorization code with PKCE for an institutional opera
3. **Create an account**
```bash
- curl -sS -X POST https://ledger.mavula.dev/api/accounts \
+ curl -sS -X POST https://ledger.mavula.io/api/accounts \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 76b05d81-c975-4daf-b054-72b2eb3a749a' \
@@ -44,7 +44,7 @@ This flow uses OAuth 2.0 authorization code with PKCE for an institutional opera
4. **Read the balance**
```bash
- curl -sS https://ledger.mavula.dev/api/accounts/ACCOUNT_ID/balance \
+ curl -sS https://ledger.mavula.io/api/accounts/ACCOUNT_ID/balance \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN" \
-H 'X-Correlation-ID: 406d8359-a53a-4d2a-a908-628798ece3b3'
```
diff --git a/src/content/docs/v1/getting-started/roles-permissions.mdx b/src/content/docs/v1/getting-started/roles-permissions.mdx
index cc8023a..10d49c2 100644
--- a/src/content/docs/v1/getting-started/roles-permissions.mdx
+++ b/src/content/docs/v1/getting-started/roles-permissions.mdx
@@ -9,12 +9,13 @@ Authorization follows least privilege and separation of duties. A role grants a
| Role | Typical responsibility | Core permissions |
| --- | --- | --- |
-| `operations_viewer` | Read operational and financial state | `finance.read` |
+| `institution_admin` | Manage institutional identity and configuration | `identity.admin`, `configuration.write` |
| `operations_maker` | Submit controlled changes | `finance.read`, `finance.write` |
| `operations_checker` | Approve or reject controlled changes | `finance.read`, `finance.approve` |
| `compliance_officer` | Review and generate regulated outputs | `finance.read`, `compliance.manage` |
-| `platform_administrator` | Manage tenant configuration and access | Explicit administrative permissions only |
-| `service_integration` | Execute a specific machine workflow | Narrow scopes for one integration |
+| `auditor` | Read audit and financial evidence | `audit.read`, `finance.read` |
+
+`service_integration` is an OAuth client class, not an operator role. Each machine client receives only the scopes required by one integration and authenticates with `private_key_jwt`.
## Maker-checker rule
@@ -23,7 +24,7 @@ The principal that submits an account lifecycle or financial adjustment request
## Verify access at runtime
```bash
-curl -sS https://identity.mavula.dev/api/v1/me \
+curl -sS https://identity.mavula.io/api/v1/me \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN"
```
diff --git a/src/content/docs/v1/guides/account-lifecycle.mdx b/src/content/docs/v1/guides/account-lifecycle.mdx
index 868e507..01f6289 100644
--- a/src/content/docs/v1/guides/account-lifecycle.mdx
+++ b/src/content/docs/v1/guides/account-lifecycle.mdx
@@ -23,7 +23,7 @@ import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
```bash
- curl -sS -X POST https://ledger.mavula.dev/api/accounts/ACCOUNT_ID/status-transitions \
+ curl -sS -X POST https://ledger.mavula.io/api/accounts/ACCOUNT_ID/status-transitions \
-H "Authorization: Bearer $MAKER_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7f2b1e3a-4c2d-4d63-9b1a-2f7b6c0d5e91' \
@@ -83,7 +83,7 @@ import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
## Approve as a different operator
```bash
-curl -sS -X POST https://ledger.mavula.dev/api/account-lifecycle-requests/REQUEST_ID/approve \
+curl -sS -X POST https://ledger.mavula.io/api/account-lifecycle-requests/REQUEST_ID/approve \
-H "Authorization: Bearer $CHECKER_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: a65e192f-aa5f-4dc9-a111-baf6136595a0' \
diff --git a/src/content/docs/v1/guides/account-setup.mdx b/src/content/docs/v1/guides/account-setup.mdx
index 64bd3cf..615e068 100644
--- a/src/content/docs/v1/guides/account-setup.mdx
+++ b/src/content/docs/v1/guides/account-setup.mdx
@@ -12,7 +12,7 @@ Required permission: `finance.write` to create, `finance.read` to inspect.
1. **Create the account**
```bash
- curl -sS -X POST https://ledger.mavula.dev/api/accounts \
+ curl -sS -X POST https://ledger.mavula.io/api/accounts \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 56a1f534-9054-4dcf-82cb-55ebc102da18' \
@@ -23,14 +23,14 @@ Required permission: `finance.write` to create, `finance.read` to inspect.
2. **Read the current balance**
```bash
- curl -sS https://ledger.mavula.dev/api/accounts/ACCOUNT_ID/balance \
+ curl -sS https://ledger.mavula.io/api/accounts/ACCOUNT_ID/balance \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN"
```
3. **Page through the statement**
```bash
- curl -sS 'https://ledger.mavula.dev/api/accounts/ACCOUNT_ID/statement?limit=100' \
+ curl -sS 'https://ledger.mavula.io/api/accounts/ACCOUNT_ID/statement?limit=100' \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN"
```
diff --git a/src/content/docs/v1/guides/configuration.mdx b/src/content/docs/v1/guides/configuration.mdx
index e40545f..a817325 100644
--- a/src/content/docs/v1/guides/configuration.mdx
+++ b/src/content/docs/v1/guides/configuration.mdx
@@ -8,7 +8,7 @@ Configuration APIs are tenant-scoped. Apply changes first in a non-production te
## Generate jurisdiction defaults
```bash
-curl -sS -X POST https://ledger.mavula.dev/api/products/config/generate \
+curl -sS -X POST https://ledger.mavula.io/api/products/config/generate \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 4cb0de72-2805-4490-8e47-df403c118275' \
diff --git a/src/content/docs/v1/guides/financial-adjustments.mdx b/src/content/docs/v1/guides/financial-adjustments.mdx
index bf0e9fb..c563ddf 100644
--- a/src/content/docs/v1/guides/financial-adjustments.mdx
+++ b/src/content/docs/v1/guides/financial-adjustments.mdx
@@ -8,7 +8,7 @@ Financial history is not edited in place. Submit a `REVERSAL` or `CORRECTION` ag
## Submit a reversal
```bash
-curl -sS -X POST https://ledger.mavula.dev/api/financial-adjustment-requests \
+curl -sS -X POST https://ledger.mavula.io/api/financial-adjustment-requests \
-H "Authorization: Bearer $MAKER_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 7b0491f2-eb4c-42b2-b77d-acadd27c571b' \
@@ -19,7 +19,7 @@ curl -sS -X POST https://ledger.mavula.dev/api/financial-adjustment-requests \
## Approve or reject
```bash
-curl -sS -X POST https://ledger.mavula.dev/api/financial-adjustment-requests/REQUEST_ID/approve \
+curl -sS -X POST https://ledger.mavula.io/api/financial-adjustment-requests/REQUEST_ID/approve \
-H "Authorization: Bearer $CHECKER_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 147598a5-10ee-49cc-b6e5-e5415bb65844' \
diff --git a/src/content/docs/v1/guides/legacy-batches.mdx b/src/content/docs/v1/guides/legacy-batches.mdx
index 45b58bb..59fe4a4 100644
--- a/src/content/docs/v1/guides/legacy-batches.mdx
+++ b/src/content/docs/v1/guides/legacy-batches.mdx
@@ -14,7 +14,7 @@ Workbench orchestrates auditable batch requests. Legacy imports are staged and v
1. **Request the export**
```bash
- curl -sS -X POST https://workbench.mavula.dev/api/regulatory-exports \
+ curl -sS -X POST https://workbench.mavula.io/api/regulatory-exports \
-H "Authorization: Bearer $COMPLIANCE_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 0e03ef93-09e0-413f-b49a-e59f93fa881f' \
@@ -28,12 +28,12 @@ Workbench orchestrates auditable batch requests. Legacy imports are staged and v
3. **Download the artifact**
- Use `GET /api/legacy-batches/{batchId}/artifact` only after the batch reports a completed state. Verify the response media type and checksum before delivery.
+ Use `GET /api/legacy-batches/{batchId}/artifact` after an export reaches `GENERATED`. Verify the response media type and checksum before delivery.
4. **Record authority delivery**
```bash
- curl -sS -X POST https://workbench.mavula.dev/api/regulatory-exports/BATCH_ID/delivery \
+ curl -sS -X POST https://workbench.mavula.io/api/regulatory-exports/BATCH_ID/delivery \
-H "Authorization: Bearer $COMPLIANCE_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: 076a329b-84f5-4f58-99ef-04ac14f3a343' \
@@ -46,6 +46,20 @@ Workbench orchestrates auditable batch requests. Legacy imports are staged and v
Submit the file metadata and import parameters to `POST /api/legacy-imports`. Inspect deterministic validation failures at `GET /api/legacy-batches/{batchId}/rejections`.
+## Receipt states
+
+| State | Export | Import | Meaning |
+| --- | --- | --- | --- |
+| `QUEUED` | Yes | Yes | Durable receipt created; processing has not started |
+| `PROCESSING` | Yes | Yes | A worker owns a fenced lease |
+| `GENERATED` | Yes | No | Export artifact is available |
+| `VALIDATED` | No | Yes | Import artifact passed validation |
+| `REJECTED` | Yes | Yes | Deterministic business or format validation failed |
+| `FAILED` | Yes | Yes | Processing exhausted its retry budget |
+| `DELIVERED` | Yes | No | Authority delivery evidence was recorded idempotently |
+
+Retries use the same batch receipt. Lease identifiers, request digests and delivery digests are internal and are never returned by the public API.
+
diff --git a/src/content/docs/v1/guides/payment-jobs.mdx b/src/content/docs/v1/guides/payment-jobs.mdx
index 0a35fd5..8405122 100644
--- a/src/content/docs/v1/guides/payment-jobs.mdx
+++ b/src/content/docs/v1/guides/payment-jobs.mdx
@@ -8,7 +8,7 @@ Workbench accepts payment jobs and delegates durable process state to the settle
## Submit a job
```bash
-curl -sS -X POST https://workbench.mavula.dev/api/jobs \
+curl -sS -X POST https://workbench.mavula.io/api/jobs \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: bfe8b575-2137-4a86-a210-cc2182130aed' \
@@ -19,7 +19,7 @@ curl -sS -X POST https://workbench.mavula.dev/api/jobs \
Persist the returned job identifier and poll the status resource:
```bash
-curl -sS https://workbench.mavula.dev/api/jobs/JOB_ID \
+curl -sS https://workbench.mavula.io/api/jobs/JOB_ID \
-H "Authorization: Bearer $MAVULA_ACCESS_TOKEN"
```
diff --git a/src/content/docs/v1/index.mdx b/src/content/docs/v1/index.mdx
index e10a325..942bdfe 100644
--- a/src/content/docs/v1/index.mdx
+++ b/src/content/docs/v1/index.mdx
@@ -9,9 +9,9 @@ MAVULA API v1 separates identity, financial state and asynchronous operations in
| API | Base URL | Interaction model |
| --- | --- | --- |
-| Identity Access | `https://identity.mavula.dev` | OAuth 2.0 and OpenID Connect |
-| Ledger Core | `https://ledger.mavula.dev` | Synchronous commands and reads |
-| Workbench | `https://workbench.mavula.dev` | Asynchronous jobs and operational reads |
+| Identity Access | `https://identity.mavula.io` | OAuth 2.0 and OpenID Connect |
+| Ledger Core | `https://ledger.mavula.io` | Synchronous commands and reads |
+| Workbench | `https://workbench.mavula.io` | Asynchronous jobs and operational reads |
Tenant scope is derived from the authenticated identity. Do not send a tenant selector in the URL or body unless a specific operation schema requires it.
@@ -27,4 +27,3 @@ Content-Type: application/json
```
Start with the [quickstart](./getting-started/quickstart/) or select a workflow from the sidebar.
-
diff --git a/src/content/docs/v1/resources/code-examples.mdx b/src/content/docs/v1/resources/code-examples.mdx
index b0d8ee0..65d4be9 100644
--- a/src/content/docs/v1/resources/code-examples.mdx
+++ b/src/content/docs/v1/resources/code-examples.mdx
@@ -19,7 +19,7 @@ The guides include executable fragments for curl, TypeScript, Python, Java and G
## Postman
-The repository includes a versioned collection at [`examples/postman/MAVULA-API-v1.postman_collection.json`](https://github.com/mavulahq/developer-docs/blob/main/examples/postman/MAVULA-API-v1.postman_collection.json). Define `identity_base_url`, `ledger_base_url`, `workbench_base_url`, `client_id`, `client_secret` and `access_token` in a private Postman environment.
+The repository includes a versioned collection at [`examples/postman/MAVULA-API-v1.postman_collection.json`](https://github.com/mavulahq/developer-docs/blob/main/examples/postman/MAVULA-API-v1.postman_collection.json). Define `identity_base_url`, `ledger_base_url`, `workbench_base_url`, `client_id`, `client_assertion` and `access_token` in a private Postman environment. Generate `client_assertion` outside the collection from the registered private RSA key; never store the key in Postman.
## Reference contracts
@@ -28,4 +28,3 @@ The repository includes a versioned collection at [`examples/postman/MAVULA-API-
- [Workbench OpenAPI](/developer-docs/openapi/workbench.public.v1.yaml)
Use these contracts to generate clients in your preferred language. Pin the downloaded contract digest in regulated build pipelines.
-
diff --git a/test/contracts.test.mjs b/test/contracts.test.mjs
index 64855f4..e69cf11 100644
--- a/test/contracts.test.mjs
+++ b/test/contracts.test.mjs
@@ -46,8 +46,8 @@ test('published examples use canonical hosts and contract payload names', () =>
'examples/postman/MAVULA-API-v1.postman_collection.json',
];
const content = files.map((file) => readFileSync(file, 'utf8')).join('\n');
- assert.doesNotMatch(content, /https:\/\/(?:identity|ledger|workbench)\.mavula\.io/);
- for (const expectation of ['mavula.dev', 'compliance.manage', 'valueMinor', 'period_from', 'authority_reference']) {
+ assert.doesNotMatch(content, /https:\/\/(?:identity|ledger|workbench)\.mavula\.dev/);
+ for (const expectation of ['mavula.io', 'compliance.manage', 'valueMinor', 'period_from', 'authority_reference']) {
assert.match(content, new RegExp(expectation, 'i'));
}
});