Skip to content

Test Scores API ​

Base URL: /api/v1/testscores

Manage standardized test types, applicant test scores, score policies, verification workflows, and superscoring.

Note: Score submission and retrieval is also available through the People domain. See Cross-Domain Routes at the bottom. The admissions-scoped route was removed with that domain.

Endpoints Overview ​

Test Type Configuration ​

MethodEndpointDescription
GET/typesList test types
POST/typesCreate test type
GET/types/{testTypeID}Get test type details
PATCH/types/{testTypeID}Update test type
DELETE/types/{testTypeID}Delete test type

Test Sections ​

MethodEndpointDescription
POST/types/{testTypeID}/sectionsCreate test section
PATCH/sections/{sectionID}Update test section
DELETE/sections/{sectionID}Delete test section

Program Requirements ​

Removed (August 2026). The five /requirements endpoints read and wrote program_test_requirements, a table that exists in no schema, so every one of them failed at runtime and nothing in the interface called them. Institution-wide score policies are a separate feature and do work.

Score Policies ​

MethodEndpointDescription
GET/policiesList score policies
POST/policiesCreate score policy
GET/policies/{policyID}Get policy details
PATCH/policies/{policyID}Update policy
DELETE/policies/{policyID}Delete policy

Applicant Test Scores ​

MethodEndpointDescription
GET/scoresList test scores
POST/scoresSubmit test score
GET/scores/{scoreID}Get score details
PATCH/scores/{scoreID}Update score
DELETE/scores/{scoreID}Delete score

Score Verification ​

MethodEndpointDescription
POST/scores/{scoreID}/verifyVerify a score
POST/scores/{scoreID}/rejectReject a score
POST/scores/{scoreID}/mark-officialMark official score received

Cross-Domain Routes ​

MethodEndpointDescription
GET/people/persons/{personID}/test-scoresList scores by person
POST/people/persons/{personID}/test-scoresSubmit score for person
GET/people/persons/{personID}/superscore/{testTypeID}Calculate superscore

Test Types ​

List Test Types ​

GET /api/v1/testscores/types

Query Parameters ​

ParameterTypeDescription
active_onlybooleanOnly return active types
searchstringSearch in code, name
orderingstringSort by: sort_order (default)

Success Response (200 OK) ​

json
{
  "count": 5,
  "results": [
    {
      "test_type_id": 1,
      "code": "SAT",
      "name": "SAT",
      "min_score": 400,
      "max_score": 1600,
      "score_increment": 10,
      "validity_months": 60,
      "is_active": true,
      "sort_order": 1
    },
    {
      "test_type_id": 2,
      "code": "ACT",
      "name": "ACT",
      "min_score": 1,
      "max_score": 36,
      "score_increment": 1,
      "validity_months": 60,
      "is_active": true,
      "sort_order": 2
    }
  ]
}

Get Test Type Details ​

GET /api/v1/testscores/types/{testTypeID}

Returns the test type with all its sections.

Success Response (200 OK) ​

json
{
  "test_type_id": 1,
  "code": "SAT",
  "name": "SAT",
  "description": "Scholastic Assessment Test",
  "min_score": 400,
  "max_score": 1600,
  "score_increment": 10,
  "validity_months": 60,
  "is_active": true,
  "sort_order": 1,
  "created_at": "2024-01-01T00:00:00Z",
  "created_by": "admin",
  "updated_at": "2024-01-01T00:00:00Z",
  "updated_by": "admin",
  "sections": [
    {
      "section_id": 1,
      "test_type_id": 1,
      "code": "EBRW",
      "name": "Evidence-Based Reading and Writing",
      "min_score": 200,
      "max_score": 800,
      "is_required": true,
      "sort_order": 1
    },
    {
      "section_id": 2,
      "test_type_id": 1,
      "code": "MATH",
      "name": "Math",
      "min_score": 200,
      "max_score": 800,
      "is_required": true,
      "sort_order": 2
    }
  ]
}

Create Test Type ​

POST /api/v1/testscores/types

Request Body ​

FieldTypeRequiredDescription
codestringYesUnique code (max 20)
namestringYesDisplay name (max 100)
descriptionstringNoDescription (max 500)
min_scoredecimalYesMinimum possible score
max_scoredecimalYesMaximum possible score
score_incrementdecimalNoScore increment step
validity_monthsintegerNoHow long scores are valid
is_activebooleanNoActive status (default: true)
sort_orderintegerNoDisplay order

Update Test Type ​

PATCH /api/v1/testscores/types/{testTypeID}

Accepts the same fields as create, all optional.


Delete Test Type ​

DELETE /api/v1/testscores/types/{testTypeID}

