Introduction
Certification Bodies API
External API for Approved Certification Bodies to manage product certification.
ID Format
ZDHC Sandbox uses a structured ID format: {prefix}-XXXXXXX-Z
| Prefix | Entity Type | Example |
|---|---|---|
01 |
Organisation | 01-XXXXXXX-Z |
20 |
Product | 20-XXXXXXX-Z |
40 |
InCheck Report | 40-XXXXXXX-Z |
Error Model
All non-2xx responses use a consistent error structure:
{
"error": {
"code": "string",
"message": "string",
"details": {}
}
}
Authenticating requests
To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
Retrieve a token by calling the /auth/login endpoint.
Certification Bodies/V1
CTZ Standard
List CTZ standards
requires authentication
Returns a paginated list of CTZ standards for this certification body.
Example request:
curl --request GET \
--get "https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/ctz-standard?per_page=1&page=1&filter%5Bname%5D=vero&filter%5Bversion%5D=1&filter%5Bctz_level_id%5D=11&filter%5Bctz_level_name%5D=est&filter%5Bstatus%5D=FAILED&filter%5Bstart_date%5D=%3C+2026-05-11+10%3A15%3A45&filter%5Bend_date%5D=%3C+2026-04-30+10%3A15%3A45&filter%5Bcreated_at%5D=%3E%3D+2026-05-03+10%3A15%3A45&filter%5Bupdated_at%5D=%3C+2026-05-15+10%3A15%3A45&search=qui&sort=name&column=name" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/ctz-standard"
);
const params = {
"per_page": "1",
"page": "1",
"filter[name]": "vero",
"filter[version]": "1",
"filter[ctz_level_id]": "11",
"filter[ctz_level_name]": "est",
"filter[status]": "FAILED",
"filter[start_date]": "< 2026-05-11 10:15:45",
"filter[end_date]": "< 2026-04-30 10:15:45",
"filter[created_at]": ">= 2026-05-03 10:15:45",
"filter[updated_at]": "< 2026-05-15 10:15:45",
"search": "qui",
"sort": "name",
"column": "name",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 47,
"name": "Foundational",
"version": "3.1",
"start_date": "2026-01-01T00:00:00.000000Z",
"end_date": null,
"status": "PASSED",
"expired_at": null,
"ctz_level_id": 1,
"ctz_level_name": "Foundational"
},
{
"id": 47,
"name": "Foundational",
"version": "3.1",
"start_date": "2026-01-01T00:00:00.000000Z",
"end_date": null,
"status": "PASSED",
"expired_at": null,
"ctz_level_id": 1,
"ctz_level_name": "Foundational"
}
],
"links": {
"first": "/?page=1",
"last": "/?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "/?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
"path": "/",
"per_page": 100,
"to": 2,
"total": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
MRSL Standard
List MRSL standards
requires authentication
Returns a paginated list of MRSL standards for this certification body.
Example request:
curl --request GET \
--get "https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/mrsl-standard?per_page=11&page=1&filter%5Bname%5D=dolores&filter%5Bversion%5D=8&filter%5Bconformance_level_id%5D=6&filter%5Bconformance_level_name%5D=ratione&filter%5Bstatus%5D=FAILED&filter%5Bstart_date%5D=%3E%3D+2026-05-17+10%3A15%3A45&filter%5Bend_date%5D=%3C%3D+2026-04-29+10%3A15%3A45&filter%5Bcreated_at%5D=%3D+2026-05-19+10%3A15%3A45&filter%5Bupdated_at%5D=%3C+2026-05-04+10%3A15%3A45&search=error&sort=name&column=name" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/mrsl-standard"
);
const params = {
"per_page": "11",
"page": "1",
"filter[name]": "dolores",
"filter[version]": "8",
"filter[conformance_level_id]": "6",
"filter[conformance_level_name]": "ratione",
"filter[status]": "FAILED",
"filter[start_date]": ">= 2026-05-17 10:15:45",
"filter[end_date]": "<= 2026-04-29 10:15:45",
"filter[created_at]": "= 2026-05-19 10:15:45",
"filter[updated_at]": "< 2026-05-04 10:15:45",
"search": "error",
"sort": "name",
"column": "name",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": 9,
"reference_id": "30-7V7GKLDBKXQA-I",
"name": "Level 1",
"version": "3.1",
"start_date": "2025-12-01T00:00:00.000000Z",
"end_date": "2027-12-31T00:00:00.000000Z",
"status": "PASSED",
"expired_at": null,
"conformance_level_id": 1,
"conformance_level_name": "Level 1"
},
{
"id": 9,
"reference_id": "30-7V7GKLDBKXQA-I",
"name": "Level 1",
"version": "3.1",
"start_date": "2025-12-01T00:00:00.000000Z",
"end_date": "2027-12-31T00:00:00.000000Z",
"status": "PASSED",
"expired_at": null,
"conformance_level_id": 1,
"conformance_level_name": "Level 1"
}
],
"links": {
"first": "/?page=1",
"last": "/?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "/?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
"path": "/",
"per_page": 100,
"to": 2,
"total": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Product Assignment
List product assignments
requires authentication
Returns a paginated list of product assignments for this certification body.
Example request:
curl --request GET \
--get "https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment?per_page=6&page=1&filter%5Bproduct_id%5D=1&filter%5Bproduct_reference_id%5D=sapiente&filter%5Bproduct_name%5D=soluta&filter%5Bproduct_alternate_names%5D=voluptas&filter%5Bassignment_status%5D=UNDER_REVIEW&filter%5Bformulator_id%5D=10&filter%5Bformulator_reference_id%5D=omnis&filter%5Bformulator_name%5D=perspiciatis&filter%5Bmrsl_level_id%5D=2&filter%5Bctz_level_id%5D=1&filter%5Bassigned_at%5D=%3C+2026-05-19+10%3A15%3A45&filter%5Bupdated_at%5D=%3C+2026-05-07+10%3A15%3A45&filter%5Bproduct_use_types_id%5D=5&filter%5Bproduct_categories_id%5D=7&search=impedit&sort=id&column=id" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment"
);
const params = {
"per_page": "6",
"page": "1",
"filter[product_id]": "1",
"filter[product_reference_id]": "sapiente",
"filter[product_name]": "soluta",
"filter[product_alternate_names]": "voluptas",
"filter[assignment_status]": "UNDER_REVIEW",
"filter[formulator_id]": "10",
"filter[formulator_reference_id]": "omnis",
"filter[formulator_name]": "perspiciatis",
"filter[mrsl_level_id]": "2",
"filter[ctz_level_id]": "1",
"filter[assigned_at]": "< 2026-05-19 10:15:45",
"filter[updated_at]": "< 2026-05-07 10:15:45",
"filter[product_use_types_id]": "5",
"filter[product_categories_id]": "7",
"search": "impedit",
"sort": "id",
"column": "id",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": [
{
"id": null,
"product_id": 1,
"product_reference_id": "20-NTNRYDE6A32E-I",
"product_name": "sed",
"product_description": null,
"product_alternate_names": null,
"formulator_id": 5,
"formulator_reference_id": "01-9YKA6G6TQSUTU-T",
"formulator_name": "test-Chemical formulator System",
"assignment_status": null,
"mrsl_level_id": null,
"mrsl_level_name": null,
"ctz_level_id": null,
"ctz_level_name": null,
"assigned_at": null,
"updated_at": null
},
{
"id": null,
"product_id": 1,
"product_reference_id": "20-NTNRYDE6A32E-I",
"product_name": "sed",
"product_description": null,
"product_alternate_names": null,
"formulator_id": 5,
"formulator_reference_id": "01-9YKA6G6TQSUTU-T",
"formulator_name": "test-Chemical formulator System",
"assignment_status": null,
"mrsl_level_id": null,
"mrsl_level_name": null,
"ctz_level_id": null,
"ctz_level_name": null,
"assigned_at": null,
"updated_at": null
}
],
"links": {
"first": "/?page=1",
"last": "/?page=1",
"prev": null,
"next": null
},
"meta": {
"current_page": 1,
"from": 1,
"last_page": 1,
"links": [
{
"url": null,
"label": "« Previous",
"active": false
},
{
"url": "/?page=1",
"label": "1",
"active": true
},
{
"url": null,
"label": "Next »",
"active": false
}
],
"path": "/",
"per_page": 100,
"to": 2,
"total": 2
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Update a product assignment's certifications
requires authentication
The certifications array in the request body is the complete desired state for this assignment.
After a successful call, the assignment holds exactly those certifications (MRSL rows, and CTZ rows
where applicable). Anything already stored on the assignment that is not included in the request
is removed or deactivated server-side—omitting an existing certification therefore deletes it
from the assignment's active set.
Example request:
curl --request PUT \
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/14" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"certifications\": [
{
\"mrsl_standard_id\": 1,
\"ctz_standard_id\": 1,
\"safety_data_sheet_id\": 1
}
]
}"
const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/14"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"certifications": [
{
"mrsl_standard_id": 1,
"ctz_standard_id": 1,
"safety_data_sheet_id": 1
}
]
};
fetch(url, {
method: "PUT",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 14,
"assignment_status": "UNDER_REVIEW",
"product": {
"id": 31,
"reference_id": "20-MAMVURD727AJ-P",
"name": "cupiditate",
"alternate_names": [
"alternative name"
],
"description": null,
"version": 1,
"status": "UNDER_REVIEW",
"formulator": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"safety_data_sheets": [
{
"id": 3,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"file_url": null
},
{
"id": 17,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 33,
"name": "test-ZDHC MRSL Certification Bodies m.breuer",
"reference_id": "01-TXF8SFVYCQAUD-H"
},
"file_url": null
}
]
},
"current_max_mrsl_certification": null,
"current_max_ctz_certification": null,
"mrsl_certifications": [],
"ctz_certifications": [],
"assigned_at": "2025-12-12T09:13:50.000000Z",
"updated_at": "2025-12-12T16:04:31.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk-update product assignments
requires authentication
Each element of certifications includes an assignment_id (product assignment row for this CB).
Rows are grouped by that id: for each assignment, the rows with matching assignment_id form the
complete desired state for that assignment only. After the call, each touched assignment holds
exactly those certifications (same replace semantics as the single update endpoint).
The response data is a summary object (counts and IDs), not a product-assignment API resource collection.
Example request:
curl --request POST \
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/bulk-update" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"certifications\": [
{
\"assignment_id\": 1,
\"mrsl_standard_id\": 1,
\"ctz_standard_id\": 1,
\"safety_data_sheet_id\": 1
}
]
}"
const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/bulk-update"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"certifications": [
{
"assignment_id": 1,
"mrsl_standard_id": 1,
"ctz_standard_id": 1,
"safety_data_sheet_id": 1
}
]
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, All submitted IDs were eligible and are now updated.):
{
"message": "Product assignments updated successfully",
"data": {
"submitted_count": 2,
"updated_count": 2,
"updated_ids": [
101,
102
],
"not_updateable_count": 0,
"errors": []
}
}
Example response (200, Some submitted IDs could not be updated (e.g. declined between validation and persistence); those IDs appear under `errors`. Same response shape as full success.):
{
"message": "Product assignments partially updated",
"data": {
"submitted_count": 2,
"updated_count": 1,
"updated_ids": [
101
],
"not_updateable_count": 1,
"errors": [
{
"assignment_id": 102,
"error": "Product assignment is already declined"
}
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Decline a product assignment
requires authentication
Marks the assignment identified in the URL as declined for this certification body.
Example request:
curl --request POST \
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/1/decline" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"reason\": \"Unable to process at this time.\"
}"
const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/1/decline"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"reason": "Unable to process at this time."
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200):
{
"data": {
"id": 14,
"assignment_status": "UNDER_REVIEW",
"product": {
"id": 31,
"reference_id": "20-MAMVURD727AJ-P",
"name": "cupiditate",
"alternate_names": [
"alternative name"
],
"description": null,
"version": 1,
"status": "UNDER_REVIEW",
"formulator": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"safety_data_sheets": [
{
"id": 3,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"file_url": null
},
{
"id": 17,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 33,
"name": "test-ZDHC MRSL Certification Bodies m.breuer",
"reference_id": "01-TXF8SFVYCQAUD-H"
},
"file_url": null
}
]
},
"current_max_mrsl_certification": null,
"current_max_ctz_certification": null,
"mrsl_certifications": [],
"ctz_certifications": [],
"assigned_at": "2025-12-12T09:13:50.000000Z",
"updated_at": "2025-12-12T16:04:31.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Bulk-decline product assignments
requires authentication
Marks several assignments as declined in one request. Assignments that are already declined are skipped.
The response data is a summary object (counts and IDs), not a product-assignment API resource collection.
Example request:
curl --request POST \
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/bulk-decline" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json" \
--data "{
\"assignment_ids\": [
101
],
\"reason\": \"Unable to process at this time.\"
}"
const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/bulk-decline"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
let body = {
"assignment_ids": [
101
],
"reason": "Unable to process at this time."
};
fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
}).then(response => response.json());Example response (200, All submitted IDs were eligible and are now declined.):
{
"message": "Product assignments declined successfully",
"data": {
"submitted_count": 2,
"declined_count": 2,
"declined_ids": [
101,
102
],
"already_declined_count": 0,
"already_declined_ids": []
}
}
Example response (200, Some IDs were already declined; they are listed under `already_declined_ids` (same response shape).):
{
"message": "Product assignments partially declined",
"data": {
"submitted_count": 2,
"declined_count": 1,
"declined_ids": [
101
],
"already_declined_count": 1,
"already_declined_ids": [
102
]
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Get a product assignment
requires authentication
Returns one assignment, including related certification and product data.
Example request:
curl --request GET \
--get "https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/14" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/14"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"data": {
"id": 14,
"assignment_status": "UNDER_REVIEW",
"product": {
"id": 31,
"reference_id": "20-MAMVURD727AJ-P",
"name": "cupiditate",
"alternate_names": [
"alternative name"
],
"description": null,
"version": 1,
"status": "UNDER_REVIEW",
"formulator": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"safety_data_sheets": [
{
"id": 3,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 32,
"name": "test-Chemical formulator m.breuer",
"reference_id": "01-R6MYJM3NE47K4-H"
},
"file_url": null
},
{
"id": 17,
"file_name": "test-pdf.pdf",
"version": 1,
"locale": "en",
"uploaded_by": {
"id": 33,
"name": "test-ZDHC MRSL Certification Bodies m.breuer",
"reference_id": "01-TXF8SFVYCQAUD-H"
},
"file_url": null
}
]
},
"current_max_mrsl_certification": null,
"current_max_ctz_certification": null,
"mrsl_certifications": [],
"ctz_certifications": [],
"assigned_at": "2025-12-12T09:13:50.000000Z",
"updated_at": "2025-12-12T16:04:31.000000Z"
}
}
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.
Download a safety data sheet
requires authentication
Returns the SDS file for the chemical product linked to the given assignment.
Example request:
curl --request GET \
--get "https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/sint/safety-data-sheet/1" \
--header "Authorization: Bearer {YOUR_AUTH_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"https://staging-api.vm400.consulting1x1.info/api/certification-bodies/v1/1/product-assignment/sint/safety-data-sheet/1"
);
const headers = {
"Authorization": "Bearer {YOUR_AUTH_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
file
Received response:
Request failed with error:
Tip: Check that you're properly connected to the network.
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
You can check the Dev Tools console for debugging information.