{
    "info": {
        "name": "Quardlyn CRM API v1",
        "_postman_id": "6e8ac7e5-f4f9-46d7-9a39-quardlyn-crm-v1",
        "description": "Ready-to-import collection for the Quardlyn public CRM API v1.",
        "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
    },
    "variable": [
        {
            "key": "base_url",
            "value": "https://example.quardlyn.test"
        },
        {
            "key": "crm_key",
            "value": "crm_key_replace_me"
        },
        {
            "key": "signing_secret",
            "value": "replace_me"
        },
        {
            "key": "sandbox_key",
            "value": "sandbox_key_replace_me"
        },
        {
            "key": "sandbox_secret",
            "value": "sandbox_secret_replace_me"
        },
        {
            "key": "external_ref",
            "value": "CRM-SALE-001"
        },
        {
            "key": "idempotency_key",
            "value": "{{$guid}}"
        },
        {
            "key": "property_uid",
            "value": "BLR_00001"
        },
        {
            "key": "last_etag",
            "value": ""
        },
        {
            "key": "oauth_access_token",
            "value": ""
        }
    ],
    "auth": {
        "type": "noauth"
    },
    "event": [
        {
            "listen": "prerequest",
            "script": {
                "type": "text/javascript",
                "exec": [
                    "const oauthToken = pm.collectionVariables.get('oauth_access_token');",
                    "if (oauthToken) { pm.request.headers.upsert({ key: 'Authorization', value: 'Bearer ' + oauthToken }); return; }",
                    "const key = pm.collectionVariables.get('crm_key');",
                    "const secret = pm.collectionVariables.get('signing_secret');",
                    "const pathPreview = '/' + pm.request.url.path.join('/');",
                    "const usingSandbox = pathPreview.indexOf('/api/v1/sandbox/crm/') === 0;",
                    "const activeKey = usingSandbox ? (pm.collectionVariables.get('sandbox_key') || key) : key;",
                    "const activeSecret = usingSandbox ? (pm.collectionVariables.get('sandbox_secret') || secret) : secret;",
                    "if (!activeKey || !activeSecret || activeKey === 'crm_key_replace_me' || activeKey === 'sandbox_key_replace_me') { return; }",
                    "const timestamp = Math.floor(Date.now() / 1000).toString();",
                    "const nonce = pm.variables.replaceIn('{{$guid}}');",
                    "const url = pm.request.url;",
                    "const path = '/' + url.path.join('/');",
                    "let body = '';",
                    "if (pm.request.body && pm.request.body.mode === 'raw') { body = pm.request.body.raw || ''; }",
                    "const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);",
                    "const canonical = [pm.request.method, path, timestamp, nonce, bodyHash].join('\\n');",
                    "const signature = CryptoJS.HmacSHA256(canonical, activeSecret).toString(CryptoJS.enc.Hex);",
                    "pm.request.headers.upsert({ key: 'X-CRM-Key', value: activeKey });",
                    "pm.request.headers.upsert({ key: 'X-CRM-Timestamp', value: timestamp });",
                    "pm.request.headers.upsert({ key: 'X-CRM-Nonce', value: nonce });",
                    "pm.request.headers.upsert({ key: 'X-CRM-Signature', value: signature });",
                    "if (!['GET'].includes(pm.request.method)) { pm.request.headers.upsert({ key: 'Idempotency-Key', value: pm.collectionVariables.get('idempotency_key') || nonce }); }"
                ]
            }
        }
    ],
    "item": [
        {
            "name": "Authentication",
            "description": "Signed request examples. The collection pre-request script signs every request with the active production or sandbox key.",
            "item": [
                {
                    "name": "Authentication - Signed GET",
                    "description": "Uses the collection pre-request script to add X-CRM-Key, X-CRM-Timestamp, X-CRM-Nonce and X-CRM-Signature. Metadata endpoints are public, but this request confirms signing headers are generated.",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties?limit=1"
                    }
                }
            ]
        },
        {
            "name": "Metadata",
            "item": [
                {
                    "name": "Health",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/health"
                    }
                },
                {
                    "name": "Capabilities",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/capabilities"
                    }
                },
                {
                    "name": "Version",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/version"
                    }
                }
            ]
        },
        {
            "name": "Production GET",
            "description": "Production read endpoints for authenticated CRM connections. GET signatures use the request path without the query string and an empty body hash.",
            "item": [
                {
                    "name": "Get Collection",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties"
                    }
                },
                {
                    "name": "Get Collection - Pagination",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties?limit=25&offset=0"
                    }
                },
                {
                    "name": "Get Collection - Filtering",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties?marketing_status=available&department=sale"
                    }
                },
                {
                    "name": "Get Collection - Field Selection",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties?fields=property_uid,external_ref,title,price,status"
                    }
                },
                {
                    "name": "Get Collection - expand=full",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties?expand=full&limit=10"
                    }
                },
                {
                    "name": "Get Property",
                    "event": [
                        {
                            "listen": "test",
                            "script": {
                                "type": "text/javascript",
                                "exec": [
                                    "const etag = pm.response.headers.get('ETag');",
                                    "if (etag) { pm.collectionVariables.set('last_etag', etag); }"
                                ]
                            }
                        }
                    ],
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Get Property - If-None-Match",
                    "request": {
                        "method": "GET",
                        "header": [
                            {
                                "key": "If-None-Match",
                                "value": "{{last_etag}}"
                            }
                        ],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Lookup by External Ref",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/lookup?id_type=external_ref&value={{external_ref}}"
                    }
                },
                {
                    "name": "Lookup by Property UID",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/lookup?id_type=property_uid&value={{property_uid}}"
                    }
                }
            ]
        },
        {
            "name": "Properties",
            "item": [
                {
                    "name": "Create Property",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"postman-create-001\",\n  \"property\": {\n    \"external_ref\": \"CRM-SALE-001\",\n    \"department\": \"sale\",\n    \"marketing_status\": \"available\",\n    \"published\": true,\n    \"display_address\": \"10 High Street, London\",\n    \"postcode\": \"SW1A 1AA\",\n    \"price\": 525000,\n    \"beds\": 3,\n    \"baths\": 2,\n    \"property_type\": \"Residential Property\",\n    \"virtual_tour_url\": \"https://my.matterport.com/show/?m=abc123\",\n    \"virtual_tour_provider\": \"Matterport\",\n    \"virtual_tour_enabled\": true\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/crm/properties"
                    }
                },
                {
                    "name": "Update Property",
                    "request": {
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"postman-update-001\",\n  \"property\": {\n    \"external_ref\": \"{{external_ref}}\",\n    \"department\": \"sale\",\n    \"marketing_status\": \"available\",\n    \"display_address\": \"10 High Street, London\",\n    \"price\": 535000\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Patch Property",
                    "request": {
                        "method": "PATCH",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"postman-patch-001\",\n  \"property\": {\n    \"price\": 540000,\n    \"price_qualifier\": \"offers_over\"\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}"
                    }
                }
            ]
        },
        {
            "name": "Lifecycle",
            "item": [
                {
                    "name": "Withdraw Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}/withdraw"
                    }
                },
                {
                    "name": "Archive Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}/archive"
                    }
                },
                {
                    "name": "Restore Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}/restore"
                    }
                },
                {
                    "name": "Reactivate Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}/reactivate"
                    }
                },
                {
                    "name": "Delete Property",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "url": "{{base_url}}/api/v1/crm/properties/{{external_ref}}"
                    }
                }
            ]
        },
        {
            "name": "Sandbox Certification",
            "description": "Uses sandbox_key and sandbox_secret. These requests exercise the same canonical property schema without touching production data.",
            "item": [
                {
                    "name": "Sandbox Create Property",
                    "request": {
                        "method": "POST",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"sandbox-postman-create-001\",\n  \"property\": {\n    \"external_ref\": \"{{external_ref}}\",\n    \"department\": \"sale\",\n    \"category\": \"residential\",\n    \"property_type\": \"house\",\n    \"property_sub_type\": \"terraced\",\n    \"marketing_status\": \"available\",\n    \"status\": \"For Sale\",\n    \"published\": true,\n    \"title\": \"Sandbox three-bedroom house\",\n    \"summary\": \"A realistic sandbox listing for CRM certification.\",\n    \"description\": \"Generated from the public canonical schema and safe for sandbox testing.\",\n    \"display_address\": \"10 High Street, London\",\n    \"address_1\": \"10 High Street\",\n    \"town\": \"London\",\n    \"county\": \"Greater London\",\n    \"postcode\": \"SW1A 1AA\",\n    \"country\": \"United Kingdom\",\n    \"latitude\": 51.501,\n    \"longitude\": -0.141,\n    \"price\": 525000,\n    \"sale_price\": 525000,\n    \"currency\": \"GBP\",\n    \"price_qualifier\": \"offers_over\",\n    \"bedrooms\": 3,\n    \"bathrooms\": 2,\n    \"receptions\": 1,\n    \"tenure\": \"Freehold\",\n    \"features\": [\"Garden\", \"Garage\", \"Close to station\", \"Period features\", \"Modern kitchen\", \"Two bathrooms\", \"South-facing garden\", \"Excellent schools\", \"Chain free\", \"EPC C\"],\n    \"media_image_00\": \"https://example.com/images/property-00.jpg\",\n    \"media_floorplan_00\": \"https://example.com/floorplans/property-00.pdf\",\n    \"epc_pdf\": \"https://example.com/epc/property-00.pdf\",\n    \"brochure_pdf\": \"https://example.com/brochures/property-00.pdf\"\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties"
                    }
                },
                {
                    "name": "Sandbox Update Property",
                    "request": {
                        "method": "PUT",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"sandbox-postman-update-001\",\n  \"property\": {\n    \"external_ref\": \"{{external_ref}}\",\n    \"department\": \"sale\",\n    \"display_address\": \"10 High Street, London\",\n    \"price\": 535000,\n    \"marketing_status\": \"available\"\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Sandbox Patch Property",
                    "request": {
                        "method": "PATCH",
                        "header": [
                            {
                                "key": "Content-Type",
                                "value": "application/json"
                            }
                        ],
                        "body": {
                            "mode": "raw",
                            "raw": "{\n  \"request_id\": \"sandbox-postman-patch-001\",\n  \"property\": {\n    \"price\": 540000,\n    \"price_qualifier\": \"guide_price\"\n  }\n}"
                        },
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Sandbox Withdraw Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}/withdraw"
                    }
                },
                {
                    "name": "Sandbox Restore Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}/restore"
                    }
                },
                {
                    "name": "Sandbox Sync",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/sync?limit=25"
                    }
                },
                {
                    "name": "Sandbox Webhook Simulations",
                    "request": {
                        "method": "GET",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/webhooks"
                    }
                },
                {
                    "name": "Sandbox Delete Property",
                    "request": {
                        "method": "DELETE",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}"
                    }
                },
                {
                    "name": "Sandbox Archive Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}/archive"
                    }
                },
                {
                    "name": "Sandbox Reactivate Property",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/properties/{{external_ref}}/reactivate"
                    }
                },
                {
                    "name": "Sandbox Replay Webhook Simulation",
                    "request": {
                        "method": "POST",
                        "header": [],
                        "url": "{{base_url}}/api/v1/sandbox/crm/webhooks/1/replay"
                    }
                }
            ]
        }
    ]
}
