{
  "info": {
    "name": "First Mile Labs — Registry API (v1)",
    "description": "Corporate-registry lookup over the metered /v1 surface.\n\n**Set four collection variables before you start**: `host`, `client_id`, `client_secret` and `scope`. The access token is fetched automatically by a collection-level pre-request script and refreshed when it expires — you never handle a bearer token yourself.\n\n**Start with `Coverage catalogue`.** It lists every jurisdiction you can reach, which tiers can actually run there, and any `caveat` — a coverage limitation worth knowing before you spend a call.\n\n**Evaluation tenants**: calls draw down the same per-jurisdiction allowance as the web interface. An identical repeat costs nothing. A spent allowance answers 402 `evaluation_allowance_exhausted`; other jurisdictions are unaffected.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Fetch an access token once and reuse it until it expires. Saves an",
          "// integrator from pasting bearer tokens by hand.",
          "const expiry = pm.collectionVariables.get('token_expires_at');",
          "if (expiry && Date.now() < Number(expiry) - 30000) { return; }",
          "",
          "pm.sendRequest({",
          "  url: pm.collectionVariables.get('host') + '/v1/oauth/token',",
          "  method: 'POST',",
          "  header: { 'Content-Type': 'application/x-www-form-urlencoded' },",
          "  body: { mode: 'urlencoded', urlencoded: [",
          "    { key: 'grant_type', value: 'client_credentials' },",
          "    { key: 'client_id', value: pm.collectionVariables.get('client_id') },",
          "    { key: 'client_secret', value: pm.collectionVariables.get('client_secret') },",
          "    { key: 'scope', value: pm.collectionVariables.get('scope') },",
          "  ] },",
          "}, (err, res) => {",
          "  if (err) { console.error('token request failed', err); return; }",
          "  const body = res.json();",
          "  if (!body.access_token) { console.error('no access_token', body); return; }",
          "  pm.collectionVariables.set('access_token', body.access_token);",
          "  pm.collectionVariables.set('token_expires_at', Date.now() + (body.expires_in || 3600) * 1000);",
          "});"
        ]
      }
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{access_token}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "host",
      "value": "https://www.firstmilelabs.com",
      "description": "API host."
    },
    {
      "key": "client_id",
      "value": "",
      "description": "pk_live_… — from your account manager."
    },
    {
      "key": "client_secret",
      "value": "",
      "description": "sk_live_… — shown once at creation."
    },
    {
      "key": "scope",
      "value": "read:registry read:registry_enhanced read:registry_ubo",
      "description": "Drop the scopes you were not granted."
    },
    {
      "key": "access_token",
      "value": "",
      "description": "Set automatically. Do not edit."
    },
    {
      "key": "token_expires_at",
      "value": "",
      "description": "Set automatically."
    },
    {
      "key": "country",
      "value": "SG",
      "description": "ISO-3166 alpha-2."
    },
    {
      "key": "company",
      "value": "DBS BANK LTD"
    }
  ],
  "item": [
    {
      "name": "0 · Start here",
      "item": [
        {
          "name": "Coverage catalogue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/jurisdictions",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "jurisdictions"
              ]
            },
            "description": "Every jurisdiction you can reach: the register behind it, the pricing band, whether it is live, which tiers can run, and any `caveat`.\n\n`registry` names the **register**, never the provider we source through. A jurisdiction quoting only a `lite` price cannot serve ownership at all."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "1 · LITE — search and company",
      "item": [
        {
          "name": "Search by name",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/search?country={{country}}&q={{company}}",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "search"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "{{country}}",
                  "description": "ISO-3166 alpha-2."
                },
                {
                  "key": "q",
                  "value": "{{company}}",
                  "description": "Legal entity name."
                }
              ]
            },
            "description": "Company search against the national register.\n\nNot every register supports name search; the catalogue tells you which. Where it does not, use `companyNumber` on Company detail instead."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200, or 402 when the evaluation allowance is spent', () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200, 402]);",
                  "});",
                  "if (pm.response.code === 402) {",
                  "  console.log('Allowance exhausted:', pm.response.json().error_description);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Company by number",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/company?country={{country}}&companyNumber=198700123K&companyName=",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "company"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "{{country}}",
                  "description": "ISO-3166 alpha-2."
                },
                {
                  "key": "companyNumber",
                  "value": "198700123K",
                  "description": "Registration number."
                },
                {
                  "key": "companyName",
                  "value": "",
                  "description": "Alternative to companyNumber."
                }
              ]
            },
            "description": "Company detail. Supply `companyNumber` where you have it — it resolves a single company rather than a list of near-identical names."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200, or 402 when the evaluation allowance is spent', () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200, 402]);",
                  "});",
                  "if (pm.response.code === 402) {",
                  "  console.log('Allowance exhausted:', pm.response.json().error_description);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2 · ENHANCED — profile with direct ownership",
      "item": [
        {
          "name": "Enhanced search",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/enhanced/search?country={{country}}&q={{company}}",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "enhanced",
                "search"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "{{country}}",
                  "description": "ISO-3166 alpha-2."
                },
                {
                  "key": "q",
                  "value": "{{company}}",
                  "description": "Legal entity name."
                }
              ]
            },
            "description": "Name search across the wider ENHANCED coverage. Requires `read:registry_enhanced`."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200, or 402 when the evaluation allowance is spent', () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200, 402]);",
                  "});",
                  "if (pm.response.code === 402) {",
                  "  console.log('Allowance exhausted:', pm.response.json().error_description);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "Enhanced company",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/enhanced/company?country={{country}}&companyName={{company}}&companyNumber=",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "enhanced",
                "company"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "{{country}}",
                  "description": "ISO-3166 alpha-2."
                },
                {
                  "key": "companyName",
                  "value": "{{company}}",
                  "description": "Or companyNumber."
                },
                {
                  "key": "companyNumber",
                  "value": "",
                  "description": "Preferred where known."
                }
              ]
            },
            "description": "Full profile: directors and officers, shareholders, capital, and **direct** ownership percentages. For ownership resolved recursively to natural persons, use the UBO tier."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200, or 402 when the evaluation allowance is spent', () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200, 402]);",
                  "});",
                  "if (pm.response.code === 402) {",
                  "  console.log('Allowance exhausted:', pm.response.json().error_description);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3 · UBO — recursive ownership",
      "item": [
        {
          "name": "UBO unwrap",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/ubo/company?country={{country}}&companyName={{company}}&companyNumber=&maxLayers=5&uboThreshold=25&maxCreditCost=",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "ubo",
                "company"
              ],
              "query": [
                {
                  "key": "country",
                  "value": "{{country}}",
                  "description": "ISO-3166 alpha-2."
                },
                {
                  "key": "companyName",
                  "value": "{{company}}",
                  "description": "Or companyNumber."
                },
                {
                  "key": "companyNumber",
                  "value": "",
                  "description": "Preferred where known."
                },
                {
                  "key": "maxLayers",
                  "value": "5",
                  "description": "How far up to walk. 1–10."
                },
                {
                  "key": "uboThreshold",
                  "value": "25",
                  "description": "Percent at which a person counts as a UBO."
                },
                {
                  "key": "maxCreditCost",
                  "value": "",
                  "description": "Optional per-request cost ceiling."
                }
              ]
            },
            "description": "Walks ownership upward and resolves to natural persons.\n\nReturns `ubos` (those meeting the threshold), `naturalPersons`, `corporateNodes` and `unresolvedBranches`. `maxCreditCost` caps the spend for a single request — worth setting on a structure you do not yet know the shape of.\n\nNot every jurisdiction can serve this. Check the catalogue: one quoting only a `lite` price never will."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200, or 402 when the evaluation allowance is spent', () => {",
                  "  pm.expect(pm.response.code).to.be.oneOf([200, 402]);",
                  "});",
                  "if (pm.response.code === 402) {",
                  "  console.log('Allowance exhausted:', pm.response.json().error_description);",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4 · Async — for slow jurisdictions",
      "item": [
        {
          "name": "Submit a job",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/jobs",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "jobs"
              ]
            },
            "description": "Run any tier asynchronously. Use this for Band C jurisdictions, where a synchronous request may outlive your client's timeout — South Africa has been measured at well over a minute.\n\nAnswers 202 with a job id; poll it below.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tier\": \"ubo\",\n  \"country\": \"{{country}}\",\n  \"companyName\": \"{{company}}\",\n  \"maxLayers\": 5,\n  \"uboThreshold\": 25\n}"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('202 Accepted', () => pm.response.to.have.status(202));",
                  "const b = pm.response.json();",
                  "if (b.id) { pm.collectionVariables.set('job_id', b.id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Collect the result",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{host}}/v1/registry/jobs/{{job_id}}",
              "host": [
                "{{host}}"
              ],
              "path": [
                "v1",
                "registry",
                "jobs",
                "{{job_id}}"
              ]
            },
            "description": "Poll until `status` is terminal. The job id is captured automatically by the submit request above."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 OK', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}