> For the complete documentation index, see [llms.txt](https://docs.stellic.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stellic.com/stellic-partner-api/audits/add-a-sub-requirement.md).

# Add a sub-requirement

Use this endpoint to add to a draft audit's tree under one parent: courses, a new named requirement, a concentration parent or one of its concentrations, or a link to a published shared requirement. `{parent_req_id}` is a node `id` from this draft's tree; the root's `id` adds at the top level. Changes stay in the draft until you [publish](/stellic-partner-api/audits/publish-a-draft-audit.md) it.

## POST /audits/{audit\_id}/requirements/{parent\_req\_id}/subrequirements

>

```json
{"openapi":"3.0.0","info":{"title":"Stellic for Developers","version":"1.0"},"tags":[{"name":"Audits","description":"Read, draft, edit, and publish an audit's requirement tree. Edits go to a draft; publishing recomputes student progress in the background."}],"servers":[{"url":"https://{institution}.stellic.com/partnerapi/v1","variables":{"institution":{"default":"{institution}","description":"Your institution's Stellic subdomain: the part before `.stellic.com` in the URL you use to sign in. It becomes the host in every request, as in `https://{institution}.stellic.com/partnerapi/v1/...`. Replace the `{institution}` placeholder with your subdomain before sending."}}}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Personal Access Token issued by your Stellic integration partner. Send it as `Authorization: Bearer <token>` on every request. One token per institution; do not share across environments."}},"schemas":{"AddSubrequirementRequest":{"type":"object","required":["type","data"],"properties":{"type":{"type":"string","description":"`course` adds one course node per code in `data.courses`. `new_requirement` adds an empty requirement, a concentration parent, a concentration child, or a default concentration child depending on the keys in `data`. `shared_requirement` links a published shared requirement.","enum":["course","new_requirement","shared_requirement"]},"data":{"type":"object","description":"Type-specific payload. `course`: `courses` (non-empty list of catalog course codes). `new_requirement`: `name`; or `name` + `is_concentration: true` + `concentration_type` (a concentration type `external_id`); or `concentration` (a concentration `external_id`, under a concentration parent); or `default_concentration` (display name, under a concentration parent). `shared_requirement`: `shared_requirement_id` (integer, from a program's `shared_requirement.id`) + `name` (display name in this audit).","properties":{"courses":{"type":"array","items":{"type":"string"}},"name":{"type":"string"},"is_concentration":{"type":"boolean"},"concentration_type":{"type":"string"},"concentration":{"type":"string"},"default_concentration":{"type":"string"},"shared_requirement_id":{"type":"integer"}}}}},"AddSubrequirementResponse":{"type":"object","properties":{"requirements":{"type":"array","description":"The tree of each node added by this call. One entry per course for `type: course`, otherwise one entry.","items":{"$ref":"#/components/schemas/RequirementNode"}},"shared_requirement_program":{"type":"integer","nullable":true,"description":"Internal `id` of the shared requirement program when `type` was `shared_requirement`; otherwise `null`."},"pending_change":{"$ref":"#/components/schemas/PendingChange"}}},"RequirementNode":{"type":"object","description":"One requirement in an audit tree. Sub-requirements nest under `choices`. Course nodes carry only `id`, `screen_name`, `course_parent_id`, and `constraints`.","properties":{"id":{"type":"integer","description":"Internal database ID of the requirement. This is the value for `parent_req_id`, `subrequirement_id`, and `requirement_id` on the editing endpoints."},"screen_name":{"type":"string","description":"Display name. For the root node this is the program name; for a course node it is the course code."},"original_min_units":{"type":"number","nullable":true,"description":"Minimum units as originally configured, or `null`."},"min_units":{"type":"number","nullable":true,"description":"Minimum units currently applied to this requirement, or `null`."},"is_shared":{"type":"boolean","description":"`true` when this node is a shared requirement linked from another program. Its entry in `shared_requirement_mapping` names that program."},"is_uni_req":{"type":"boolean","description":"`true` when the shared requirement is a university-wide requirement."},"is_concentration":{"type":"boolean","description":"`true` for a concentration parent, whose children are the program's concentrations."},"concentration_type":{"type":"string","description":"Display name of the concentration type (not its `external_id`). Present only when `is_concentration` is `true`."},"default_concentration":{"type":"boolean","description":"`true` for the default child of a concentration parent."},"is_text":{"type":"boolean","description":"Present (and `true`) only for text-only requirements."},"is_check":{"type":"boolean","description":"Present (and `true`) only for check-box requirements."},"is_free_elective":{"type":"boolean","description":"Present (and `true`) only for the free-electives requirement."},"course_parent_id":{"type":"string","description":"Course nodes only. A 20-digit composite of the parent's ID and the course requirement's ID. It is not a database ID; use the node's `id` on the editing endpoints."},"choices":{"type":"array","description":"Sub-requirements, in display order. Absent on course nodes.","items":{"$ref":"#/components/schemas/RequirementNode"}},"constraints":{"type":"array","description":"Rules on this requirement, in evaluation order.","items":{"$ref":"#/components/schemas/RequirementConstraint"}},"uid":{"type":"string","description":"Cache token present only on a node returned as a tree root. Ignore it."}}},"RequirementConstraint":{"type":"object","description":"One rule on a requirement.","properties":{"id":{"type":"integer","description":"Internal database ID of the constraint; the `constraint_id` for update and remove calls."},"type":{"type":"string","description":"Constraint type name, for example `anyxof`, `minxunits`, `minxcourses`, `mingrade`, `notcount`, `xfromdepts`, `coderange`."},"type_string":{"type":"string","description":"Human-readable rendering of the rule. Present for most constraint types."},"data":{"type":"object","description":"Type-specific fields, in the same shape you send on the add and update calls. For `minxunits` that is `units` and `is_each`; for `anyxof` it is `x` and `is_and`."},"is_hidden":{"type":"boolean","description":"Whether the constraint is hidden from students in the audit view."}}},"PendingChange":{"type":"object","description":"A draft change queued for replay onto the published version when the draft is published.","properties":{"user":{"$ref":"#/components/schemas/PendingChangeUser"},"text":{"type":"string","description":"Plain-language summary of the change."},"timestamp":{"type":"string","description":"When the change was recorded, as `YYYY-MM-DD HH:MM:SS.ffffff+00:00`."}}},"PendingChangeUser":{"type":"object","description":"The account that made the change, as recorded on the change record.","properties":{"username":{"type":"string"},"display_username":{"type":"string"},"userid":{"type":"string","nullable":true},"title":{"type":"string","nullable":true},"first_name":{"type":"string"},"last_name":{"type":"string"},"photo_url":{"type":"string","nullable":true},"email":{"type":"string"},"sms_alerts_enabled":{"type":"boolean"},"last_log_in":{"type":"string","nullable":true},"is_active":{"type":"boolean"},"is_admin":{"type":"boolean"},"role":{"type":"string","nullable":true},"mobile_number":{"type":"string","nullable":true},"hash":{"type":"string","description":"Opaque account token. Ignore it."}}},"ErrorMessage":{"type":"object","description":"Error envelope used by the programs, audits, and requirements endpoints.","properties":{"error":{"type":"string","description":"Human-readable reason. Match on it if you need to branch; the strings on each endpoint page are verbatim."}}}}},"paths":{"/audits/{audit_id}/requirements/{parent_req_id}/subrequirements":{"post":{"tags":["Audits"],"parameters":[{"name":"audit_id","in":"path","required":true,"description":"Internal numeric ID of the draft audit you are editing (the `draft_audit_id` from `POST /audits/{audit_id}/draft`, or the `new_audit_id` from `POST /programs/{program_short_name}/audits`).","schema":{"type":"integer"}},{"name":"parent_req_id","in":"path","required":true,"description":"Internal numeric ID of the requirement the new sub-requirement goes under: a node `id` from this draft's `requirement_tree`. The root node's `id` adds at the top level. Must be in this draft's tree; anything else returns `No Requirement matches the given query.`","schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSubrequirementRequest"}}}},"responses":{"200":{"description":"Sub-requirement added to the draft. `requirements` holds the tree of each node that was added (one per course for `type: course`); use its `id` for follow-up calls. `shared_requirement_program` is the `id` of the shared requirement program for `type: shared_requirement`, otherwise `null`. `pending_change` is the change record that will be replayed onto the published version when you publish.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddSubrequirementResponse"}}}},"400":{"description":"Validation failure.\n\nCommon causes:\n- Missing `type` or `data` - returns `Missing required field(s): 'type', 'data'`\n- Empty `type` - returns `'type' cannot be empty`\n- `data` is not an object - returns `'data' must be a dictionary`\n- Unknown `type` - returns `Type '{type}' is not supported.`\n- `type: course` without `data.courses` - returns `'data.courses' is required for type 'course'`\n- `type: course` with an empty or non-list `courses` - returns `'data.courses' must be a non-empty list for type 'course'`\n- A course code that is not in the catalog - returns `Invalid course code(s): [BAD 101]`\n- `type: new_requirement` with none of `name`, `default_concentration`, `concentration` - returns `'data.name' or 'data.default_concentration' or 'data.concentration' is required for type 'new_requirement'`\n- `is_concentration: true` without `concentration_type` - returns `'data.concentration_type' is required for type 'new_requirement' if is_concentration is True`\n- Unknown `concentration_type` - returns `No ConcentrationType matches the given query.`\n- `concentration` with no active match in the audit's program - returns `['No active concentration with external id {concentration} in program {program}.']`, or `['External id {concentration} matches {n} active concentrations in program {program}.']` when several match\n- `name` under a concentration parent that doesn't match exactly one active concentration of its type - returns `['No concentrations or more than 1 concentrations found with the given name:{name}']`\n- `default_concentration` not under a concentration parent, or the parent already has a default - returns `['Requirement not under concentration parent or there exists a default already.']`\n- The parent has no `anyxof`, `and`, `or`, or `minxcourses` constraint - returns `['Cannot add subrequirement if anyxof/and/or constraints are not added to parent requirement']`\n- A course code listed more than once - returns `['The following courses do not exist in the catalog: . Remove them first then try again.']`\n- A concentration parent of that type already exists in the audit - returns `['A Concentration requirement with the type {type} already exists. Edit that one.']`\n- `type: shared_requirement` without an integer `shared_requirement_id` - returns `'data.shared_requirement_id' is required for type 'shared_requirement'` or `'data.shared_requirement_id' must be an integer for type 'shared_requirement'`\n- `type: shared_requirement` without `name` - returns `'data.name' is required and must be a non-empty string for type 'shared_requirement'`\n- `shared_requirement_id` unknown or not yet published - returns `['Shared Requirement Does Not Exist']`\n- No audit has the given ID, or the parent isn't in this draft - returns `No Audit matches the given query.` or `No Requirement matches the given query.`","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorMessage"}}}},"401":{"description":"Missing or invalid Bearer token.","content":{"application/json":{"schema":{"type":"object","properties":{"detail":{"type":"string"}}}}}},"403":{"description":"Your IP address isn't on the allowlist for this endpoint. Contact Stellic to add it; the response includes the `client_ip` Stellic saw. Also returned, with only `error` set, when the audit isn't a draft (`Can only add subrequirements to draft audits.`) or belongs to a compliance requirement (`Compliance requirements are not available via the API.`).","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"string"},"message":{"type":"string"},"client_ip":{"type":"string"}}}}}}}}}}}
```

#### What happens after the call

* The new nodes go after the parent's existing children, and the response returns each one with its new `id`.
* `course` adds one node per course code. A course that's already under the parent moves to the end instead of being added twice.
* `new_requirement` with `name` adds a requirement with no rules. With `is_concentration: true` and a `concentration_type`, it adds a concentration parent with one child for each of the program's active concentrations of that type, and a rule to complete one of them.
* `shared_requirement` adds a linked copy, shown under the `name` you give. Later publishes of the shared requirement reach this audit until you [unlink](/stellic-partner-api/audits/unlink-a-shared-requirement.md) it.

#### Notes

{% hint style="warning" %}
**Give a requirement its grouping rule before you add anything under it.** The parent needs an `anyxof`, `and`, `or`, or `minxcourses` rule, which a new requirement doesn't have. Without one the call returns `['Cannot add subrequirement if anyxof/and/or constraints are not added to parent requirement']`. Add the rule with [`POST /requirements/{requirement_id}/constraints`](/stellic-partner-api/requirements/add-a-constraint-to-a-requirement.md) first. Concentration parents always have one, and an audit's root usually does.
{% endhint %}

* **Send course codes exactly as in your catalog, once each.** An unknown code returns `Invalid course code(s): [BAD 101]` and nothing is added. A code listed twice returns `['The following courses do not exist in the catalog: . Remove them first then try again.']`
* **Concentration values come from** [**`GET /programs/{external_id}`**](/stellic-partner-api/programs/read-a-program.md)**.** `concentration_type` takes the type's `external_id`. Under a concentration parent, `concentration` takes an active concentration's `external_id` and returns `['No active concentration with external id <id> in program <program>.']` if there isn't one. `default_concentration` takes the display name of the parent's default child, and a parent can have only one. An audit can have one concentration parent per type.
* **Link only published shared requirements.** `shared_requirement_id` is the program's `shared_requirement.id` from [`GET /programs/`](/stellic-partner-api/programs/list-programs.md). An unpublished or unknown one returns `['Shared Requirement Does Not Exist']`.
* **Only drafts can be edited.** A published or under-review audit returns a 403 with `Can only add subrequirements to draft audits.` A parent that isn't in this draft returns `No Requirement matches the given query.`
* **Errors return 400 with `{"error": "<message>"}`.** Messages from Stellic's requirement checks come wrapped as `['<message>']`, as shown above. A compliance requirement's audit returns a 403.

#### Related APIs

* [`GET /audits/{audit_id}`](/stellic-partner-api/audits/read-an-audit.md) to pick `parent_req_id`.
* [`DELETE /audits/{audit_id}/requirements/{parent_req_id}/subrequirements/{subrequirement_id}`](/stellic-partner-api/audits/remove-a-sub-requirement.md) to take a node back out.
* [`POST /requirements/{requirement_id}/constraints`](/stellic-partner-api/requirements/add-a-constraint-to-a-requirement.md) to add rules.