Test Sections ​

Create Test Section ​

POST /api/v1/testscores/types/{testTypeID}/sections

Request Body ​

FieldTypeRequiredDescription
codestringYesSection code (max 30)
namestringYesSection name (max 100)
min_scoredecimalYesMinimum section score
max_scoredecimalYesMaximum section score
is_requiredbooleanNoWhether section is required
sort_orderintegerNoDisplay order

Example Request ​

json
{
  "code": "EBRW",
  "name": "Evidence-Based Reading and Writing",
  "min_score": 200,
  "max_score": 800,
  "is_required": true,
  "sort_order": 1
}

Update Test Section ​

PATCH /api/v1/testscores/sections/{sectionID}

Accepts the same fields as create, all optional.


Delete Test Section ​

DELETE /api/v1/testscores/sections/{sectionID}

Score Policies ​

List Score Policies ​

GET /api/v1/testscores/policies

Success Response (200 OK) ​

json
{
  "count": 2,
  "results": [
    {
      "policy_id": 1,
      "test_type_id": 1,
      "test_type_code": "SAT",
      "test_type_name": "SAT",
      "score_selection_method": "SUPERSCORE",
      "allow_superscoring": true,
      "superscore_method": "BEST_SECTION",
      "notes": "Use best section scores across all attempts",
      "is_active": true
    }
  ]
}

Create Score Policy ​

POST /api/v1/testscores/policies

Request Body ​

FieldTypeRequiredDescription
test_type_idintegerYesTest type
score_selection_methodstringYesHIGHEST, MOST_RECENT, SUPERSCORE, or MANUAL
allow_superscoringbooleanNoAllow superscoring
superscore_methodstringNoBEST_SECTION, WEIGHTED_AVERAGE, or CUSTOM
notesstringNoPolicy notes (max 500)
is_activebooleanNoActive status

Update Score Policy ​

PATCH /api/v1/testscores/policies/{policyID}

Accepts the same fields as create (except test_type_id), all optional.


Delete Score Policy ​

DELETE /api/v1/testscores/policies/{policyID}

Applicant Test Scores ​

List Scores ​

GET /api/v1/testscores/scores

Query Parameters ​

ParameterTypeDescription
person_idstring (UUID)Filter by person
application_idintegerFilter by application
test_type_idintegerFilter by test type
verification_status_idintegerFilter by verification status
is_officialbooleanFilter official scores
is_self_reportedbooleanFilter self-reported scores
orderingstringSort by: -test_date (default)

Success Response (200 OK) ​

json
{
  "count": 3,
  "results": [
    {
      "score_id": 1,
      "person_id": "550e8400-e29b-41d4-a716-446655440000",
      "application_id": 42,
      "test_type_id": 1,
      "test_type_code": "SAT",
      "test_type_name": "SAT",
      "composite_score": 1350,
      "test_date": "2024-06-01T00:00:00Z",
      "is_self_reported": false,
      "is_official": true,
      "verification_status_id": 1,
      "verification_status_code": "VERIFIED",
      "verification_status_display": "Verified",
      "created_at": "2024-07-15T10:00:00Z",
      "created_by": "admin"
    }
  ]
}

Get Score Details ​

GET /api/v1/testscores/scores/{scoreID}

Success Response (200 OK) ​

json
{
  "score_id": 1,
  "person_id": "550e8400-e29b-41d4-a716-446655440000",
  "application_id": 42,
  "test_type_id": 1,
  "test_type_code": "SAT",
  "test_type_name": "SAT",
  "composite_score": 1350,
  "test_date": "2024-06-01T00:00:00Z",
  "score_reported_at": "2024-07-01T00:00:00Z",
  "is_self_reported": false,
  "is_official": true,
  "verification_status_id": 1,
  "verification_status_code": "VERIFIED",
  "verification_status_display": "Verified",
  "verified_by": 5,
  "verified_at": "2024-07-15T10:00:00Z",
  "verification_notes": "Official score report received",
  "official_score_received_at": "2024-07-01T00:00:00Z",
  "score_send_confirmation": "CB-2024-123456",
  "notes": null,
  "created_at": "2024-07-15T10:00:00Z",
  "created_by": "admin",
  "updated_at": "2024-07-15T10:00:00Z",
  "updated_by": "admin",
  "section_scores": [
    {
      "score_section_id": 1,
      "score_id": 1,
      "section_id": 1,
      "section_code": "EBRW",
      "section_name": "Evidence-Based Reading and Writing",
      "score": 680,
      "percentile": 92,
      "min_score": 200,
      "max_score": 800
    },
    {
      "score_section_id": 2,
      "score_id": 1,
      "section_id": 2,
      "section_code": "MATH",
      "section_name": "Math",
      "score": 670,
      "percentile": 88,
      "min_score": 200,
      "max_score": 800
    }
  ]
}

