{
	"info": {
		"name": "Digital Savings — Portal API",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
		"description": "The portal and ops surface for **MoMo Savings+**: the savings product catalogue, the daily liability tally, and the one action an operator can take on a stuck payment order.\n\nEvery answer carries the same envelope — `data` on a success, `error` on a failure, never both — so a caller branches on one field rather than on the status.\n\nImport whichever environment matches your target stage (`develop.postman_environment.json`, `internal.postman_environment.json`) — its `baseUrl` is already that stage's live host. Run **createPortalToken** first and paste the `accessToken` it returns into the environment's `accessToken` variable; every other request sends it.\n\nEvery route but one is behind a Cognito user pool. Ask `POST /auth/token` for a token, sending the email address or phone number you sign in with and your password, then send it as `Authorization: Bearer <accessToken>`. A token lasts an hour. A request without one is answered `401 Unauthorized` by the gateway, before any of this API runs."
	},
	"auth": {
		"type": "bearer",
		"bearer": [
			{
				"key": "token",
				"value": "{{accessToken}}",
				"type": "string"
			}
		]
	},
	"item": [
		{
			"name": "createPortalToken",
			"request": {
				"method": "POST",
				"header": [
					{
						"key": "Content-Type",
						"value": "application/json"
					}
				],
				"body": {
					"mode": "raw",
					"raw": "{\n  \"username\": \"ops@kamoa.io\",\n  \"password\": \"…\"\n}",
					"options": {
						"raw": {
							"language": "json"
						}
					}
				},
				"url": {
					"raw": "{{baseUrl}}/auth/token",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"auth",
						"token"
					]
				},
				"description": "Exchange an email address or phone number and a password for a token."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "getProductInForce",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/products/:code",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"products",
						":code"
					]
				},
				"description": "The version of one product in force, whatever its status."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "listProductVersions",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/products/:code/versions",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"products",
						":code",
						"versions"
					]
				},
				"description": "One page of a product's versions, oldest first."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "createProductVersion",
			"request": {
				"method": "POST",
				"header": [
					{
						"key": "Content-Type",
						"value": "application/json"
					}
				],
				"body": {
					"mode": "raw",
					"raw": "{\n  \"productCode\": \"prod3m\",\n  \"version\": \"v3\",\n  \"currency\": \"XAF\",\n  \"termDays\": 91,\n  \"accrualPeriod\": \"MONTH\",\n  \"accrualPeriods\": 3,\n  \"interestRate\": \"0.055\",\n  \"discountRate\": \"0.10\",\n  \"minAmount\": \"1000\",\n  \"effectiveFrom\": \"2027-01-01T00:00:00.000Z\",\n  \"status\": \"ACTIVE\"\n}",
					"options": {
						"raw": {
							"language": "json"
						}
					}
				},
				"url": {
					"raw": "{{baseUrl}}/products",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"products"
					]
				},
				"description": "Append one product version. Versions are immutable."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "getLiabilityRange",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/liability/days",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"liability",
						"days"
					]
				},
				"description": "What the book owed across a window, one row per day."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "getLiabilityDay",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/liability/days/:asOf",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"liability",
						"days",
						":asOf"
					]
				},
				"description": "What the book owed on one day."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "getProductLiabilitySeries",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/liability/products/:productVersionId",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"liability",
						"products",
						":productVersionId"
					]
				},
				"description": "One product version's liability over a window, oldest first."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		},
		{
			"name": "redriveStuckOrder",
			"request": {
				"method": "POST",
				"header": [],
				"url": {
					"raw": "{{baseUrl}}/orders/:reference/redrive",
					"host": [
						"{{baseUrl}}"
					],
					"path": [
						"orders",
						":reference",
						"redrive"
					]
				},
				"description": "Clear one stuck order's marker so the overdue sweep picks it up again."
			},
			"event": [
				{
					"listen": "test",
					"script": {
						"type": "text/javascript",
						"exec": [
							"pm.test(\"status is 200\", () => pm.response.to.have.status(200));"
						]
					}
				}
			]
		}
	]
}
