Developers
laboratory.love API
A public, read-friendly API for AI assistants and other software: search our published phthalate and bisphenol test results, read every sample-level measurement with its provenance, check crowdfunding status, and create checkout links that a person completes themselves. No account or API key is needed.
OpenAPI spec
www.laboratory.love/openapi.jsonBase URL
www.laboratory.love/api/v1MCP endpoint
www.laboratory.love/mcpUsing laboratory.love with AI assistants
The API is designed for AI agents and custom connectors. In an assistant that can build a custom connector from an API description, ask:
Create a custom connector for laboratory.love using its API documentation at https://www.laboratory.love/developers and OpenAPI spec at https://www.laboratory.love/openapi.json.Assistants that support remote MCP servers can add https://www.laboratory.love/mcp (Streamable HTTP, no authentication). Its tools call the same operations as the REST API.
laboratory.love isn’t an official integration of any AI assistant; this is a public API anyone can connect.
Endpoints
All paths are relative to https://www.laboratory.love/api/v1.
GET/results
Search published results by name, brand, source, category, tag, analyte, or detection.
Read · operationId searchResults
GET/results/{id}
Full record: provenance, samples, every measurement, ratings and assumptions.
Read · operationId getResult
GET/analytes
What DEHP, DBP, BPA, BPS… mean and how they feed the ratings.
Read · operationId listAnalytes
GET/crowdfund
Products being crowdfunded for testing, with live funding status.
Read · operationId listCrowdfundingProducts
GET/crowdfund/{id}
One product’s goal, amount funded, remaining, and contribution range.
Read · operationId getCrowdfundingProduct
POST/crowdfund/{id}/checkout
Creates a Stripe-hosted link to contribute toward a product.
Write (sensitive) · operationId createContributionCheckout
GET/testing/tiers
Direct testing tiers (packages), prices, and terms.
Read · operationId listTestingTiers
POST/testing/checkout
Saves a pending testing order and creates a Stripe-hosted payment link.
Write (sensitive) · operationId createTestingCheckout
GET/checkouts/{checkout_id}
Whether a checkout made through the API is awaiting payment, paid, or expired.
Read · operationId getCheckoutStatus
Examples
Has laboratory.love tested Coca-Cola Zero?
curl "https://www.laboratory.love/api/v1/results?q=Coca-Cola%20Zero"{
"data": [{
"id": "pl-72",
"product_name": "Coca-Cola Zero",
"source_type": "plasticlist_derived",
"commissioned_by_laboratory_love": false,
"sample_count": 3,
"latest_date": "2024-07-17",
"latest_date_basis": "sample_collection",
"detected_analytes": [
{ "abbreviation": "BPF", "samples_detected": 1, "max_detected_ng_g": 14 }
],
"below_loq_analytes": ["BBP", "BPA", "BPS", "DBP", "DEHP", "…"],
"api_url": "https://www.laboratory.love/api/v1/results/pl-72",
"match": "exact_name"
}],
"pagination": { "limit": 10, "returned": 1, "total_matches": 1, "next_cursor": null }
}Commissioned results where DEHP was detected
curl "https://www.laboratory.love/api/v1/results?source=commissioned&analyte=DEHP&detected=true"Measurements from GET /results/cf-116 (abridged)
"measurements": [
{ "abbreviation": "DEHP", "status": "detected",
"value_ng_g": 42.82, "limit_of_quantitation_ng_g": null },
{ "abbreviation": "BBP", "status": "below_limit_of_quantitation",
"value_ng_g": null, "limit_of_quantitation_ng_g": null },
{ "abbreviation": "BPA", "status": "not_detected",
"value_ng_g": null, "limit_of_quantitation_ng_g": null }
]Provenance
Every result carries source_type and commissioned_by_laboratory_love. Please say which kind of record you are describing:
- laboratory_love_commissioned (ids starting
cf-): we bought retail samples and paid an independent lab to test them. Lab, method, and purchase and test dates are included. - plasticlist_derived (ids starting
pl-): historical results imported from PlasticList’s published data under CC BY 4.0. We did not run this testing. The record links to the PlasticList source; dates are sample-collection dates.
Search and detail responses come from the same records as the result pages and the CSV/JSON dataset, and carry its dataset_version.
Measurement states
Each measurement has one of four statuses. They are not interchangeable, and none of them means zero.
detected
The lab quantified it. The number is in value_ng_g (ng/g, the same as ppb). qualifier "greater_than" means the value is a lower bound.
below_limit_of_quantitation
Present below what the lab can quantify, or not quantifiable. value_ng_g is null; limit_of_quantitation_ng_g is given when the source reported it.
not_detected
Reported as not detected (PlasticList reported these as 0).
not_tested
The analyte was not part of that sample’s analysis.
Use limitations
- Results describe the specific samples tested, at the time they were tested. They don’t prove anything about every unit, lot, the whole product line, or a brand.
- A low or not-detected value doesn’t make a product “safe”, and a detection doesn’t make it “unsafe”.
- Ratings (Low, Medium, High) compare one serving, averaged across samples, with a tolerable daily intake for a 70 kg adult or a 14 kg child. They are exposure context, not medical advice. Assumptions are in each record and on the methodology page.
- An empty search means no published record matched, not that a product is free of these chemicals.
Checkouts
The two POST endpoints never charge anyone and never take card details. They return a Stripe-hosted checkout_url; the person opens it, reviews the price, and pays there. Success and cancel pages are fixed to laboratory.love.
curl -X POST "https://www.laboratory.love/api/v1/crowdfund/140/checkout" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2a9e-contribution" \
-d '{"amount": 10}'{
"checkout_id": "cs_live_…",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_live_…",
"status": "awaiting_user_payment",
"requires_user_completion": true,
"amount": 10,
"currency": "USD",
"terms": { "seller": "Very Little Gravitas Indeed, LLC (d/b/a Laboratory.love)", "…": "…" }
}- Contributions are whole US dollars, from $5 (or less when less remains) up to the amount still needed.
- Send an
Idempotency-Keyheader when retrying: the same key and body return the same checkout. - Check
GET /checkouts/{checkout_id}and report a payment only when its status ispaid. Links expire after about 24 hours. - Each response includes the seller, refund rules, and a link to the terms.
Limits, errors, and versions
- About 120 read requests per minute and 10 checkout creations per 10 minutes per client. Over the limit you get 429 with Retry-After.
- Search pages hold up to 50 results; follow
next_cursorfor more. Queries are limited to 100 characters. - Errors are JSON:
{"error": {"code": "…", "message": "…"}}. - Result responses may be cached for up to an hour and update when we publish new results. Crowdfunding status is live within about a minute.
- CORS is open, no cookies are used, and every call stands alone. Breaking changes will get a new path (
/api/v2).
Data, privacy, and terms
Read endpoints need nothing about you. A testing checkout stores the product links and notes sent with it as part of the order. Payment and contact details are entered on Stripe’s page, not sent through the API.
Published data is licensed as described on the data page. See the privacy policy and terms of service.
Support
Questions, bugs, or data corrections: use the contact page.