Bulk Update Assignment Submission License
Sets or clears the license associated with many assignment submissions in a single request.
Sets or clears the license associated with many assignment submissions in a single request.
https://example.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicenseThis endpoint is served by the v3 API. Its full path is
https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense, not the/incoming/v2base URL used by the rest of this reference. Use the full URL shown in the examples below.
Example request — set a license per submission
curl -X POST "https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"submissions": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"licenseId": "223e4567-e89b-12d3-a456-426614174111"
},
{
"id": "323e4567-e89b-12d3-a456-426614174222",
"licenseId": "423e4567-e89b-12d3-a456-426614174333"
}
]
}'Example request — apply one license to many submissions
curl -X POST "https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"ids": [
"123e4567-e89b-12d3-a456-426614174000",
"323e4567-e89b-12d3-a456-426614174222"
],
"licenseId": "223e4567-e89b-12d3-a456-426614174111"
}'Example request — clear the license
Pass licenseId: null to remove the license from a submission. This works in either shape.
curl -X POST "https://{instance}.thoughtindustries.com/v3/assignmentSubmissions/bulkUpdateLicense" \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"ids": ["123e4567-e89b-12d3-a456-426614174000"],
"licenseId": null
}'Parameters
Requires the assignments.grade permission. A standard company API key satisfies this.
Send submissions, or ids with a sibling licenseId, or both. At least one of the two lists must
be present and non-empty.
| Name | Type | Required | Location | Description |
|---|---|---|---|---|
submissions | BulkUpdateSubmissionItem[] | One of submissions / ids | body | Per-submission updates, each naming its own target license. |
ids | uuid[] | One of submissions / ids | body | Submission IDs that all receive the same licenseId. Requires the sibling licenseId field. |
licenseId | uuid | null | Required when ids is present | body | License applied to every entry in ids. Pass null to clear. Ignored when ids is absent. |
Each object in submissions (BulkUpdateSubmissionItem):
| Field | Type | Required | Description |
|---|---|---|---|
id | uuid | Yes | Assignment submission to update. |
licenseId | uuid | null | Yes | Target license for this submission. Pass null to clear the current license. |
Batch limit: 1000 submissions per request. The Batch limit exceeded error counts unique
submission IDs after submissions and ids are merged — not each array separately. A request with
600 IDs in each list but only 900 distinct IDs between them is accepted. Each array is also capped
at 1000 entries on its own, and that cap is enforced earlier, during request validation, so a single
array of 1001 entries returns the opaque 400 described under Errors instead of the friendly
message.
Precedence: ids wins. When the same submission ID appears in both submissions and ids, the
shared licenseId from ids is applied and the per-item value is discarded.
Only submissions and licenses belonging to your instance can be referenced. IDs from another instance are treated as not found.
Example response
Both shapes return 200 with the same body. Note the data wrapper — v3 endpoints nest their
payload under data.
{
"data": {
"updatedRecordCount": 2,
"errors": []
}
}Response fields
| Field | Type | Description |
|---|---|---|
data.updatedRecordCount | integer | Number of submissions whose license was actually changed. |
data.errors | object[] | Per-item failures. Empty when every submission was updated. |
data.errors[].id | uuid | The ID that triggered the failure — a submission ID, or a license ID when record is license. |
data.errors[].error | string | Human-readable reason the entry failed. |
data.errors[].record | string | Which side of the update failed: submission or license. |
Partial success
Individual failures never fail the batch. Unknown submissions, unknown licenses, and IDs owned by
another instance are collected into errors[] and skipped; everything else still updates. A
response with a non-empty errors[] and a 200 status is normal — always read
updatedRecordCount rather than relying on the status code alone.
An unrecognized submission ID returns:
{
"data": {
"updatedRecordCount": 0,
"errors": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"error": "Submission not found or does not belong to company",
"record": "submission"
}
]
}
}Two behaviors are worth planning for:
- An unrecognized license produces one error entry per affected submission, and each entry
carries the license ID rather than the submission ID. Ten submissions pointing at the same bad
license yield ten identical entries. Use
record: "license"to tell these apart from submission failures. - Duplicate submission IDs are reported but not skipped. A duplicate adds an entry with the
message
Duplicate submission id in payload — last value applied, and the last value still applies, soupdatedRecordCountcan be positive alongside a non-emptyerrors[].
Updates are surgical: only the submission's license and its updated-at timestamp change. Grades, feedback, and submission content are untouched.
Search indexing for updated submissions happens asynchronously after the response is sent. It never affects the result, but the search index is eventually consistent — a submission may briefly appear under its previous license in search results.
Errors
Three conditions fail the entire request with a message naming the problem. All return 400 with
code: "VALIDATION_ERROR":
| Condition | Message |
|---|---|
Neither submissions nor ids supplied | At least one of `submissions` or `ids` must be provided |
ids supplied without a sibling licenseId | `licenseId` is required when `ids` is provided (may be null to clear) |
| More than 1000 unique IDs after merging, each array being within 1000 | Batch limit exceeded: combined item count must be <= 1000 |
{
"code": "VALIDATION_ERROR",
"message": "At least one of `submissions` or `ids` must be provided",
"category": "VALIDATION",
"timestamp": "2026-08-12T00:00:00.000Z",
"statusCode": 400,
"metadata": {
"errors": ["submissions", "ids"]
}
}A missing or invalid API key returns 401 with code: "UNAUTHENTICATED", and a key without the
assignments.grade permission returns 403 with code: "FORBIDDEN". Both use the same body shape
with category: "CLIENT_ERROR" and carry no metadata.
Malformed field values return an opaque 400. A value that is not a valid UUID, any property
the endpoint does not recognize, or more than 1000 entries in a single array is rejected before the
request reaches the endpoint. All three return code: "BAD_REQUEST", category: "CLIENT_ERROR" and
message: "Bad Request Exception", with no indication of which field was at fault. Validate UUIDs,
check field spelling, and split oversized arrays client-side; log the request body alongside the
response.
Rate limit
500 requests per 60 seconds, counted per instance. This limit is specific to this endpoint and is
independent of the per-family rate limits that apply to /incoming/v2 endpoints.