{
	"info": {
		"_postman_id": "95186b61-1197-4a6e-a90f-d97223528d90",
		"name": "LetsEncrypt Client (MaksIT.CertsUI)",
		"description": "Aligned with MaksIT.CertsUI controllers: routes under /api (except /.well-known/acme-challenge). Use Identity Login first, then set collection variable accessToken from the response (tests on Login do this). JWT is required for all /api/* routes except identity login, refresh, and logout.",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"auth": {
		"type": "bearer",
		"bearer": [
			{
				"key": "token",
				"value": "{{accessToken}}",
				"type": "string"
			}
		]
	},
	"variable": [
		{ "key": "baseUrl", "value": "http://localhost:8080" },
		{ "key": "accessToken", "value": "" },
		{ "key": "refreshToken", "value": "" },
		{ "key": "sessionId", "value": "" },
		{ "key": "accountId", "value": "" },
		{ "key": "userId", "value": "" },
		{ "key": "challenge", "value": "" },
		{ "key": "isStaging", "value": "true" },
		{ "key": "letsEncryptDirectory", "value": "https://acme-v02.api.letsencrypt.org/directory" }
	],
	"item": [
		{
			"name": "Identity",
			"item": [
				{
					"name": "login",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"if (pm.response.code === 200) {",
									"    const j = pm.response.json();",
									"    const token = j.token ?? j.Token;",
									"    const refresh = j.refreshToken ?? j.RefreshToken;",
									"    if (token) pm.collectionVariables.set('accessToken', token);",
									"    if (refresh) pm.collectionVariables.set('refreshToken', refresh);",
									"}"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"auth": { "type": "noauth" },
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"username\": \"admin\",\n  \"password\": \"password\"\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/identity/login"
					}
				},
				{
					"name": "refresh",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"if (pm.response.code === 200) {",
									"    const j = pm.response.json();",
									"    const token = j.token ?? j.Token;",
									"    if (token) pm.collectionVariables.set('accessToken', token);",
									"}"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"auth": { "type": "noauth" },
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"refreshToken\": \"{{refreshToken}}\"\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/identity/refresh"
					}
				},
				{
					"name": "logout",
					"request": {
						"auth": { "type": "noauth" },
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"token\": \"{{accessToken}}\",\n  \"logoutFromAllDevices\": false\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/identity/logout"
					}
				},
				{
					"name": "patch user (password)",
					"request": {
						"method": "PATCH",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"password\": \"new-password\",\n  \"operations\": {\n    \"password\": \"SetField\"\n  }\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/identity/user/{{userId}}"
					}
				}
			]
		},
		{
			"name": "Accounts",
			"item": [
				{
					"name": "list accounts",
					"request": {
						"method": "GET",
						"header": [{ "key": "Accept", "value": "application/json" }],
						"url": "{{baseUrl}}/api/accounts"
					}
				},
				{
					"name": "get account",
					"request": {
						"method": "GET",
						"header": [{ "key": "Accept", "value": "application/json" }],
						"url": "{{baseUrl}}/api/account/{{accountId}}"
					}
				},
				{
					"name": "create account (full flow)",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"description\": \"Postman test\",\n  \"contacts\": [\"mailto:you@example.com\"],\n  \"challengeType\": \"http-01\",\n  \"hostnames\": [\"staging.example.com\"],\n  \"isStaging\": true,\n  \"agreeToS\": true\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/account"
					}
				},
				{
					"name": "patch account (description)",
					"request": {
						"method": "PATCH",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"description\": \"Updated from Postman\",\n  \"operations\": {\n    \"description\": \"SetField\"\n  }\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/account/{{accountId}}"
					}
				},
				{
					"name": "delete account",
					"request": {
						"method": "DELETE",
						"header": [{ "key": "Accept", "value": "application/json" }],
						"url": "{{baseUrl}}/api/account/{{accountId}}"
					}
				}
			]
		},
		{
			"name": "Cache",
			"item": [
				{
					"name": "download full cache (zip)",
					"request": {
						"method": "GET",
						"url": "{{baseUrl}}/api/cache/download"
					}
				},
				{
					"name": "upload full cache (zip)",
					"request": {
						"method": "POST",
						"body": {
							"mode": "formdata",
							"formdata": [
								{
									"key": "file",
									"type": "file",
									"src": []
								}
							]
						},
						"url": "{{baseUrl}}/api/cache/upload"
					}
				},
				{
					"name": "delete full cache",
					"request": {
						"method": "DELETE",
						"url": "{{baseUrl}}/api/cache"
					}
				},
				{
					"name": "download account cache (zip)",
					"request": {
						"method": "GET",
						"url": "{{baseUrl}}/api/cache/{{accountId}}/download"
					}
				},
				{
					"name": "upload account cache (zip bytes)",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "\"<base64-encoded-zip>\"",
							"options": { "raw": { "language": "json" } }
						},
						"description": "API binds [FromBody] byte[] as a base64 JSON string. Replace with a real base64 zip payload.",
						"url": "{{baseUrl}}/api/cache/{{accountId}}/upload"
					}
				}
			]
		},
		{
			"name": "Certs flow",
			"item": [
				{
					"name": "letsencrypt directory (external)",
					"request": {
						"auth": { "type": "noauth" },
						"method": "GET",
						"header": [],
						"url": "{{letsEncryptDirectory}}",
						"description": "Public ACME directory URL (status: https://letsencrypt.status.io/)"
					}
				},
				{
					"name": "configure-client",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"if (pm.response.code !== 200) return;",
									"let id = null;",
									"try {",
									"    const j = pm.response.json();",
									"    if (typeof j === 'string') id = j.replace(/^\"|\"$/g, '');",
									"    else if (j && j.value != null) id = String(j.value);",
									"    else if (j && j.sessionId) id = String(j.sessionId);",
									"} catch (e) {",
									"    const m = pm.response.text().match(/[0-9a-fA-F]{8}-[0-9a-fA-F-]{27}/);",
									"    if (m) id = m[0];",
									"}",
									"if (id && /^[0-9a-fA-F-]{36}$/.test(id)) {",
									"    pm.collectionVariables.set('sessionId', id);",
									"    console.log('sessionId set to', id);",
									"}"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"isStaging\": {{isStaging}}\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/configure-client"
					}
				},
				{
					"name": "terms-of-service",
					"request": {
						"method": "GET",
						"header": [{ "key": "Accept", "value": "application/json" }],
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/terms-of-service",
						"description": "Returns base64-encoded PDF (JSON string or wrapped per API Result serializer)."
					}
				},
				{
					"name": "init (new account)",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"if (pm.response.code !== 200) return;",
									"try {",
									"    const j = pm.response.json();",
									"    const v = typeof j === 'string' ? j.replace(/^\"|\"$/g, '') : (j.value != null ? String(j.value) : null);",
									"    if (v && /^[0-9a-fA-F-]{36}$/.test(v)) pm.collectionVariables.set('accountId', v);",
									"} catch (e) {}"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"description\": \"Postman session\",\n  \"contacts\": [\"mailto:you@example.com\"]\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/init"
					}
				},
				{
					"name": "init (existing account)",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"description\": \"Postman session\",\n  \"contacts\": [\"mailto:you@example.com\"]\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/init/{{accountId}}"
					}
				},
				{
					"name": "new order",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"if (pm.response.code !== 200) return;",
									"const j = pm.response.json();",
									"const arr = Array.isArray(j) ? j : (j.value && Array.isArray(j.value) ? j.value : null);",
									"if (arr && arr.length > 0) {",
									"    pm.collectionVariables.set('challenge', arr[0]);",
									"    console.log('challenge token file name:', arr[0]);",
									"}"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"hostnames\": [\"staging.example.com\"],\n  \"challengeType\": \"http-01\"\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/order"
					}
				},
				{
					"name": "acme-challenge (local API)",
					"request": {
						"auth": { "type": "noauth" },
						"method": "GET",
						"url": "{{baseUrl}}/.well-known/acme-challenge/{{challenge}}",
						"description": "No JWT. Returns text/plain challenge response."
					}
				},
				{
					"name": "acme-challenge (public host)",
					"request": {
						"auth": { "type": "noauth" },
						"method": "GET",
						"url": "http://staging.example.com/.well-known/acme-challenge/{{challenge}}"
					}
				},
				{
					"name": "complete-challenges",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/complete-challenges"
					}
				},
				{
					"name": "order-status",
					"request": {
						"method": "GET",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"hostnames\": [\"staging.example.com\"]\n}",
							"options": { "raw": { "language": "json" } }
						},
						"description": "Matches CertsFlowController: HttpGet with [FromBody] GetOrderRequest. Some clients/proxies strip GET bodies; if this fails, consider fixing the API to POST.",
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/order-status"
					}
				},
				{
					"name": "download certificates",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"hostnames\": [\"staging.example.com\"]\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/certificates/download"
					}
				},
				{
					"name": "apply certificates",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Accept", "value": "application/json" }
						],
						"description": "Uses accountId (not sessionId). No request body.",
						"url": "{{baseUrl}}/api/certs/{{accountId}}/certificates/apply"
					}
				},
				{
					"name": "revoke certificates",
					"request": {
						"method": "POST",
						"header": [
							{ "key": "Content-Type", "value": "application/json" },
							{ "key": "Accept", "value": "application/json" }
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"hostnames\": [\"staging.example.com\"]\n}",
							"options": { "raw": { "language": "json" } }
						},
						"url": "{{baseUrl}}/api/certs/{{sessionId}}/certificates/revoke"
					}
				}
			]
		},
		{
			"name": "Agent (via Web API)",
			"description": "Proxied by AgentController; uses same JWT as other /api routes.",
			"item": [
				{
					"name": "test (HelloWorld)",
					"request": {
						"method": "GET",
						"header": [{ "key": "Accept", "value": "application/json" }],
						"url": "{{baseUrl}}/api/agent/test"
					}
				}
			]
		}
	]
}
