{
  "info": {
    "_postman_id": "ef40bb15-221e-4b16-a6e2-c8610e0470f4",
    "name": "The Beauty API",
    "description": "Requests for every endpoint of The Beauty API, plus the errors you should expect to see.\n\nHow to use this file\n1. Import it into Postman (File > Import, or drag it into the sidebar).\n2. Open the collection, go to the Variables tab and paste your key into the CURRENT VALUE column of apiKey. Keys come from https://thebeautyapi.com/dashboard.\n3. Send \"Health\" first. It needs no key. Then work down the folders in order.\n4. Barcode requests only work on barcode-enabled plans. Switch baseUrl to https://api.thebeautyapi.com/barcode for those.\n\nEvery request carries the key in an x-api-key header (set once under the collection Authorization tab). Each request has a small Tests script that checks the status code, so the Test Results tab tells you at a glance whether the call behaved as expected.\n\nReference: https://thebeautyapi.com/docs",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "x-api-key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.thebeautyapi.com",
      "description": "Standard base URL. Barcode-enabled plans use https://api.thebeautyapi.com/barcode"
    },
    {
      "key": "apiKey",
      "value": "",
      "description": "Paste your key in the CURRENT VALUE column. Leave INITIAL VALUE empty so the key is not saved if you export or share this collection."
    },
    {
      "key": "productId",
      "value": "06d9476a-397b-5f61-abc8-640b30a9ae23",
      "description": "The Ordinary Niacinamide 10% + Zinc 1%. Replace with any id from a search result."
    },
    {
      "key": "ingredientId",
      "value": "a1526dea-37d1-5904-9106-be583378339b",
      "description": "Niacinamide. Replace with any id from an ingredient search result."
    },
    {
      "key": "barcode",
      "value": "769915195941",
      "description": "Example UPC. Barcode-enabled plans only."
    }
  ],
  "item": [
    {
      "name": "1. Start here",
      "description": "Send Health first: it needs no key and confirms you can reach the API. Then run Search, copy an id from the results into the productId variable, and open the full record.",
      "item": [
        {
          "name": "Health",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            },
            "description": "Liveness check and dataset build info. No API key required. If this fails, the problem is your network or a typo in baseUrl, not your key.",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"status is ok\", () => pm.expect(pm.response.json().status).to.eql(\"ok\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Search products",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/search?q=ordinary niacinamide&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "ordinary niacinamide",
                  "description": "Brand and/or product name. Matches prefixes, so partial words work."
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "1 to 51. 20 results per page."
                }
              ]
            },
            "description": "Autocomplete-style search by brand and name. Returns light records (id, brand, name, category, image_url). Copy an id into the productId variable to fetch the full record."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"has results\", () => pm.expect(pm.response.json().results.length).to.be.above(0));",
                  "const first = pm.response.json().results[0];",
                  "if (first) pm.collectionVariables.set(\"productId\", first.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "Get a product (full record)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/{{productId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "{{productId}}"
              ]
            },
            "description": "The complete record: free-from flags and the full ingredient list in label order, each ingredient joined to its INCI name, functions, rating, irritancy and comedogenicity. This is the payload most apps build on."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"has ingredients in label order\", () => {",
                  "  const p = pm.response.json();",
                  "  pm.expect(p.ingredients.length).to.be.above(0);",
                  "  pm.expect(p.ingredients[0].position).to.eql(1);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2. Products",
      "item": [
        {
          "name": "Filter products (fragrance-free skincare)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?category=skincare&fragrance_free=true&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "category",
                  "value": "skincare"
                },
                {
                  "key": "fragrance_free",
                  "value": "true"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Discovery endpoint. Combine category, brand, origin, the five free-from booleans (fragrance_free, alcohol_free, paraben_free, sulfate_free, silicone_free) and ingredient filters."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"pagination present\", () => pm.expect(pm.response.json().pagination).to.have.property(\"has_more\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Filter products (contains an ingredient)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?has_ingredient={{ingredientId}}&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "has_ingredient",
                  "value": "{{ingredientId}}"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Products that contain the ingredient in ingredientId. Get ingredient ids from the ingredient search in folder 3."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Filter products (excludes ingredients)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products?category=skincare&excludes_ingredient={{ingredientId}}&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "category",
                  "value": "skincare"
                },
                {
                  "key": "excludes_ingredient",
                  "value": "{{ingredientId}}",
                  "description": "Up to 10 ids, comma-separated."
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Products that do NOT contain the listed ingredients. Useful for avoid lists."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Batch: several products in one call",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ids\": [\n    \"06d9476a-397b-5f61-abc8-640b30a9ae23\"\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/v1/products/batch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "batch"
              ]
            },
            "description": "Fetch up to 50 full product records in one request. Add more ids to the ids array (from search results). Returns an array in the same shape as Get a product."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"returns an array\", () => pm.expect(pm.response.json()).to.be.an(\"array\"));"
                ]
              }
            }
          ]
        },
        {
          "name": "Barcode lookup (barcode-enabled plans only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/barcode/{{barcode}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "barcode",
                "{{barcode}}"
              ]
            },
            "description": "Resolve a UPC/EAN/GTIN to the full product record. Requires a barcode-enabled plan AND baseUrl set to https://api.thebeautyapi.com/barcode. On a standard plan or the standard base URL this returns 403."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 on a barcode plan, 403 otherwise\", () => pm.expect([200, 403, 404]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Ingredients",
      "item": [
        {
          "name": "Search ingredients",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ingredients/search?q=niacinamide&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ingredients",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "niacinamide"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Search by canonical name and synonyms (vitamin B3 finds Niacinamide). Copy an id into the ingredientId variable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "const first = pm.response.json().results[0];",
                  "if (first) pm.collectionVariables.set(\"ingredientId\", first.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "Get an ingredient",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/ingredients/{{ingredientId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "ingredients",
                "{{ingredientId}}"
              ]
            },
            "description": "Full scientific record: functions, rating, irritancy, comedogenicity, CAS and EC numbers, Ph. Eur. and IUPAC names, synonyms."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"has a name\", () => pm.expect(pm.response.json().name).to.be.a(\"string\"));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4. Errors you should expect",
      "description": "These requests are meant to fail (or return nothing). Each Tests script passes when the API answers the way it should, so a green tick here means the API is behaving correctly.",
      "item": [
        {
          "name": "Missing API key (403)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/search?q=retinol",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "retinol"
                }
              ]
            },
            "description": "Same search as folder 1, with authorization switched off for this request. Expect 403 with message \"Invalid or missing API key\". If you see this on a normal request, check the apiKey variable and that you filled in CURRENT VALUE.",
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"403 Forbidden\", () => pm.response.to.have.status(403));"
                ]
              }
            }
          ]
        },
        {
          "name": "Nonsense search string (200, no results)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/search?q=asdfghjkl&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "asdfghjkl"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "A string that matches nothing is not an error. Expect 200 with an empty results array and has_more false. Your app should handle the empty list, not treat it as a failure."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"200 OK\", () => pm.response.to.have.status(200));",
                  "pm.test(\"empty results\", () => pm.expect(pm.response.json().results).to.have.lengthOf(0));"
                ]
              }
            }
          ]
        },
        {
          "name": "Symbols-only search string (400)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/search?q=???",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "???"
                }
              ]
            },
            "description": "Search text with no searchable characters. Expect 400 with message \"q contains no searchable characters\". Strip punctuation before sending user input."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 Bad Request\", () => pm.response.to.have.status(400));"
                ]
              }
            }
          ]
        },
        {
          "name": "Page beyond the maximum (400)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/search?q=serum&page=99",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "serum"
                },
                {
                  "key": "page",
                  "value": "99"
                }
              ]
            },
            "description": "page is limited to 1 to 51. Expect 400. Use has_more to know when to stop paging."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 Bad Request\", () => pm.response.to.have.status(400));"
                ]
              }
            }
          ]
        },
        {
          "name": "Malformed product id (400)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/not-a-real-id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "not-a-real-id"
              ]
            },
            "description": "Ids are UUIDs. Anything else is rejected before lookup. Expect 400."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 Bad Request\", () => pm.response.to.have.status(400));"
                ]
              }
            }
          ]
        },
        {
          "name": "Unknown product id (404)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/products/00000000-0000-4000-8000-000000000000",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "00000000-0000-4000-8000-000000000000"
              ]
            },
            "description": "A well-formed UUID that no product has. Expect 404."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"404 Not Found\", () => pm.response.to.have.status(404));"
                ]
              }
            }
          ]
        },
        {
          "name": "Batch with an empty ids array (400)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ids\": []\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/v1/products/batch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "products",
                "batch"
              ]
            },
            "description": "The batch body needs 1 to 50 ids. Expect 400."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"400 Bad Request\", () => pm.response.to.have.status(400));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
