Employment Equity Plans
An Employment Equity (EE) Plan is a multi-year plan that captures the demographic workforce targets an organisation commits to. A plan is scoped to an account and is split into one milestone per financial year (September 1 – August 31). Each milestone holds two targets: one for all employees and one for employees with disabilities. A target records roughly 100 counters — one per race × gender × occupational level combination.
The API lets you:
- Create a new plan (optionally pre-filled with the current workforce demographics from a scorecard).
- Retrieve plans with their nested milestones and targets.
- Update plan-level metadata.
- Update individual target values on a specific milestone.
- Delete a plan (milestones and targets are cascaded).
Endpoints
| Method | Path | Purpose |
|---|---|---|
GET | /api/public/v1/employment_equity_plans | List all plans on the account |
GET | /api/public/v1/employment_equity_plans/{plan_id} | Retrieve a single plan with nested milestones and targets |
POST | /api/public/v1/employment_equity_plans | Create a new plan (optionally with baseline prefill) |
PATCH | /api/public/v1/employment_equity_plans/{plan_id} | Update plan metadata |
DELETE | /api/public/v1/employment_equity_plans/{plan_id} | Delete a plan and its milestones/targets |
PATCH | /api/public/v1/employment_equity_plans/{plan_id}/employment_equity_milestones/{milestone_id}/employment_equity_targets/{target_id} | Update the demographic counters on a single target |
Include the following headers on every request:
Authorization: HMAC <signature>X-Api-Key: <your account key>Accept: application/json
See Authentication.
Attributes
Plan
| Field | Type | Required | Description |
|---|---|---|---|
name | string | no | Human-readable plan name. If blank, the API derives a description from the plan dates. |
start_date | string | yes | Plan start date (YYYY-MM-DD). Must be on or after 2025-09-01. |
end_date | string | yes on create | Plan end date (YYYY-MM-DD). For post-2025 plans this is automatically clamped to 2030-08-31. |
scorecard_id | string | null | no | Id of an optional scorecard on the same account (e.g. scorecard_Y2EQKp8z1r9jeToPW6deGwo9), matching the identifier exposed by the Scorecards API. When supplied on create, the API pre-fills every target's counters using that scorecard's current workforce demographics. Supplying it on update only rebinds the scorecard reference — it does not re-run the prefill. |
sector | string | null | no | The published industry sector whose sectoral EE targets this plan benchmarks against (e.g. "Construction", "Financial and Insurance Activities"). The plan's sector — when set — is the source of truth for sectoral-target lookups across every scorecard linked to the plan. If left blank, lookups fall back to each individual scorecard's own per-scorecard sector setting. See Sector values below for the accepted strings. |
Milestone (response only)
| Field | Type | Description |
|---|---|---|
id | integer | Milestone ID (used in the target update path). |
year_number | integer | 1-indexed year number of the plan (1 = first year). |
start_date | string | ISO date for the start of the financial year. |
end_date | string | ISO date for the end of the financial year. |
Target
| Field | Type | Description |
|---|---|---|
id | integer | Target ID (used in the target update path). |
target_type | string | One of all_employees, disabled_employees. Immutable. |
number_of_{race}_{gender}_{occupational_level} | integer | A non-negative count per demographic slice. See Counter fields for the complete list. |
System fields (response only)
| Field | Type | Description |
|---|---|---|
id | integer | Plan ID. |
era | string | Either pre_2025 or post_2025. Plans created through this API default to post_2025. |
created_at | datetime | Creation timestamp. |
updated_at | datetime | Last update timestamp. |
Enumerations
target_type
all_employeesdisabled_employees
era
pre_2025post_2025
Sector values
sector accepts any of the following strings (case-sensitive). These are the same industry sectors used by the BEEtoolkit sectoral-target tables:
Accommodation and Food Service ActivitiesAdministrative and Support ActivitiesAgriculture, Forestry & FishingArts, Entertainment and RecreationConstructionEducationElectricity, Gas Steam and Air Conditioning SupplyFinancial and Insurance ActivitiesHuman Health and Social WorkInformation and CommunicationManufacturingMining and QuarryingProfessional Scientific and TechnicalPublic Administration and Defence; Compulsory Social SecurityReal Estate ActivitiesTransport and StorageWater Supply, Sewerage, Waste Management and Remediation ActivitiesWholesale and Retail Trade; Repair of Motor Vehicles and Motorcycles
Any other value returns 422 with an inclusion error. Leaving sector blank/null is allowed — sectoral-target lookups then fall back to each linked scorecard's own per-scorecard sector setting.
Counter fields
Each target exposes one integer counter per number_of_{race}_{gender}_{occupational_level} combination.
- Races:
african,coloured,indian,white,foreign - Genders:
male,female - Occupational levels:
top_managers,senior_managers,middle_managers,junior_managers,semi_skilled_employees,unskilled_employees, plus a handful of temporary / permanent slices
See the target response below for the full rendered field list.
Notes
- All dates use ISO 8601 (
YYYY-MM-DD) in both requests and responses. start_datemust be on or after2025-09-01.- Plans on
post_2025era haveend_dateautomatically clamped to2030-08-31. - Baseline prefill is a create-time convenience. If you need to reload baselines later, delete the plan and re-create it.
- A target's
target_typecannot be changed; attempting to set it is silently ignored. - Counter fields must be non-negative integers. Negative values return
422. - Deleting a plan cascades to its milestones and all their targets.
- In the Retrieve a plan response, milestones are ordered by
year_numberascending and targets are ordered withall_employeesbeforedisabled_employees. The list endpoint omits milestones and targets entirely.
List plans
Retrieve all plans on the account. This endpoint returns plan metadata only — use Retrieve a plan for the full nested milestones and targets.
GET /api/public/v1/employment_equity_plans
Code examples
- cURL
- JavaScript (axios)
curl -X GET "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans" \
-H "Accept: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>"
const url = "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans";
const path = new URL(url).pathname;
const signature = await generateSignature(path, "", apiSecret, 5);
const response = await axios.get(url, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.data);
Response
[
{
"id": 42,
"name": "FY26 EE Plan",
"start_date": "2025-09-01",
"end_date": "2030-08-31",
"era": "post_2025",
"scorecard_id": "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
"sector": "Construction",
"created_at": "2026-04-22T08:30:00.000Z",
"updated_at": "2026-04-22T08:30:00.000Z"
}
]
Retrieve a plan
Retrieve a single plan with its milestones and targets.
GET /api/public/v1/employment_equity_plans/{plan_id}
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
plan_id | integer | yes | The plan identifier. |
Code examples
- cURL
- JavaScript (axios)
curl -X GET "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/{plan_id}" \
-H "Accept: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>"
const planId = 42;
const url = `https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/${planId}`;
const path = new URL(url).pathname;
const signature = await generateSignature(path, "", apiSecret, 5);
const response = await axios.get(url, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.data);
Response
{
"id": 42,
"name": "FY26 EE Plan",
"start_date": "2025-09-01",
"end_date": "2030-08-31",
"era": "post_2025",
"scorecard_id": "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
"sector": "Construction",
"created_at": "2026-04-22T08:30:00.000Z",
"updated_at": "2026-04-22T08:30:00.000Z",
"milestones": [
{
"id": 101,
"year_number": 1,
"start_date": "2025-09-01",
"end_date": "2026-08-31",
"targets": [
{
"id": 201,
"target_type": "all_employees",
"number_of_african_male_top_managers": 0,
"number_of_coloured_male_top_managers": 0,
"number_of_indian_male_top_managers": 0,
"number_of_white_male_top_managers": 0,
"number_of_african_female_top_managers": 0,
"number_of_coloured_female_top_managers": 0,
"number_of_indian_female_top_managers": 0,
"number_of_white_female_top_managers": 0,
"number_of_foreign_male_top_managers": 0,
"number_of_foreign_female_top_managers": 0,
"number_of_african_male_senior_managers": 0,
"number_of_coloured_male_senior_managers": 0
/* …additional counters for middle/junior/semi_skilled/unskilled levels… */
},
{
"id": 202,
"target_type": "disabled_employees",
"number_of_african_male_top_managers": 0
/* …same counter set as above… */
}
]
}
/* …milestones 2 through N… */
]
}
Create a plan
Create a new EE plan with milestones and empty targets. If a scorecard_id from the same account is supplied, every target is pre-filled with that scorecard's current workforce demographics ("baseline prefill").
POST /api/public/v1/employment_equity_plans
Request body
{
"employment_equity_plan": {
"name": "FY26 EE Plan",
"start_date": "2025-09-01",
"end_date": "2030-08-31",
"scorecard_id": "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
"sector": "Construction"
}
}
Omit scorecard_id to create a plan with zero-valued targets that you'll populate later via the target update endpoint. Supply scorecard_id to have each target's counters pre-filled from the current workforce on that scorecard.
Code examples
- cURL
- JavaScript (axios)
curl -X POST "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>" \
-d '{
"employment_equity_plan": {
"name": "FY26 EE Plan",
"start_date": "2025-09-01",
"end_date": "2030-08-31",
"scorecard_id": "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
"sector": "Construction"
}
}'
const url = "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans";
const payload = {
employment_equity_plan: {
name: "FY26 EE Plan",
start_date: "2025-09-01",
end_date: "2030-08-31",
scorecard_id: "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
sector: "Construction",
},
};
const path = new URL(url).pathname;
const signature = await generateSignature(path, payload, apiSecret, 5);
const response = await axios.post(url, payload, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.data);
Response
Returns 201 Created with the full plan (milestones and targets included), using the same shape as Retrieve a plan.
Errors
| Status | Description |
|---|---|
422 | Validation failed — e.g. start_date is before 2025-09-01, or scorecard_id does not resolve to a scorecard visible to the caller. |
{
"errors": [
"Start date cannot be before 01/09/2025"
]
}
A 422 with Scorecard not found is returned when scorecard_id is unknown to the caller. The response does not distinguish between a non-existent id and one belonging to a different account.
Update a plan
Update plan-level metadata. Only name, scorecard_id, and sector are editable — see Editable fields below. Target values are not editable through this endpoint; use the target update endpoint instead.
PATCH /api/public/v1/employment_equity_plans/{plan_id}
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
plan_id | integer | yes | The plan identifier. |
Editable fields
| Field | Editable on update? | Notes |
|---|---|---|
name | yes | Free-text; no validation beyond length. |
scorecard_id | yes | Accepts the scorecard id. Rebinding only updates the reference — it does not re-run the baseline prefill across existing targets. |
sector | yes | Must be one of the sector values or null. Changing sector only affects sectoral-target lookups going forward; it does not retroactively rewrite stored target counters. |
start_date, end_date | no | Ignored on update. Plan dates drive milestone generation and are fixed at create time; if you need different dates, delete the plan and create a new one. |
Request body
{
"employment_equity_plan": {
"name": "Updated plan name",
"scorecard_id": "scorecard_Y2EQKp8z1r9jeToPW6deGwo9",
"sector": "Construction"
}
}
Code examples
- cURL
- JavaScript (axios)
curl -X PATCH "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/{plan_id}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>" \
-d '{
"employment_equity_plan": {
"name": "Updated plan name"
}
}'
const planId = 42;
const url = `https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/${planId}`;
const payload = {
employment_equity_plan: {
name: "Updated plan name",
},
};
const path = new URL(url).pathname;
const signature = await generateSignature(path, payload, apiSecret, 5);
const response = await axios.patch(url, payload, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.data);
Response
Returns 200 OK with the updated plan, including nested milestones and targets.
Errors
| Status | Description |
|---|---|
404 | The plan does not exist on the account. |
422 | Validation failed — e.g. invalid start_date, or scorecard_id does not resolve to a scorecard visible to the caller. |
Delete a plan
Delete a plan and all its milestones and targets.
DELETE /api/public/v1/employment_equity_plans/{plan_id}
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
plan_id | integer | yes | The plan identifier. |
Code examples
- cURL
- JavaScript (axios)
curl -X DELETE "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/{plan_id}" \
-H "Accept: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>"
const planId = 42;
const url = `https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/${planId}`;
const path = new URL(url).pathname;
const signature = await generateSignature(path, "", apiSecret, 5);
const response = await axios.delete(url, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.status); // 204
Response
Returns 204 No Content on success.
Errors
| Status | Description |
|---|---|
404 | The plan does not exist on the account. |
Update a target
Update one or more demographic counters on a single target. target_type cannot be changed; unrecognised fields are ignored.
PATCH /api/public/v1/employment_equity_plans/{plan_id}/employment_equity_milestones/{milestone_id}/employment_equity_targets/{target_id}
Path parameters
| Name | Type | Required | Description |
|---|---|---|---|
plan_id | integer | yes | The plan identifier. |
milestone_id | integer | yes | The milestone identifier (from the nested plan response). |
target_id | integer | yes | The target identifier (from the nested milestone response). |
Request body
Only send the counters you want to update — all other counters are left untouched.
{
"employment_equity_target": {
"number_of_african_male_top_managers": 3,
"number_of_coloured_female_senior_managers": 1
}
}
Code examples
- cURL
- JavaScript (axios)
curl -X PATCH "https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/{plan_id}/employment_equity_milestones/{milestone_id}/employment_equity_targets/{target_id}" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "X-Api-Key: <your account key>" \
-H "Authorization: HMAC <your signature>" \
-d '{
"employment_equity_target": {
"number_of_african_male_top_managers": 3
}
}'
const planId = 42;
const milestoneId = 101;
const targetId = 201;
const url =
`https://www.beetoolkit.co.za/api/public/v1/employment_equity_plans/${planId}` +
`/employment_equity_milestones/${milestoneId}` +
`/employment_equity_targets/${targetId}`;
const payload = {
employment_equity_target: {
number_of_african_male_top_managers: 3,
number_of_coloured_female_senior_managers: 1,
},
};
const path = new URL(url).pathname;
const signature = await generateSignature(path, payload, apiSecret, 5);
const response = await axios.patch(url, payload, {
headers: {
Accept: "application/json",
Authorization: `HMAC ${signature}`,
"X-Api-Key": apiKey,
},
});
console.log(response.data);
Response
Returns 200 OK with the updated target:
{
"id": 201,
"target_type": "all_employees",
"number_of_african_male_top_managers": 3,
"number_of_coloured_female_senior_managers": 1
/* …remaining counters unchanged… */
}
Errors
| Status | Description |
|---|---|
404 | The plan, milestone, or target is not accessible to the account. |
422 | One or more counter values are invalid (e.g. negative). |