Submit Score ​

POST /api/v1/testscores/scores

Request Body ​

FieldTypeRequiredDescription
person_idstring (UUID)YesPerson ID
application_idintegerNoAssociated application
test_type_idintegerYesTest type
composite_scoredecimalYesComposite/total score
test_datedatetimeYesDate test was taken
score_reported_atdatetimeNoWhen score was reported
is_self_reportedbooleanNoSelf-reported flag
score_send_confirmationstringNoScore send confirmation (max 100)
notesstringNoNotes (max 1000)
section_scoresarrayNoIndividual section scores
section_scores[].section_idintegerYesSection ID
section_scores[].scoredecimalYesSection score
section_scores[].percentileintegerNoPercentile rank

Example Request ​

json
{
  "person_id": "550e8400-e29b-41d4-a716-446655440000",
  "application_id": 42,
  "test_type_id": 1,
  "composite_score": 1350,
  "test_date": "2024-06-01T00:00:00Z",
  "is_self_reported": false,
  "section_scores": [
    { "section_id": 1, "score": 680, "percentile": 92 },
    { "section_id": 2, "score": 670, "percentile": 88 }
  ]
}

Update Score ​

PATCH /api/v1/testscores/scores/{scoreID}

Request Body ​

FieldTypeRequiredDescription
composite_scoredecimalNoUpdated composite score
test_datedatetimeNoUpdated test date
score_reported_atdatetimeNoWhen score was reported
score_send_confirmationstringNoConfirmation code (max 100)
notesstringNoNotes (max 1000)
section_scoresarrayNoUpdated section scores

Delete Score ​

DELETE /api/v1/testscores/scores/{scoreID}

Score Verification ​

Verify Score ​

POST /api/v1/testscores/scores/{scoreID}/verify

Marks a score as verified.

Request Body ​

FieldTypeRequiredDescription
verified_byintegerYesUser ID of verifier
verification_notesstringNoNotes (max 1000)

Reject Score ​

POST /api/v1/testscores/scores/{scoreID}/reject

Marks a score as rejected.

Request Body ​

FieldTypeRequiredDescription
verified_byintegerYesUser ID of reviewer
verification_notesstringYesReason for rejection (max 1000)

Mark Official Score Received ​

POST /api/v1/testscores/scores/{scoreID}/mark-official

Records that an official score report has been received.

Request Body ​

FieldTypeRequiredDescription
verified_byintegerYesUser ID of reviewer
official_score_received_atdatetimeYesDate official score was received
document_idintegerNoAssociated document ID
verification_notesstringNoNotes (max 1000)

Cross-Domain Routes ​

Scores by Person ​

GET /api/v1/people/persons/{personID}/test-scores
POST /api/v1/people/persons/{personID}/test-scores

List or submit test scores for a specific person. The POST endpoint accepts the same body as POST /testscores/scores (the person_id is inferred from the path).


Scores by Application ​

Removed in August 2026 with the admissions domain (migration 167). Scores are reached by person instead: GET /api/v1/people/persons/{personID}/test-scores.


Superscoring ​

GET /api/v1/people/persons/{personID}/superscore/{testTypeID}

Calculate the superscore for a person across all their test attempts of the given type. Takes the best section score from each attempt and combines them.

Success Response (200 OK) ​

json
{
  "person_id": "550e8400-e29b-41d4-a716-446655440000",
  "test_type_id": 1,
  "test_type_code": "SAT",
  "test_type_name": "SAT",
  "superscore": 1400,
  "calculated_at": "2024-12-20T10:00:00Z",
  "source_scores": [
    {
      "score_id": 1,
      "test_date": "2024-03-01T00:00:00Z",
      "composite_score": 1350
    },
    {
      "score_id": 2,
      "test_date": "2024-06-01T00:00:00Z",
      "composite_score": 1380
    }
  ],
  "section_bests": [
    {
      "section_id": 1,
      "section_code": "EBRW",
      "section_name": "Evidence-Based Reading and Writing",
      "best_score": 720,
      "from_score_id": 2,
      "from_test_date": "2024-06-01T00:00:00Z"
    },
    {
      "section_id": 2,
      "section_code": "MATH",
      "section_name": "Math",
      "best_score": 680,
      "from_score_id": 1,
      "from_test_date": "2024-03-01T00:00:00Z"
    }
  ]
}

TypeScript Types ​

