{
	"info": {
		"_postman_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
		"name": "XCO API - Express Checkout Documentation",
		"description": "Postman collection for the Purse Express Checkout (XCO) API v1.\n\nThis collection covers the full merchant signup integration flow:\n- Check if a customer has an existing XCO account\n- Create a new XCO user with consent\n- Authenticate an existing XCO user via passwordless OTP\n- Link an XCO account to a merchant customer via consent challenge\n- Retrieve user information\n\n**Getting started:**\n1. Fill in the collection variables (`client_id`, `client_secret`, `merchant_id`).\n2. Run the \"Authenticate\" request first to obtain an access token.\n3. Follow the flow that matches your use case (new user or existing user).",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"item": [
		{
			"name": "Authenticate",
			"event": [
				{
					"listen": "test",
					"script": {
						"exec": [
							"pm.test(\"authenticate\", function (){",
							"    pm.response.to.have.jsonBody(\"access_token\");",
							"    var jsonData = JSON.parse(responseBody);",
							"    pm.collectionVariables.set(\"access_token\", jsonData.access_token);",
							"});"
						],
						"type": "text/javascript"
					}
				}
			],
			"request": {
				"auth": {
					"type": "basic",
					"basic": [
						{
							"key": "password",
							"value": "{{client_secret}}",
							"type": "string"
						},
						{
							"key": "username",
							"value": "{{client_id}}",
							"type": "string"
						}
					]
				},
				"method": "POST",
				"header": [],
				"body": {
					"mode": "urlencoded",
					"urlencoded": [
						{
							"key": "grant_type",
							"value": "client_credentials",
							"type": "text"
						},
						{
							"key": "scope",
							"value": "xco-api",
							"type": "text"
						}
					]
				},
				"url": {
					"raw": "https://api.purse-sandbox.com/oauth2/token",
					"protocol": "https",
					"host": [
						"api",
						"purse-sandbox",
						"com"
					],
					"path": [
						"oauth2",
						"token"
					]
				},
				"description": "Obtain an OAuth2 access token with the `xco-api` scope.\n\nThis token is required for all subsequent XCO API calls. The test script automatically stores the token in the `access_token` collection variable."
			},
			"response": []
		},
		{
			"name": "1. Check User Existence",
			"item": [
				{
					"name": "Check user exists (by email)",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{base_url}}/v1/users/exists?email=john.doe@example.com&caller_id=123e4567-e89b-12d3-a456-426614174000",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"users",
								"exists"
							],
							"query": [
								{
									"key": "email",
									"value": "john.doe@example.com",
									"description": "Email of the user to check existence"
								},
								{
									"key": "phone_number",
									"value": "+331234567890",
									"description": "Phone number of the user to check existence",
									"disabled": true
								},
								{
									"key": "caller_id",
									"value": "123e4567-e89b-12d3-a456-426614174000",
									"description": "Identifier of the caller for granular rate limiting (e.g. browser IP address)"
								}
							]
						},
						"description": "Check if a user already has an XCO account. Search by email or phone number.\n\nBased on the response:\n- `exists: false` → proceed to **2A. Create User with Consent**\n- `exists: true` → proceed to **2B. Passwordless Authentication**\n\n⚠️ Always provide a `caller_id` to enable granular rate limiting."
					},
					"response": [
						{
							"name": "User exists with payment tokens",
							"originalRequest": {
								"method": "GET",
								"header": [],
								"url": {
									"raw": "{{base_url}}/v1/users/exists?email=john.doe@example.com&caller_id=123e4567-e89b-12d3-a456-426614174000",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"users",
										"exists"
									],
									"query": [
										{
											"key": "email",
											"value": "john.doe@example.com"
										},
										{
											"key": "caller_id",
											"value": "123e4567-e89b-12d3-a456-426614174000"
										}
									]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"exists\": true,\n  \"token_count\": 2\n}"
						},
						{
							"name": "User does not exist",
							"originalRequest": {
								"method": "GET",
								"header": [],
								"url": {
									"raw": "{{base_url}}/v1/users/exists?email=new.user@example.com&caller_id=123e4567-e89b-12d3-a456-426614174000",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"users",
										"exists"
									],
									"query": [
										{
											"key": "email",
											"value": "new.user@example.com"
										},
										{
											"key": "caller_id",
											"value": "123e4567-e89b-12d3-a456-426614174000"
										}
									]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"exists\": false,\n  \"token_count\": 0\n}"
						}
					]
				},
				{
					"name": "Check user exists (by phone number)",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{base_url}}/v1/users/exists?phone_number=%2B331234567890&caller_id=123e4567-e89b-12d3-a456-426614174000",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"users",
								"exists"
							],
							"query": [
								{
									"key": "email",
									"value": "john.doe@example.com",
									"description": "Email of the user to check existence",
									"disabled": true
								},
								{
									"key": "phone_number",
									"value": "%2B331234567890",
									"description": "Phone number of the user to check existence (URL-encoded)"
								},
								{
									"key": "caller_id",
									"value": "123e4567-e89b-12d3-a456-426614174000",
									"description": "Identifier of the caller for granular rate limiting (e.g. browser IP address)"
								}
							]
						},
						"description": "Check if a user already has an XCO account by phone number.\n\nNote: The phone number must be URL-encoded (e.g. `+33` becomes `%2B33`)."
					},
					"response": []
				}
			],
			"description": "Step 1 of the Express Checkout integration: determine if the customer already has an XCO account."
		},
		{
			"name": "2A. Create User with Consent",
			"item": [
				{
					"name": "Create user with consent (full profile)",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"pm.test(\"user created\", function (){",
									"    pm.response.to.have.jsonBody(\"customer_reference\");",
									"    var jsonData = JSON.parse(responseBody);",
									"    pm.collectionVariables.set(\"customer_reference\", jsonData.customer_reference);",
									"});"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"customer_reference\": \"customer_12345\",\n  \"user\": {\n    \"email\": \"john.doe@example.com\",\n    \"phone_number\": \"+33612345678\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"gender\": \"male\",\n    \"birthdate\": \"1990-01-15\",\n    \"addresses\": [\n      {\n        \"address_type\": \"billing\",\n        \"street_address\": \"123 Main Street\",\n        \"address_complement\": \"Apt 4B\",\n        \"locality\": \"Paris\",\n        \"postal_code\": \"75001\",\n        \"country\": \"FRANCE\"\n      }\n    ]\n  }\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users"
							]
						},
						"description": "Create a new XCO user account with merchant consent.\n\nUse this when the customer does NOT have an existing XCO account (`exists: false` from Step 1).\n\nThe test script automatically stores `customer_reference` in collection variables."
					},
					"response": [
						{
							"name": "Full user profile created",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "Content-Type",
										"value": "application/json"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{\n  \"customer_reference\": \"customer_12345\",\n  \"user\": {\n    \"email\": \"john.doe@example.com\",\n    \"phone_number\": \"+33612345678\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"gender\": \"male\",\n    \"birthdate\": \"1990-01-15\",\n    \"addresses\": [\n      {\n        \"address_type\": \"billing\",\n        \"street_address\": \"123 Main Street\",\n        \"address_complement\": \"Apt 4B\",\n        \"locality\": \"Paris\",\n        \"postal_code\": \"75001\",\n        \"country\": \"FRANCE\"\n      }\n    ]\n  }\n}",
									"options": {
										"raw": {
											"language": "json"
										}
									}
								},
								"url": {
									"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"merchants",
										"{{merchant_id}}",
										"users"
									]
								}
							},
							"status": "Created",
							"code": 201,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"customer_reference\": \"customer_12345\",\n  \"user\": {\n    \"email\": \"john.doe@example.com\",\n    \"phone_number\": \"+33612345678\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"gender\": \"male\",\n    \"birthdate\": \"1990-01-15\",\n    \"addresses\": [\n      {\n        \"address_type\": \"billing\",\n        \"street_address\": \"123 Main Street\",\n        \"address_complement\": \"Apt 4B\",\n        \"locality\": \"Paris\",\n        \"postal_code\": \"75001\",\n        \"country\": \"FRANCE\"\n      }\n    ]\n  }\n}"
						},
						{
							"name": "Minimal user profile created",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "Content-Type",
										"value": "application/json"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{\n  \"customer_reference\": \"customer_minimal_001\",\n  \"user\": {\n    \"email\": \"minimal.user@example.com\"\n  }\n}",
									"options": {
										"raw": {
											"language": "json"
										}
									}
								},
								"url": {
									"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"merchants",
										"{{merchant_id}}",
										"users"
									]
								}
							},
							"status": "Created",
							"code": 201,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"customer_reference\": \"customer_minimal_001\",\n  \"user\": {\n    \"email\": \"minimal.user@example.com\"\n  }\n}"
						}
					]
				},
				{
					"name": "Create user with consent (minimal)",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"pm.test(\"user created\", function (){",
									"    pm.response.to.have.jsonBody(\"customer_reference\");",
									"    var jsonData = JSON.parse(responseBody);",
									"    pm.collectionVariables.set(\"customer_reference\", jsonData.customer_reference);",
									"});"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"customer_reference\": \"customer_minimal_001\",\n  \"user\": {\n    \"email\": \"minimal.user@example.com\"\n  }\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users"
							]
						},
						"description": "Create a new XCO user with minimal data (email only).\n\nAt minimum, an `email` is required in the `user` object."
					},
					"response": []
				}
			],
			"description": "Step 2A: When the customer does NOT have an existing XCO account, create a new XCO user alongside their merchant account."
		},
		{
			"name": "2B. Passwordless Authentication",
			"item": [
				{
					"name": "2B.1 Start passwordless (by email)",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"email\": \"john.doe@example.com\",\n  \"caller_id\": \"browser-session-abc123\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/passwordless/start",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"passwordless",
								"start"
							]
						},
						"description": "Initiate passwordless authentication by sending an OTP to the customer's email.\n\nA successful request returns `204 No Content`. The customer receives an OTP code via email.\n\n⚠️ Always include a `caller_id` to enable granular rate limiting."
					},
					"response": []
				},
				{
					"name": "2B.1 Start passwordless (by phone)",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"phone_number\": \"+33623456789\",\n  \"caller_id\": \"192.168.1.100\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/passwordless/start",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"passwordless",
								"start"
							]
						},
						"description": "Initiate passwordless authentication by sending an OTP to the customer's phone via SMS.\n\nA successful request returns `204 No Content`. The customer receives an OTP code via SMS.\n\n⚠️ Always include a `caller_id` to enable granular rate limiting."
					},
					"response": []
				},
				{
					"name": "2B.2 Verify passwordless (by email)",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"pm.test(\"passwordless verified\", function (){",
									"    pm.response.to.have.jsonBody(\"consent_challenge_id\");",
									"    var jsonData = JSON.parse(responseBody);",
									"    pm.collectionVariables.set(\"consent_challenge_id\", jsonData.consent_challenge_id);",
									"});"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"email\": \"john.doe@example.com\",\n  \"verification_code\": \"123456\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/passwordless/verify",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"passwordless",
								"verify"
							]
						},
						"description": "Verify the OTP code received by the customer via email.\n\nOn success, the response contains:\n- `consent_challenge_id` — required for the next step (stored automatically by the test script)\n- `user` — the existing XCO customer profile (use to pre-fill your signup form)"
					},
					"response": [
						{
							"name": "Passwordless verified successfully",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "Content-Type",
										"value": "application/json"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{\n  \"email\": \"john.doe@example.com\",\n  \"verification_code\": \"123456\"\n}",
									"options": {
										"raw": {
											"language": "json"
										}
									}
								},
								"url": {
									"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/passwordless/verify",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"merchants",
										"{{merchant_id}}",
										"users",
										"passwordless",
										"verify"
									]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"consent_challenge_id\": \"ae900e3c-91df-4e71-ba49-60985827c258\",\n  \"user\": {\n    \"email\": \"john.doe@example.com\",\n    \"phone_number\": \"+331234567890\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"gender\": \"male\",\n    \"birthdate\": \"1990-01-01\",\n    \"addresses\": [\n      {\n        \"address_type\": \"billing\",\n        \"street_address\": \"123 Main St\",\n        \"address_complement\": \"Apt 4B\",\n        \"locality\": \"Paris\",\n        \"postal_code\": \"75001\",\n        \"country\": \"FRANCE\"\n      }\n    ]\n  }\n}"
						}
					]
				},
				{
					"name": "2B.2 Verify passwordless (by phone)",
					"event": [
						{
							"listen": "test",
							"script": {
								"exec": [
									"pm.test(\"passwordless verified\", function (){",
									"    pm.response.to.have.jsonBody(\"consent_challenge_id\");",
									"    var jsonData = JSON.parse(responseBody);",
									"    pm.collectionVariables.set(\"consent_challenge_id\", jsonData.consent_challenge_id);",
									"});"
								],
								"type": "text/javascript"
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"phone_number\": \"+33623456789\",\n  \"verification_code\": \"123456\",\n  \"caller_id\": \"192.168.1.100\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/passwordless/verify",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"passwordless",
								"verify"
							]
						},
						"description": "Verify the OTP code received by the customer via SMS.\n\nOn success, the response contains:\n- `consent_challenge_id` — required for the next step (stored automatically by the test script)\n- `user` — the existing XCO customer profile (use to pre-fill your signup form)"
					},
					"response": []
				},
				{
					"name": "2B.3 Create consent using challenge",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"consent_challenge_id\": \"{{consent_challenge_id}}\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/{{customer_reference}}/consents",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"{{customer_reference}}",
								"consents"
							]
						},
						"description": "Finalize the link between an existing XCO account and your merchant customer record by submitting the consent challenge.\n\nThe `consent_challenge_id` is obtained from the previous \"Verify passwordless\" step.\nThe `customer_reference` is your own customer identifier.\n\nA successful request returns `204 No Content`."
					},
					"response": []
				}
			],
			"description": "Step 2B: When the customer already has an XCO account, authenticate via passwordless OTP and link accounts via consent challenge.\n\nFlow: Start passwordless → Verify OTP → Create consent."
		},
		{
			"name": "User Management",
			"item": [
				{
					"name": "Get user by customer reference",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/{{customer_reference}}",
							"host": [
								"{{base_url}}"
							],
							"path": [
								"v1",
								"merchants",
								"{{merchant_id}}",
								"users",
								"{{customer_reference}}"
							]
						},
						"description": "Retrieve user information based on the merchant customer reference.\n\nReturns the XCO user profile linked to the specified customer reference."
					},
					"response": [
						{
							"name": "Full user profile",
							"originalRequest": {
								"method": "GET",
								"header": [],
								"url": {
									"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/{{customer_reference}}",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"merchants",
										"{{merchant_id}}",
										"users",
										"{{customer_reference}}"
									]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"email\": \"john.doe@example.com\",\n  \"phone_number\": \"+33612345678\",\n  \"first_name\": \"John\",\n  \"last_name\": \"Doe\",\n  \"gender\": \"male\",\n  \"birthdate\": \"1990-01-15\",\n  \"addresses\": [\n    {\n      \"address_type\": \"billing\",\n      \"street_address\": \"123 Main Street\",\n      \"address_complement\": \"Apt 4B\",\n      \"locality\": \"Paris\",\n      \"postal_code\": \"75001\",\n      \"country\": \"FRANCE\"\n    }\n  ]\n}"
						},
						{
							"name": "Minimal user profile",
							"originalRequest": {
								"method": "GET",
								"header": [],
								"url": {
									"raw": "{{base_url}}/v1/merchants/{{merchant_id}}/users/{{customer_reference}}",
									"host": [
										"{{base_url}}"
									],
									"path": [
										"v1",
										"merchants",
										"{{merchant_id}}",
										"users",
										"{{customer_reference}}"
									]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"email\": \"minimal.user@example.com\"\n}"
						}
					]
				}
			],
			"description": "Retrieve XCO user information for an existing merchant-customer link."
		}
	],
	"auth": {
		"type": "bearer",
		"bearer": [
			{
				"key": "token",
				"value": "{{access_token}}",
				"type": "string"
			}
		]
	},
	"event": [
		{
			"listen": "prerequest",
			"script": {
				"type": "text/javascript",
				"exec": [
					""
				]
			}
		},
		{
			"listen": "test",
			"script": {
				"type": "text/javascript",
				"exec": [
					""
				]
			}
		}
	],
	"variable": [
		{
			"key": "base_url",
			"value": "https://xco-api.purse-sandbox.com",
			"type": "string"
		},
		{
			"key": "client_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "client_secret",
			"value": "",
			"type": "string"
		},
		{
			"key": "access_token",
			"value": "",
			"type": "string"
		},
		{
			"key": "merchant_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "customer_reference",
			"value": "",
			"type": "string"
		},
		{
			"key": "consent_challenge_id",
			"value": "",
			"type": "string"
		}
	]
}

