{
	"openapi": "3.0.3",
	"info": {
		"title": "Digital Savings — DJS API",
		"version": "1.0.0",
		"description": "**MoMo Savings+** — a digital locked-savings product for MTN MoMo customers in Congo, operated under the BCH banking licence. This is its DJS (Dynamic Journey Service) API: what MTN's USSD gateway calls, once per step, to drive a customer through a savings journey — registering, depositing into a locked position, checking a balance, withdrawing early, requesting a statement.\n\nEvery operation goes through one of two endpoints, picked by whether it returns a single record or a list; the `operation_name` argument in a shared envelope selects which operation actually runs. Pick an operation from the request/response **examples** below to see the exact envelope to send and the keys you get back.\n\nProduction ingress (the MoMo gateway channel) uses standard TLS (HTTPS) — no client certificate is needed or accepted.\n\nClicking **Try it out** gets you a real response only when this page is loaded from a stage's own docs site (`docs.<stage>-savings-ussd-api.kamoa.io`) with that same stage selected as the server below — any other combination (a different stage's server, or this page loaded from the plain CloudFront domain) is blocked by design, not a bug. To reach any stage from anywhere, use the Postman collection instead."
	},
	"servers": [
		{
			"url": "https://develop-savings-ussd-api.kamoa.io",
			"description": "develop — MoMo sandbox (default), live over HTTPS."
		},
		{
			"url": "https://internal-savings-ussd-api.kamoa.io",
			"description": "internal — our test stage, live over HTTPS."
		}
	],
	"paths": {
		"/getdynamicarguments": {
			"post": {
				"operationId": "getDynamicArguments",
				"summary": "Single-record operations (one record of return keys).",
				"description": "Operations:\n- `check_registration_status` — Is this MSISDN registered? Routes to the Home Menu or to the registration disclosure screen. Returns: registration_status, account_id?, user_email?.\n- `register_savings_account` — Opens the savings account for this MSISDN (idempotent; re-dial → ALREADY_REGISTERED). Returns: registration_result, account_id?, failure_reason?.\n- `create_deposit_summary` — Quotes the chosen amount against the savings this money would actually go into: a new one at a full term, or the one the customer already holds in this product, which it joins for whatever is left of that savings' term. Reads the customer's claim to decide which; writes nothing but the SavingsRequestLog row confirm reads back. Returns: summary_status, deposit_basis?, amount?, interest_rate?, estimated_earnings?, total_at_maturity?, duration_days?, maturity_date?, locked_account_id?, savings_balance_now?, savings_balance_after?, savings_earnings_at_maturity?, savings_total_at_maturity?, minimum_amount?, currency, product_code?, deposit_reference?.\n- `confirm_deposit` — Writes the PENDING PaymentOrder + fires the MoMo Request-to-Pay; PENDING_VALIDATION until the async outcome settles. Trusts the quote create_deposit_summary recorded, not resent amount/product_code. Returns: deposit_status, deposit_reference?, payment_message?, failure_reason?.\n- `create_withdrawal_summary` — Withdrawal preview for one LOCKED position (no state written): what it would pay now vs. what waiting to maturity would earn. Mints withdrawal_reference for confirm. Returns: position_status, amount?, original_rate?, withdrawal_basis?, accrued_interest?, interest_at_maturity?, effective_rate?, withdrawable_amount?, forfeited_interest?, total_payout, currency, payout_status?, payout_message?, settlement_delay?, settlement_reference?, withdrawal_reference?.\n- `confirm_withdrawal` — Reserves the LOCKED position + fires the MoMo Disbursement; PENDING until the async outcome settles. Trusts the position create_withdrawal_summary recorded, not a resent locked_account_id. Returns: withdrawal_status, withdrawal_reference?, settlement_reference?, payout_status?, payout_message?, settlement_delay?, failure_reason?.\n- `get_savings_balance` — Savings totals, split across the three states a savings can be in: still earning, matured and waiting, and already on its way out. The three amounts add up to total_balance and the three counts to position_count. Paired with list_locked_accounts for the per-position breakdown. Returns: total_balance, accruing_balance, due_balance, total_interest, pending_withdrawal_amount, accruing_count, due_count, paying_out_count, position_count, currency.\n- `confirm_opt_out` — Early-exits every open locked position, then closes the account once everything has settled. Returns: opt_out_status, failure_reason?, settlement_delay?.\n- `get_user_email` — Fetches the email on file (internal step; happy path = on file). Returns: email, email_status.\n- `create_full_statement_summary` — Mints the reference the confirm step (request_full_statement) echoes back to make it idempotent (no state written). Returns: statement_reference.\n- `request_full_statement` — Confirms/updates the email on file, then hands the period-bounded PDF generation + delivery off to a background worker (async email delivery). Returns: request_status, failure_reason?.\n- `check_statement_status` — Where a requested statement got to. request_full_statement only answers for the moment it was called — the render and the send finish on a worker, after the session has ended. Returns: statement_status.\n- `create_test_clock` — Creates a movable today, so a customer registered against it can be walked to maturity without waiting. Returns: clock_id, now, created_at.\n- `get_test_clock` — Where a clock currently stands, and how many customers are attached to it. Returns: clock_id, now, created_at, attached_customer_count, advancing_to?.\n- `reset_test_customer` — Puts a clocked customer back into a usable state: registered, nothing locked, nothing in flight, clock idle. Does nothing when that is already true. Re-entrant — call again while it reports WORKING. Returns: reset_status, reset_waiting_on?.\n- `advance_test_clock` — Walks a clock to a later date, running each day's accrual, maturity and hold-release work. Returns once queued — poll get_test_clock until advancing_to clears. Asking for the date the clock is already on is not a rewind: it answers where the clock stands and starts no walk. Returns: clock_id, now, advancing_to?.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/DjsEnvelope"
							},
							"examples": {
								"check_registration_status": {
									"summary": "Is this MSISDN registered? Routes to the Home Menu or to the registration disclosure screen.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "check_registration_status"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"register_savings_account": {
									"summary": "Opens the savings account for this MSISDN (idempotent; re-dial → ALREADY_REGISTERED).",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "register_savings_account"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_EMAIL"
												},
												"value": {
													"value": "client@example.cd"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"create_deposit_summary": {
									"summary": "Quotes the chosen amount against the savings this money would actually go into: a new one at a full term, or the one the customer already holds in this product, which it joins for whatever is left of that savings' term. Reads the customer's claim to decide which; writes nothing but the SavingsRequestLog row confirm reads back.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "create_deposit_summary"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "amount"
												},
												"value": {
													"value": "50000"
												}
											},
											{
												"key": {
													"value": "product_code"
												},
												"value": {
													"value": "prod3m"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"confirm_deposit": {
									"summary": "Writes the PENDING PaymentOrder + fires the MoMo Request-to-Pay; PENDING_VALIDATION until the async outcome settles. Trusts the quote create_deposit_summary recorded, not resent amount/product_code.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "confirm_deposit"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "deposit_reference"
												},
												"value": {
													"value": "dep_2f1c…"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"create_withdrawal_summary": {
									"summary": "Withdrawal preview for one LOCKED position (no state written): what it would pay now vs. what waiting to maturity would earn. Mints withdrawal_reference for confirm.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "create_withdrawal_summary"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "locked_account_id"
												},
												"value": {
													"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"confirm_withdrawal": {
									"summary": "Reserves the LOCKED position + fires the MoMo Disbursement; PENDING until the async outcome settles. Trusts the position create_withdrawal_summary recorded, not a resent locked_account_id.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "confirm_withdrawal"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "withdrawal_reference"
												},
												"value": {
													"value": "c4e7f8a2-1b3d-4f9e-8c0a-5d2e6f7a9b1c"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"get_savings_balance": {
									"summary": "Savings totals, split across the three states a savings can be in: still earning, matured and waiting, and already on its way out. The three amounts add up to total_balance and the three counts to position_count. Paired with list_locked_accounts for the per-position breakdown.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "get_savings_balance"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"confirm_opt_out": {
									"summary": "Early-exits every open locked position, then closes the account once everything has settled.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "confirm_opt_out"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"get_user_email": {
									"summary": "Fetches the email on file (internal step; happy path = on file).",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "get_user_email"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"create_full_statement_summary": {
									"summary": "Mints the reference the confirm step (request_full_statement) echoes back to make it idempotent (no state written).",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "create_full_statement_summary"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"request_full_statement": {
									"summary": "Confirms/updates the email on file, then hands the period-bounded PDF generation + delivery off to a background worker (async email delivery).",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "request_full_statement"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "email"
												},
												"value": {
													"value": "client@example.cd"
												}
											},
											{
												"key": {
													"value": "period"
												},
												"value": {
													"value": "3m"
												}
											},
											{
												"key": {
													"value": "statement_reference"
												},
												"value": {
													"value": "stmt_2f1c…"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"check_statement_status": {
									"summary": "Where a requested statement got to. request_full_statement only answers for the moment it was called — the render and the send finish on a worker, after the session has ended.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "check_statement_status"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											},
											{
												"key": {
													"value": "statement_reference"
												},
												"value": {
													"value": "stmt_2f1c…"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"create_test_clock": {
									"summary": "Creates a movable today, so a customer registered against it can be walked to maturity without waiting.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "create_test_clock"
												}
											},
											{
												"key": {
													"value": "now"
												},
												"value": {
													"value": "2026-01-15"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"get_test_clock": {
									"summary": "Where a clock currently stands, and how many customers are attached to it.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "get_test_clock"
												}
											},
											{
												"key": {
													"value": "clock_id"
												},
												"value": {
													"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"reset_test_customer": {
									"summary": "Puts a clocked customer back into a usable state: registered, nothing locked, nothing in flight, clock idle. Does nothing when that is already true. Re-entrant — call again while it reports WORKING.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "reset_test_customer"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"advance_test_clock": {
									"summary": "Walks a clock to a later date, running each day's accrual, maturity and hold-release work. Returns once queued — poll get_test_clock until advancing_to clears. Asking for the date the clock is already on is not a rewind: it answers where the clock stands and starts no walk.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "advance_test_clock"
												}
											},
											{
												"key": {
													"value": "clock_id"
												},
												"value": {
													"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
												}
											},
											{
												"key": {
													"value": "to"
												},
												"value": {
													"value": "2026-04-15"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The operation's return keys as an arguments record.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ArgumentsResponse"
								},
								"examples": {
									"check_registration_status": {
										"summary": "Is this MSISDN registered? Routes to the Home Menu or to the registration disclosure screen.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "registration_status"
													},
													"value": {
														"value": "REGISTERED"
													}
												},
												{
													"key": {
														"value": "account_id"
													},
													"value": {
														"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
													}
												},
												{
													"key": {
														"value": "user_email"
													},
													"value": {
														"value": "client@example.cd"
													}
												}
											]
										}
									},
									"register_savings_account": {
										"summary": "Opens the savings account for this MSISDN (idempotent; re-dial → ALREADY_REGISTERED).",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "registration_result"
													},
													"value": {
														"value": "REGISTERED"
													}
												},
												{
													"key": {
														"value": "account_id"
													},
													"value": {
														"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
													}
												}
											]
										}
									},
									"create_deposit_summary": {
										"summary": "Quotes the chosen amount against the savings this money would actually go into: a new one at a full term, or the one the customer already holds in this product, which it joins for whatever is left of that savings' term. Reads the customer's claim to decide which; writes nothing but the SavingsRequestLog row confirm reads back.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "summary_status"
													},
													"value": {
														"value": "OK"
													}
												},
												{
													"key": {
														"value": "deposit_basis"
													},
													"value": {
														"value": "TOP_UP"
													}
												},
												{
													"key": {
														"value": "amount"
													},
													"value": {
														"value": "50000"
													}
												},
												{
													"key": {
														"value": "interest_rate"
													},
													"value": {
														"value": "5"
													}
												},
												{
													"key": {
														"value": "estimated_earnings"
													},
													"value": {
														"value": "416"
													}
												},
												{
													"key": {
														"value": "total_at_maturity"
													},
													"value": {
														"value": "50416"
													}
												},
												{
													"key": {
														"value": "duration_days"
													},
													"value": {
														"value": "61"
													}
												},
												{
													"key": {
														"value": "maturity_date"
													},
													"value": {
														"value": "2026-11-15"
													}
												},
												{
													"key": {
														"value": "locked_account_id"
													},
													"value": {
														"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
													}
												},
												{
													"key": {
														"value": "savings_balance_now"
													},
													"value": {
														"value": "100000"
													}
												},
												{
													"key": {
														"value": "savings_balance_after"
													},
													"value": {
														"value": "150000"
													}
												},
												{
													"key": {
														"value": "savings_earnings_at_maturity"
													},
													"value": {
														"value": "1666"
													}
												},
												{
													"key": {
														"value": "savings_total_at_maturity"
													},
													"value": {
														"value": "151666"
													}
												},
												{
													"key": {
														"value": "currency"
													},
													"value": {
														"value": "XAF"
													}
												},
												{
													"key": {
														"value": "product_code"
													},
													"value": {
														"value": "prod3m"
													}
												},
												{
													"key": {
														"value": "deposit_reference"
													},
													"value": {
														"value": "dep_2f1c…"
													}
												}
											]
										}
									},
									"confirm_deposit": {
										"summary": "Writes the PENDING PaymentOrder + fires the MoMo Request-to-Pay; PENDING_VALIDATION until the async outcome settles. Trusts the quote create_deposit_summary recorded, not resent amount/product_code.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "deposit_status"
													},
													"value": {
														"value": "PENDING_VALIDATION"
													}
												},
												{
													"key": {
														"value": "deposit_reference"
													},
													"value": {
														"value": "dep_2f1c…"
													}
												},
												{
													"key": {
														"value": "payment_message"
													},
													"value": {
														"value": "Confirmez le paiement sur votre téléphone MoMo (saisissez votre code PIN)."
													}
												}
											]
										}
									},
									"create_withdrawal_summary": {
										"summary": "Withdrawal preview for one LOCKED position (no state written): what it would pay now vs. what waiting to maturity would earn. Mints withdrawal_reference for confirm.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "position_status"
													},
													"value": {
														"value": "ACCRUING"
													}
												},
												{
													"key": {
														"value": "amount"
													},
													"value": {
														"value": "100000"
													}
												},
												{
													"key": {
														"value": "original_rate"
													},
													"value": {
														"value": "5"
													}
												},
												{
													"key": {
														"value": "withdrawal_basis"
													},
													"value": {
														"value": "EARLY_EXIT"
													}
												},
												{
													"key": {
														"value": "accrued_interest"
													},
													"value": {
														"value": "833"
													}
												},
												{
													"key": {
														"value": "interest_at_maturity"
													},
													"value": {
														"value": "1250"
													}
												},
												{
													"key": {
														"value": "effective_rate"
													},
													"value": {
														"value": "4.5"
													}
												},
												{
													"key": {
														"value": "withdrawable_amount"
													},
													"value": {
														"value": "750"
													}
												},
												{
													"key": {
														"value": "forfeited_interest"
													},
													"value": {
														"value": "83"
													}
												},
												{
													"key": {
														"value": "total_payout"
													},
													"value": {
														"value": "100750"
													}
												},
												{
													"key": {
														"value": "currency"
													},
													"value": {
														"value": "XAF"
													}
												},
												{
													"key": {
														"value": "settlement_delay"
													},
													"value": {
														"value": "48h"
													}
												},
												{
													"key": {
														"value": "withdrawal_reference"
													},
													"value": {
														"value": "c4e7f8a2-1b3d-4f9e-8c0a-5d2e6f7a9b1c"
													}
												}
											]
										}
									},
									"confirm_withdrawal": {
										"summary": "Reserves the LOCKED position + fires the MoMo Disbursement; PENDING until the async outcome settles. Trusts the position create_withdrawal_summary recorded, not a resent locked_account_id.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "withdrawal_status"
													},
													"value": {
														"value": "PENDING"
													}
												},
												{
													"key": {
														"value": "withdrawal_reference"
													},
													"value": {
														"value": "c4e7f8a2-1b3d-4f9e-8c0a-5d2e6f7a9b1c"
													}
												},
												{
													"key": {
														"value": "settlement_delay"
													},
													"value": {
														"value": "48h"
													}
												}
											]
										}
									},
									"get_savings_balance": {
										"summary": "Savings totals, split across the three states a savings can be in: still earning, matured and waiting, and already on its way out. The three amounts add up to total_balance and the three counts to position_count. Paired with list_locked_accounts for the per-position breakdown.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "total_balance"
													},
													"value": {
														"value": "120000"
													}
												},
												{
													"key": {
														"value": "accruing_balance"
													},
													"value": {
														"value": "100000"
													}
												},
												{
													"key": {
														"value": "due_balance"
													},
													"value": {
														"value": "20000"
													}
												},
												{
													"key": {
														"value": "pending_withdrawal_amount"
													},
													"value": {
														"value": "0"
													}
												},
												{
													"key": {
														"value": "total_interest"
													},
													"value": {
														"value": "932"
													}
												},
												{
													"key": {
														"value": "accruing_count"
													},
													"value": {
														"value": "1"
													}
												},
												{
													"key": {
														"value": "due_count"
													},
													"value": {
														"value": "1"
													}
												},
												{
													"key": {
														"value": "paying_out_count"
													},
													"value": {
														"value": "0"
													}
												},
												{
													"key": {
														"value": "position_count"
													},
													"value": {
														"value": "2"
													}
												},
												{
													"key": {
														"value": "currency"
													},
													"value": {
														"value": "XAF"
													}
												}
											]
										}
									},
									"confirm_opt_out": {
										"summary": "Early-exits every open locked position, then closes the account once everything has settled.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "opt_out_status"
													},
													"value": {
														"value": "COMPLETED"
													}
												},
												{
													"key": {
														"value": "settlement_delay"
													},
													"value": {
														"value": "48h"
													}
												}
											]
										}
									},
									"get_user_email": {
										"summary": "Fetches the email on file (internal step; happy path = on file).",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "email"
													},
													"value": {
														"value": "client@example.cd"
													}
												},
												{
													"key": {
														"value": "email_status"
													},
													"value": {
														"value": "ON_FILE"
													}
												}
											]
										}
									},
									"create_full_statement_summary": {
										"summary": "Mints the reference the confirm step (request_full_statement) echoes back to make it idempotent (no state written).",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "statement_reference"
													},
													"value": {
														"value": "stmt_2f1c…"
													}
												}
											]
										}
									},
									"request_full_statement": {
										"summary": "Confirms/updates the email on file, then hands the period-bounded PDF generation + delivery off to a background worker (async email delivery).",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "request_status"
													},
													"value": {
														"value": "REQUESTED"
													}
												}
											]
										}
									},
									"check_statement_status": {
										"summary": "Where a requested statement got to. request_full_statement only answers for the moment it was called — the render and the send finish on a worker, after the session has ended.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "statement_status"
													},
													"value": {
														"value": "SENT"
													}
												}
											]
										}
									},
									"create_test_clock": {
										"summary": "Creates a movable today, so a customer registered against it can be walked to maturity without waiting.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "clock_id"
													},
													"value": {
														"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
													}
												},
												{
													"key": {
														"value": "now"
													},
													"value": {
														"value": "2026-01-15"
													}
												},
												{
													"key": {
														"value": "created_at"
													},
													"value": {
														"value": "2026-01-15T09:00:00.000Z"
													}
												}
											]
										}
									},
									"get_test_clock": {
										"summary": "Where a clock currently stands, and how many customers are attached to it.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "clock_id"
													},
													"value": {
														"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
													}
												},
												{
													"key": {
														"value": "now"
													},
													"value": {
														"value": "2026-02-15"
													}
												},
												{
													"key": {
														"value": "created_at"
													},
													"value": {
														"value": "2026-01-15T09:00:00.000Z"
													}
												},
												{
													"key": {
														"value": "attached_customer_count"
													},
													"value": {
														"value": "1"
													}
												},
												{
													"key": {
														"value": "advancing_to"
													},
													"value": {
														"value": "2026-04-15"
													}
												}
											]
										}
									},
									"reset_test_customer": {
										"summary": "Puts a clocked customer back into a usable state: registered, nothing locked, nothing in flight, clock idle. Does nothing when that is already true. Re-entrant — call again while it reports WORKING.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "reset_status"
													},
													"value": {
														"value": "READY"
													}
												}
											]
										}
									},
									"advance_test_clock": {
										"summary": "Walks a clock to a later date, running each day's accrual, maturity and hold-release work. Returns once queued — poll get_test_clock until advancing_to clears. Asking for the date the clock is already on is not a rewind: it answers where the clock stands and starts no walk.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "clock_id"
													},
													"value": {
														"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
													}
												},
												{
													"key": {
														"value": "now"
													},
													"value": {
														"value": "2026-01-15"
													}
												},
												{
													"key": {
														"value": "advancing_to"
													},
													"value": {
														"value": "2026-04-15"
													}
												}
											]
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Malformed envelope, invalid JSON, or unknown operation.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					}
				}
			}
		},
		"/getdynamicoptions": {
			"post": {
				"operationId": "getDynamicOptions",
				"summary": "Collection operations (one row of keys per option).",
				"description": "Operations:\n- `list_locked_accounts` — Locked positions the customer can withdraw from — also backs the balance screen's per-position breakdown (get_savings_balance only carries totals). One row per open LOCKED position; balance reflects any in-progress reservation. Returns: locked_account_id, position_status, payout_status?, payout_message?, settlement_reference?, settlement_delay?, label, maturity_date, current_balance, locked_rate?, accrued_interest?, interest_at_maturity?, withdrawable_amount?, withdrawal_basis?.\n- `list_opt_out_positions` — The closure breakdown: every open locked position with its early-exit terms, so the pre-confirmation screen can show what confirm_opt_out would settle. Returns: locked_account_id, position_status, label, maturity_date, amount, withdrawal_basis, original_rate, effective_rate, accrued_interest, interest_at_maturity, withdrawable_amount, forfeited_interest, payout, currency, total_payout, settlement_delay, total_settlement_delay.\n- `list_recent_transactions` — The last few transactions (mini statement). Returns: transaction_id, transaction_date, transaction_type, transaction_status, amount, currency.\n- `list_clocked_customers` — Every customer registered against a test clock, with the number to drive them by. Nothing stores the roster — a clocked customer is one whose profile carries a clock id, so the list is derived rather than kept in step. Returns: msisdn, clock_id, now, advancing_to?.",
				"requestBody": {
					"required": true,
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/DjsEnvelope"
							},
							"examples": {
								"list_locked_accounts": {
									"summary": "Locked positions the customer can withdraw from — also backs the balance screen's per-position breakdown (get_savings_balance only carries totals). One row per open LOCKED position; balance reflects any in-progress reservation.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "list_locked_accounts"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"list_opt_out_positions": {
									"summary": "The closure breakdown: every open locked position with its early-exit terms, so the pre-confirmation screen can show what confirm_opt_out would settle.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "list_opt_out_positions"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"list_recent_transactions": {
									"summary": "The last few transactions (mini statement).",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "list_recent_transactions"
												}
											},
											{
												"key": {
													"value": "ACCOUNT_HOLDER_MSISDN"
												},
												"value": {
													"value": "242060000001"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								},
								"list_clocked_customers": {
									"summary": "Every customer registered against a test clock, with the number to drive them by. Nothing stores the roster — a clocked customer is one whose profile carries a clock id, so the list is derived rather than kept in step.",
									"value": {
										"arguments": [
											{
												"key": {
													"value": "operation_name"
												},
												"value": {
													"value": "list_clocked_customers"
												}
											}
										],
										"languageCode": "fr",
										"sessionIdentifier": "example-session",
										"journeyIdentifier": "example-journey"
									}
								}
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "The options as an argumentsList (one row of keys each).",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ArgumentsListResponse"
								},
								"examples": {
									"list_locked_accounts": {
										"summary": "Locked positions the customer can withdraw from — also backs the balance screen's per-position breakdown (get_savings_balance only carries totals). One row per open LOCKED position; balance reflects any in-progress reservation.",
										"value": {
											"argumentsList": [
												[
													{
														"key": {
															"value": "locked_account_id"
														},
														"value": {
															"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
														}
													},
													{
														"key": {
															"value": "position_status"
														},
														"value": {
															"value": "ACCRUING"
														}
													},
													{
														"key": {
															"value": "label"
														},
														"value": {
															"value": "3 mois - 50000 FCFA"
														}
													},
													{
														"key": {
															"value": "maturity_date"
														},
														"value": {
															"value": "2026-09-11"
														}
													},
													{
														"key": {
															"value": "current_balance"
														},
														"value": {
															"value": "50000"
														}
													},
													{
														"key": {
															"value": "locked_rate"
														},
														"value": {
															"value": "5.5"
														}
													},
													{
														"key": {
															"value": "accrued_interest"
														},
														"value": {
															"value": "458"
														}
													},
													{
														"key": {
															"value": "interest_at_maturity"
														},
														"value": {
															"value": "687"
														}
													},
													{
														"key": {
															"value": "withdrawable_amount"
														},
														"value": {
															"value": "412"
														}
													},
													{
														"key": {
															"value": "withdrawal_basis"
														},
														"value": {
															"value": "EARLY_EXIT"
														}
													}
												]
											]
										}
									},
									"list_opt_out_positions": {
										"summary": "The closure breakdown: every open locked position with its early-exit terms, so the pre-confirmation screen can show what confirm_opt_out would settle.",
										"value": {
											"argumentsList": [
												[
													{
														"key": {
															"value": "locked_account_id"
														},
														"value": {
															"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
														}
													},
													{
														"key": {
															"value": "position_status"
														},
														"value": {
															"value": "ACCRUING"
														}
													},
													{
														"key": {
															"value": "label"
														},
														"value": {
															"value": "3 mois - 50000 FCFA"
														}
													},
													{
														"key": {
															"value": "maturity_date"
														},
														"value": {
															"value": "2026-09-11"
														}
													},
													{
														"key": {
															"value": "amount"
														},
														"value": {
															"value": "50000"
														}
													},
													{
														"key": {
															"value": "withdrawal_basis"
														},
														"value": {
															"value": "EARLY_EXIT"
														}
													},
													{
														"key": {
															"value": "original_rate"
														},
														"value": {
															"value": "5.5"
														}
													},
													{
														"key": {
															"value": "effective_rate"
														},
														"value": {
															"value": "3.2"
														}
													},
													{
														"key": {
															"value": "accrued_interest"
														},
														"value": {
															"value": "458"
														}
													},
													{
														"key": {
															"value": "interest_at_maturity"
														},
														"value": {
															"value": "625"
														}
													},
													{
														"key": {
															"value": "withdrawable_amount"
														},
														"value": {
															"value": "412"
														}
													},
													{
														"key": {
															"value": "forfeited_interest"
														},
														"value": {
															"value": "46"
														}
													},
													{
														"key": {
															"value": "payout"
														},
														"value": {
															"value": "50412"
														}
													},
													{
														"key": {
															"value": "currency"
														},
														"value": {
															"value": "XAF"
														}
													},
													{
														"key": {
															"value": "total_payout"
														},
														"value": {
															"value": "50412"
														}
													},
													{
														"key": {
															"value": "settlement_delay"
														},
														"value": {
															"value": "48h"
														}
													},
													{
														"key": {
															"value": "total_settlement_delay"
														},
														"value": {
															"value": "48h"
														}
													}
												]
											]
										}
									},
									"list_recent_transactions": {
										"summary": "The last few transactions (mini statement).",
										"value": {
											"argumentsList": [
												[
													{
														"key": {
															"value": "transaction_id"
														},
														"value": {
															"value": "7ade4636-df3e-4ac9-a73b-32635bccc7d3"
														}
													},
													{
														"key": {
															"value": "transaction_date"
														},
														"value": {
															"value": "2026-06-10"
														}
													},
													{
														"key": {
															"value": "transaction_type"
														},
														"value": {
															"value": "DEPOSIT"
														}
													},
													{
														"key": {
															"value": "transaction_status"
														},
														"value": {
															"value": "SUCCESSFUL"
														}
													},
													{
														"key": {
															"value": "amount"
														},
														"value": {
															"value": "50000"
														}
													},
													{
														"key": {
															"value": "currency"
														},
														"value": {
															"value": "XAF"
														}
													}
												],
												[
													{
														"key": {
															"value": "transaction_id"
														},
														"value": {
															"value": "a1f2e3d4-5b6c-7d8e-9f01-234567890abc"
														}
													},
													{
														"key": {
															"value": "transaction_date"
														},
														"value": {
															"value": "2026-06-09"
														}
													},
													{
														"key": {
															"value": "transaction_type"
														},
														"value": {
															"value": "MATURITY"
														}
													},
													{
														"key": {
															"value": "transaction_status"
														},
														"value": {
															"value": "PENDING"
														}
													},
													{
														"key": {
															"value": "amount"
														},
														"value": {
															"value": "125"
														}
													},
													{
														"key": {
															"value": "currency"
														},
														"value": {
															"value": "XAF"
														}
													}
												]
											]
										}
									},
									"list_clocked_customers": {
										"summary": "Every customer registered against a test clock, with the number to drive them by. Nothing stores the roster — a clocked customer is one whose profile carries a clock id, so the list is derived rather than kept in step.",
										"value": {
											"arguments": [
												{
													"key": {
														"value": "msisdn"
													},
													"value": {
														"value": "242066866445"
													}
												},
												{
													"key": {
														"value": "clock_id"
													},
													"value": {
														"value": "clk_9b1c7f42-6a3d-4e58-8f21-0c4de7a91b36"
													}
												},
												{
													"key": {
														"value": "now"
													},
													"value": {
														"value": "2026-02-15"
													}
												}
											]
										}
									}
								}
							}
						}
					},
					"400": {
						"description": "Malformed envelope, invalid JSON, or unknown operation.",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/ErrorResponse"
								}
							}
						}
					}
				}
			}
		}
	},
	"components": {
		"schemas": {
			"ArgPair": {
				"type": "object",
				"properties": {
					"key": {
						"type": "object",
						"properties": {
							"value": {
								"type": "string"
							}
						},
						"required": [
							"value"
						]
					},
					"value": {
						"type": "object",
						"properties": {
							"value": {
								"type": "string"
							}
						},
						"required": [
							"value"
						]
					}
				},
				"required": [
					"key",
					"value"
				]
			},
			"DjsEnvelope": {
				"type": "object",
				"description": "The DJS envelope: a flat list of key/value pairs plus session metadata. `operation_name` selects the operation.",
				"properties": {
					"arguments": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ArgPair"
						}
					},
					"languageCode": {
						"type": "string",
						"default": "fr"
					},
					"sessionIdentifier": {
						"type": "string"
					},
					"journeyIdentifier": {
						"type": "string"
					}
				},
				"required": [
					"arguments",
					"sessionIdentifier",
					"journeyIdentifier"
				]
			},
			"ArgumentsResponse": {
				"type": "object",
				"properties": {
					"arguments": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ArgPair"
						}
					}
				},
				"required": [
					"arguments"
				]
			},
			"ArgumentsListResponse": {
				"type": "object",
				"properties": {
					"argumentsList": {
						"type": "array",
						"items": {
							"type": "array",
							"items": {
								"$ref": "#/components/schemas/ArgPair"
							}
						}
					}
				},
				"required": [
					"argumentsList"
				]
			},
			"ErrorResponse": {
				"type": "object",
				"properties": {
					"error": {
						"type": "object",
						"properties": {
							"code": {
								"type": "string"
							},
							"message": {
								"type": "string"
							}
						},
						"required": [
							"code",
							"message"
						]
					}
				},
				"required": [
					"error"
				]
			},
			"CheckRegistrationStatusOutput": {
				"type": "object",
				"properties": {
					"registration_status": {
						"type": "string",
						"description": "Whether the MSISDN already has an account.",
						"enum": [
							"REGISTERED",
							"NOT_REGISTERED"
						]
					},
					"account_id": {
						"type": "string",
						"description": "Opaque account reference; present when REGISTERED."
					},
					"user_email": {
						"type": "string",
						"description": "Email on file; present when REGISTERED and one was supplied."
					}
				},
				"required": [
					"registration_status"
				],
				"additionalProperties": false
			},
			"RegisterSavingsAccountOutput": {
				"type": "object",
				"properties": {
					"registration_result": {
						"type": "string",
						"description": "Outcome of the open-account attempt.",
						"enum": [
							"REGISTERED",
							"ALREADY_REGISTERED",
							"FAILED"
						]
					},
					"account_id": {
						"type": "string",
						"description": "Opaque account reference; present on REGISTERED/ALREADY_REGISTERED."
					},
					"failure_reason": {
						"type": "string",
						"description": "Generic reason; present only on FAILED (identity not yet resolvable, or resolver unavailable — try again)."
					}
				},
				"required": [
					"registration_result"
				],
				"additionalProperties": false
			},
			"CreateDepositSummaryOutput": {
				"type": "object",
				"properties": {
					"summary_status": {
						"type": "string",
						"description": "OK | AMOUNT_TOO_LOW."
					},
					"deposit_basis": {
						"type": "string",
						"description": "NEW_SAVINGS (this money starts a savings, running a full term from today) | TOP_UP (it joins the savings the customer already holds in this product, stopping at that savings' own maturity_date)."
					},
					"amount": {
						"type": "string",
						"description": "Deposit amount (XAF).",
						"pattern": "^-?\\d+$"
					},
					"interest_rate": {
						"type": "string",
						"description": "Annual rate, bare percent (e.g. 5)."
					},
					"estimated_earnings": {
						"type": "string",
						"description": "Projected interest at maturity (XAF).",
						"pattern": "^-?\\d+$"
					},
					"total_at_maturity": {
						"type": "string",
						"description": "Principal + projected interest (XAF).",
						"pattern": "^-?\\d+$"
					},
					"duration_days": {
						"type": "string",
						"description": "How long this money is locked: today to maturity_date. The product term on NEW_SAVINGS, and less than it on a TOP_UP."
					},
					"maturity_date": {
						"type": "string",
						"description": "The day the customer gets this money back (ISO-8601)."
					},
					"locked_account_id": {
						"type": "string",
						"description": "The savings this money joins (TOP_UP only — on NEW_SAVINGS there is not one yet)."
					},
					"savings_balance_now": {
						"type": "string",
						"description": "What that savings holds before this payment (TOP_UP only, whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"savings_balance_after": {
						"type": "string",
						"description": "What it holds once this payment lands (TOP_UP only, whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"savings_earnings_at_maturity": {
						"type": "string",
						"description": "The interest the whole savings earns by maturity_date, this payment included — each payment valued from its own start, so an earlier one carries more completed periods (TOP_UP only, whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"savings_total_at_maturity": {
						"type": "string",
						"description": "savings_balance_after and savings_earnings_at_maturity together — what the customer receives on maturity_date (TOP_UP only, whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"minimum_amount": {
						"type": "string",
						"description": "Product minimum (present when AMOUNT_TOO_LOW).",
						"pattern": "^-?\\d+$"
					},
					"currency": {
						"type": "string",
						"description": "ISO currency (XAF)."
					},
					"product_code": {
						"type": "string",
						"description": "Echoed for confirm."
					},
					"deposit_reference": {
						"type": "string",
						"description": "Minted X-Reference-Id, carried into confirm."
					}
				},
				"required": [
					"summary_status",
					"currency"
				],
				"additionalProperties": false
			},
			"ConfirmDepositOutput": {
				"type": "object",
				"properties": {
					"deposit_status": {
						"type": "string",
						"description": "PENDING_VALIDATION | FAILED."
					},
					"deposit_reference": {
						"type": "string",
						"description": "The deposit reference (PENDING_VALIDATION)."
					},
					"payment_message": {
						"type": "string",
						"description": "Customer-facing PIN prompt (PENDING_VALIDATION)."
					},
					"failure_reason": {
						"type": "string",
						"description": "Why the deposit couldn't start (FAILED)."
					}
				},
				"required": [
					"deposit_status"
				],
				"additionalProperties": false
			},
			"CreateWithdrawalSummaryOutput": {
				"type": "object",
				"properties": {
					"position_status": {
						"type": "string",
						"description": "ACCRUING | DUE (a quote follows) or PAYING_OUT | PAID (a payout is already on its way or made — what is coming and when rather than a new quote). The same vocabulary list_locked_accounts uses so the journey branches on one throughout."
					},
					"amount": {
						"type": "string",
						"description": "Principal being withdrawn (XAF).",
						"pattern": "^-?\\d+$"
					},
					"original_rate": {
						"type": "string",
						"description": "The rate this position locked in at, bare percent (e.g. 5)."
					},
					"withdrawal_basis": {
						"type": "string",
						"description": "EARLY_EXIT (before maturity, discount applies) or AT_MATURITY (on/after maturity, full rate) — which figures below reflect."
					},
					"accrued_interest": {
						"type": "string",
						"description": "Interest earned so far at the full locked rate — not discounted, not what confirming now pays out (XAF).",
						"pattern": "^-?\\d+$"
					},
					"interest_at_maturity": {
						"type": "string",
						"description": "Interest this position would earn if left to run to full term instead — the tradeoff against withdrawing now (XAF).",
						"pattern": "^-?\\d+$"
					},
					"effective_rate": {
						"type": "string",
						"description": "The rate withdrawable_amount was actually computed at — discounted under EARLY_EXIT, equal to original_rate under AT_MATURITY. Bare percent."
					},
					"withdrawable_amount": {
						"type": "string",
						"description": "Interest actually paid if this quote is confirmed now (XAF).",
						"pattern": "^-?\\d+$"
					},
					"forfeited_interest": {
						"type": "string",
						"description": "accrued_interest minus withdrawable_amount — interest given up by not waiting. Zero under AT_MATURITY (XAF).",
						"pattern": "^-?\\d+$"
					},
					"total_payout": {
						"type": "string",
						"description": "What reaches the customer: principal plus interest on a quote, and off the reserved order once a payout is on its way (XAF).",
						"pattern": "^-?\\d+$"
					},
					"currency": {
						"type": "string",
						"description": "ISO currency (XAF)."
					},
					"payout_status": {
						"type": "string",
						"description": "Where a payout that has left the savings has got to: HELD | SENDING | DELAYED | PAID. position_status says where the money sits and has no word for a transfer that was sent and stalled. Branch on this one."
					},
					"payout_message": {
						"type": "string",
						"description": "The same fact in the customer's language — the sentence to show on the handset. payout_status is for branching."
					},
					"settlement_delay": {
						"type": "string",
						"description": "How long until the money arrives — the configured hold on an early exit and what is left of it while a payout is still held. Absent once the hold has passed: when it lands is then the operator's to say and a zero here would read as immediate.",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					},
					"settlement_reference": {
						"type": "string",
						"description": "The reference a statement shows the payout under (PAYING_OUT | PAID only)."
					},
					"withdrawal_reference": {
						"type": "string",
						"description": "The handle for this quote, carried into confirm_withdrawal. Absent on PAYING_OUT | PAID — there is nothing left to confirm."
					}
				},
				"required": [
					"position_status",
					"total_payout",
					"currency"
				],
				"additionalProperties": false
			},
			"ConfirmWithdrawalOutput": {
				"type": "object",
				"properties": {
					"withdrawal_status": {
						"type": "string",
						"description": "PENDING | FAILED — whether this request started a payout. It says nothing about where the money is: where payout_status is present that field is the authority and this one has no terminal value to disagree with it."
					},
					"withdrawal_reference": {
						"type": "string",
						"description": "The withdrawal reference (PENDING)."
					},
					"settlement_reference": {
						"type": "string",
						"description": "The maturity fast path's own PaymentOrder reference — present only for an on-time exit (past the position's maturityDate). Statements display transactions under this reference, not withdrawal_reference, so a client polling for the outcome should store this one."
					},
					"payout_status": {
						"type": "string",
						"description": "Where a payout that has left the savings has got to: HELD | SENDING | DELAYED | PAID. position_status says where the money sits and has no word for a transfer that was sent and stalled. Branch on this one."
					},
					"payout_message": {
						"type": "string",
						"description": "The same fact in the customer's language — the sentence to show on the handset. payout_status is for branching."
					},
					"settlement_delay": {
						"type": "string",
						"description": "Time remaining until disbursement (e.g. 48h) while the payout is still held. Absent once the hold has passed or the order has settled — nothing left that we can promise.",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					},
					"failure_reason": {
						"type": "string",
						"description": "Why the withdrawal couldn't start (FAILED)."
					}
				},
				"required": [
					"withdrawal_status"
				],
				"additionalProperties": false
			},
			"ListLockedAccountsOutput": {
				"type": "object",
				"properties": {
					"locked_account_id": {
						"type": "string",
						"description": "Locked-position reference."
					},
					"position_status": {
						"type": "string",
						"description": "ACCRUING (still earning, can be topped up), DUE (matured, waiting to be paid) or PAYING_OUT (the money is on its way). The same vocabulary get_savings_balance splits its totals on."
					},
					"payout_status": {
						"type": "string",
						"description": "Where a payout that has left the savings has got to: HELD | SENDING | DELAYED | PAID. Present on PAYING_OUT rows only."
					},
					"payout_message": {
						"type": "string",
						"description": "The same fact in the customer's language — the sentence to show on the handset."
					},
					"settlement_reference": {
						"type": "string",
						"description": "The reference a statement shows the payout under. Present on PAYING_OUT rows only."
					},
					"settlement_delay": {
						"type": "string",
						"description": "How long until the money arrives while the payout is still held. Absent once the hold has passed — when it lands is then the operator's to say.",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					},
					"label": {
						"type": "string",
						"description": "Human label shown on the USSD menu (duration + balance, e.g. '3 mois - 50000 FCFA')."
					},
					"maturity_date": {
						"type": "string",
						"description": "ISO-8601 maturity date."
					},
					"current_balance": {
						"type": "string",
						"description": "The money in this savings, or on one paying out the amount on its way (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"locked_rate": {
						"type": "string",
						"description": "Annual rate this position locked in at, bare percent (e.g. 5.5)."
					},
					"accrued_interest": {
						"type": "string",
						"description": "Interest earned so far at the full locked rate — not discounted, not what an early exit would pay out (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"interest_at_maturity": {
						"type": "string",
						"description": "Interest this position will have earned if left to run to its own maturity_date (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"withdrawable_amount": {
						"type": "string",
						"description": "What a withdrawal today would actually pay — see withdrawal_basis for which figure this is (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"withdrawal_basis": {
						"type": "string",
						"description": "EARLY_EXIT (discount applied, before maturity_date) or AT_MATURITY (full rate, on/after maturity_date) — which one withdrawable_amount reflects."
					}
				},
				"required": [
					"locked_account_id",
					"position_status",
					"label",
					"maturity_date",
					"current_balance"
				],
				"additionalProperties": false
			},
			"GetSavingsBalanceOutput": {
				"type": "object",
				"properties": {
					"total_balance": {
						"type": "string",
						"description": "Total locked principal across all open positions, plus pending_withdrawal_amount (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"accruing_balance": {
						"type": "string",
						"description": "Money still earning, in savings that can be topped up (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"due_balance": {
						"type": "string",
						"description": "Money in savings that have matured and are waiting to be paid (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"total_interest": {
						"type": "string",
						"description": "Interest earned so far on the money still held — what accruing_balance and due_balance have made. A savings paying out was priced at reserve and is not counted again (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"pending_withdrawal_amount": {
						"type": "string",
						"description": "Money reserved out of a position for an in-flight withdrawal, not yet disbursed or reversed — still the customer's, just mid-transfer. Zero outside an active hold (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"accruing_count": {
						"type": "string",
						"description": "How many savings are still earning."
					},
					"due_count": {
						"type": "string",
						"description": "How many have matured and are waiting to be paid."
					},
					"paying_out_count": {
						"type": "string",
						"description": "How many have a payout already on its way."
					},
					"position_count": {
						"type": "string",
						"description": "How many open positions make up total_balance — the three state counts added up. list_locked_accounts lists which ones, each with its own rate and interest."
					},
					"currency": {
						"type": "string",
						"description": "ISO currency (FCFA)."
					}
				},
				"required": [
					"total_balance",
					"accruing_balance",
					"due_balance",
					"total_interest",
					"pending_withdrawal_amount",
					"accruing_count",
					"due_count",
					"paying_out_count",
					"position_count",
					"currency"
				],
				"additionalProperties": false
			},
			"ConfirmOptOutOutput": {
				"type": "object",
				"properties": {
					"opt_out_status": {
						"type": "string",
						"description": "COMPLETED | PENDING | FAILED."
					},
					"failure_reason": {
						"type": "string",
						"description": "Why closure couldn't complete (FAILED)."
					},
					"settlement_delay": {
						"type": "string",
						"description": "Worst-case delay before every closed position's funds have landed (COMPLETED only) — the longest remaining hold across the batch. Absent once nothing in the batch is still on a hold.",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					}
				},
				"required": [
					"opt_out_status"
				],
				"additionalProperties": false
			},
			"ListOptOutPositionsOutput": {
				"type": "object",
				"properties": {
					"locked_account_id": {
						"type": "string",
						"description": "Locked-position reference."
					},
					"position_status": {
						"type": "string",
						"description": "ACCRUING (still earning) or DUE (matured, waiting to be paid). A closure lists no other state — anything already paying out was reserved before this call and is paid no second time."
					},
					"label": {
						"type": "string",
						"description": "Human label shown on the USSD menu (duration + balance, e.g. '3 mois - 50000 FCFA')."
					},
					"maturity_date": {
						"type": "string",
						"description": "ISO-8601 maturity date."
					},
					"amount": {
						"type": "string",
						"description": "The principal this position returns (whole XAF) — create_withdrawal_summary's key for the same figure.",
						"pattern": "^-?\\d+$"
					},
					"withdrawal_basis": {
						"type": "string",
						"description": "EARLY_EXIT (discount applied, before maturity_date) or AT_MATURITY (full rate, on/after maturity_date) — which one this position's payout reflects."
					},
					"original_rate": {
						"type": "string",
						"description": "Annual rate this position locked in at, bare percent (e.g. 5.5)."
					},
					"effective_rate": {
						"type": "string",
						"description": "Rate actually applied to this payout — discounted under EARLY_EXIT, same as original_rate under AT_MATURITY."
					},
					"accrued_interest": {
						"type": "string",
						"description": "Interest earned so far at the full locked rate — not discounted (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"interest_at_maturity": {
						"type": "string",
						"description": "What this position would have earned by leaving it alone until maturity_date (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"withdrawable_amount": {
						"type": "string",
						"description": "The interest this closure actually pays on this position — discounted under EARLY_EXIT, the full amount under AT_MATURITY (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"forfeited_interest": {
						"type": "string",
						"description": "Interest given up by closing now instead of at maturity — zero under AT_MATURITY (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"payout": {
						"type": "string",
						"description": "What this position pays out: current_balance plus its own withdrawable interest (whole XAF).",
						"pattern": "^-?\\d+$"
					},
					"currency": {
						"type": "string",
						"description": "ISO currency (XAF)."
					},
					"total_payout": {
						"type": "string",
						"description": "Sum of payout across every open position in this closure — repeated on every row (no separate top-level field on a collection op).",
						"pattern": "^-?\\d+$"
					},
					"settlement_delay": {
						"type": "string",
						"description": "This position's own delay before disbursement — 'immediate' for an already-matured position, the configured early-exit hold otherwise (e.g. 48h).",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					},
					"total_settlement_delay": {
						"type": "string",
						"description": "The longest settlement_delay in this closure — when the customer has all of their money. Repeated on every row, and the same figure confirm_opt_out answers.",
						"pattern": "^(immediate|(\\d+)(h|min|s))$"
					}
				},
				"required": [
					"locked_account_id",
					"position_status",
					"label",
					"maturity_date",
					"amount",
					"withdrawal_basis",
					"original_rate",
					"effective_rate",
					"accrued_interest",
					"interest_at_maturity",
					"withdrawable_amount",
					"forfeited_interest",
					"payout",
					"currency",
					"total_payout",
					"settlement_delay",
					"total_settlement_delay"
				],
				"additionalProperties": false
			},
			"GetUserEmailOutput": {
				"type": "object",
				"properties": {
					"email": {
						"type": "string",
						"description": "Email on file."
					},
					"email_status": {
						"type": "string",
						"description": "ON_FILE or NOT_ON_FILE."
					}
				},
				"required": [
					"email",
					"email_status"
				],
				"additionalProperties": false
			},
			"CreateFullStatementSummaryOutput": {
				"type": "object",
				"properties": {
					"statement_reference": {
						"type": "string",
						"description": "Minted reference, carried into request_full_statement."
					}
				},
				"required": [
					"statement_reference"
				],
				"additionalProperties": false
			},
			"RequestFullStatementOutput": {
				"type": "object",
				"properties": {
					"request_status": {
						"type": "string",
						"description": "REQUESTED | FAILED."
					},
					"failure_reason": {
						"type": "string",
						"description": "Generic reason; present only on FAILED."
					}
				},
				"required": [
					"request_status"
				],
				"additionalProperties": false
			},
			"CheckStatementStatusOutput": {
				"type": "object",
				"properties": {
					"statement_status": {
						"type": "string",
						"description": "SENDING | SENT | FAILED | UNKNOWN. UNKNOWN covers both a reference that does not exist and one belonging to another customer."
					}
				},
				"required": [
					"statement_status"
				],
				"additionalProperties": false
			},
			"ListRecentTransactionsOutput": {
				"type": "object",
				"properties": {
					"transaction_id": {
						"type": "string",
						"description": "The payment provider's own transaction reference where available, else our internal reference. Never blank."
					},
					"transaction_date": {
						"type": "string",
						"description": "ISO-8601 transaction date."
					},
					"transaction_type": {
						"type": "string",
						"description": "DEPOSIT, ACCRUAL, MATURITY, or EARLY_EXIT. A payout reads the same whichever way it ended — transaction_status is what says so."
					},
					"transaction_status": {
						"type": "string",
						"description": "PENDING, SUCCESSFUL, or FAILED — whether the transaction has actually completed. Empty on an accrual, which records interest earned rather than a payment attempted."
					},
					"amount": {
						"type": "string",
						"description": "Transaction amount.",
						"pattern": "^-?\\d+$"
					},
					"currency": {
						"type": "string",
						"description": "ISO currency (FCFA)."
					}
				},
				"required": [
					"transaction_id",
					"transaction_date",
					"transaction_type",
					"transaction_status",
					"amount",
					"currency"
				],
				"additionalProperties": false
			},
			"CreateTestClockOutput": {
				"type": "object",
				"properties": {
					"clock_id": {
						"type": "string",
						"description": "The new clock's id — pass it to register_savings_account to attach a customer."
					},
					"now": {
						"type": "string",
						"description": "The date the clock calls today."
					},
					"created_at": {
						"type": "string",
						"description": "When the clock was created, in real time."
					}
				},
				"required": [
					"clock_id",
					"now",
					"created_at"
				],
				"additionalProperties": false
			},
			"GetTestClockOutput": {
				"type": "object",
				"properties": {
					"clock_id": {
						"type": "string",
						"description": "The clock's id."
					},
					"now": {
						"type": "string",
						"description": "The date the clock calls today."
					},
					"created_at": {
						"type": "string",
						"description": "When the clock was created, in real time."
					},
					"attached_customer_count": {
						"type": "string",
						"description": "How many customers were registered against this clock."
					},
					"advancing_to": {
						"type": "string",
						"description": "The date a walk in flight is heading for. Absent once it arrives."
					}
				},
				"required": [
					"clock_id",
					"now",
					"created_at",
					"attached_customer_count"
				],
				"additionalProperties": false
			},
			"ListClockedCustomersOutput": {
				"type": "object",
				"properties": {
					"msisdn": {
						"type": "string",
						"description": "The number this customer is reached on."
					},
					"clock_id": {
						"type": "string",
						"description": "The clock they are attached to."
					},
					"now": {
						"type": "string",
						"description": "The date that clock calls today."
					},
					"advancing_to": {
						"type": "string",
						"description": "The date a walk in flight is heading for. Absent once it arrives."
					}
				},
				"required": [
					"msisdn",
					"clock_id",
					"now"
				],
				"additionalProperties": false
			},
			"ResetTestCustomerOutput": {
				"type": "object",
				"properties": {
					"reset_status": {
						"type": "string",
						"description": "READY | WORKING."
					},
					"reset_waiting_on": {
						"type": "string",
						"description": "What the reset is waiting for; present only on WORKING. CLOCK_ADVANCING | MONEY_IN_FLIGHT | POSITIONS_CLOSING."
					}
				},
				"required": [
					"reset_status"
				],
				"additionalProperties": false
			},
			"AdvanceTestClockOutput": {
				"type": "object",
				"properties": {
					"clock_id": {
						"type": "string",
						"description": "The clock's id."
					},
					"now": {
						"type": "string",
						"description": "Where the clock stands as the walk starts."
					},
					"advancing_to": {
						"type": "string",
						"description": "The accepted target. Poll get_test_clock until it clears. Absent when the clock is already on the date asked for and idle, which is the same signal get_test_clock gives on arrival."
					}
				},
				"required": [
					"clock_id",
					"now"
				],
				"additionalProperties": false
			}
		}
	}
}