typescript
interface TestType {
  test_type_id: number
  code: string
  name: string
  description: string | null
  min_score: number
  max_score: number
  score_increment: number | null
  validity_months: number | null
  is_active: boolean
  sort_order: number
  sections?: TestSection[]
}

interface TestSection {
  section_id: number
  test_type_id: number
  code: string
  name: string
  min_score: number
  max_score: number
  is_required: boolean
  sort_order: number
}

interface ProgramTestRequirement {
  requirement_id: number
  test_type_id: number
  test_type_code: string
  test_type_name: string
  application_type_id: number | null
  program_id: number | null
  is_required: boolean
  minimum_score: number | null
  preferred_score: number | null
  accept_superscoring: boolean
  alternative_test_type_id: number | null
  alternative_test_code: string | null
  notes: string | null
  is_active: boolean
}

interface ScorePolicy {
  policy_id: number
  test_type_id: number
  test_type_code: string
  test_type_name: string
  score_selection_method: 'HIGHEST' | 'MOST_RECENT' | 'SUPERSCORE' | 'MANUAL'
  allow_superscoring: boolean
  superscore_method: 'BEST_SECTION' | 'WEIGHTED_AVERAGE' | 'CUSTOM' | null
  notes: string | null
  is_active: boolean
}

interface ApplicantTestScore {
  score_id: number
  person_id: string
  application_id: number | null
  test_type_id: number
  test_type_code: string
  test_type_name: string
  composite_score: number
  test_date: string
  score_reported_at: string | null
  self_reported_at: string | null
  is_self_reported: boolean
  is_official: boolean
  verification_status_id: number | null
  verification_status_code: string | null
  verification_status_display: string | null
  verified_by: number | null
  verified_at: string | null
  verification_notes: string | null
  official_score_received_at: string | null
  score_send_confirmation: string | null
  document_id: number | null
  notes: string | null
  section_scores?: SectionScore[]
}

interface SectionScore {
  score_section_id: number
  score_id: number
  section_id: number
  section_code: string
  section_name: string
  score: number
  percentile: number | null
  min_score: number
  max_score: number
}

interface Superscore {
  person_id: string
  test_type_id: number
  test_type_code: string
  test_type_name: string
  superscore: number
  calculated_at: string
  source_scores: SuperscoreSource[]
  section_bests: SectionBest[]
}

interface SuperscoreSource {
  score_id: number
  test_date: string
  composite_score: number
}

interface SectionBest {
  section_id: number
  section_code: string
  section_name: string
  best_score: number
  from_score_id: number
  from_test_date: string
}



Complete endpoint reference ​

Every route this domain registers, with the permission node that gates it. Generated from the route table, so it is complete by construction — the sections above explain behaviour, this one guarantees nothing is missing.

Paths are relative to /api/v1/testscores. A node in italics is an exemption rather than a grant: selfservice acts only on the caller's own record, public needs no session at all, and custom carries its own wrapper.

MethodEndpointPermission nodeDescription
GET/policiesTESTSCORES.POLICIESList policies
POST/policiesTESTSCORES.POLICIESCreate a policy
DELETE/policies/{policyID}TESTSCORES.POLICIESDelete a policy
GET/policies/{policyID}TESTSCORES.POLICIESGet one policy
PATCH/policies/{policyID}TESTSCORES.POLICIESUpdate a policy
GET/scoresSTUDENTS.TAB.TEST_SCORESList scores
POST/scoresSTUDENTS.SCORES.EDITCreate a score
DELETE/scores/{scoreID}STUDENTS.SCORES.EDITDelete a score
GET/scores/{scoreID}STUDENTS.TAB.TEST_SCORESGet one score
PATCH/scores/{scoreID}STUDENTS.SCORES.EDITUpdate a score
POST/scores/{scoreID}/mark-officialADMISSIONS.SCORES.VERIFYMark official for a score
POST/scores/{scoreID}/rejectADMISSIONS.SCORES.VERIFYReject for a score
POST/scores/{scoreID}/verifyADMISSIONS.SCORES.VERIFYVerify for a score
DELETE/sections/{sectionID}TESTSCORES.TYPESDelete a section
PATCH/sections/{sectionID}TESTSCORES.TYPESUpdate a section
GET/typesTESTSCORES.TYPESList types
POST/typesTESTSCORES.TYPESCreate a type
DELETE/types/{testTypeID}TESTSCORES.TYPESDelete a type
GET/types/{testTypeID}TESTSCORES.TYPESGet one type
PATCH/types/{testTypeID}TESTSCORES.TYPESUpdate a type
POST/types/{testTypeID}/sectionsTESTSCORES.TYPESAdd sections to a type

Student Information System Documentation