Skip to main content

Add Credential

API Endpoint: POST /admin/law-firms/{lawFirmId}/users/{userId}/credentials

Priority: P1

User Story: As an admin, I want to add a professional credential to a user's profile.

Overview

Attach professional credentials (bar licenses, notary certifications, etc.) to user profiles within a law firm. Credentials are verified and stored with metadata for compliance tracking.

Scenarios

Scenario 1: Add bar license credential

Given:

  • Admin is authenticated with scope credentials:create
  • Law firm firm_abc123 exists
  • User user_12345 exists in law firm
  • User has functional role LAWYER

When:

  • Admin POSTs to /admin/law-firms/firm_abc123/users/user_12345/credentials with payload:
    {
    "credentialType": "BAR_LICENSE",
    "issuingAuthority": "New York State Bar",
    "credentialNumber": "12345678",
    "issueDate": "2020-01-15",
    "expirationDate": "2025-12-31",
    "jurisdictions": ["NY"],
    "status": "ACTIVE",
    "verificationStatus": "VERIFIED",
    "metadata": {
    "admissionDate": "2020-01-15",
    "courtAdmissions": ["NY Supreme Court", "US District Court SDNY"]
    }
    }

Then:

  • Response status is 201 Created
  • Response body contains:
    {
    "id": "cred_xyz789",
    "userId": "user_12345",
    "credentialType": "BAR_LICENSE",
    "issuingAuthority": "New York State Bar",
    "credentialNumber": "12345678",
    "issueDate": "2020-01-15",
    "expirationDate": "2025-12-31",
    "jurisdictions": ["NY"],
    "status": "ACTIVE",
    "verificationStatus": "VERIFIED",
    "metadata": {
    "admissionDate": "2020-01-15",
    "courtAdmissions": ["NY Supreme Court", "US District Court SDNY"]
    },
    "createdAt": "2025-10-19T10:00:00Z",
    "updatedAt": "2025-10-19T10:00:00Z"
    }
  • Credential is stored and linked to user profile

Scenario 2: Add notary certification

Given:

  • Admin is authenticated with scope credentials:create
  • Law firm firm_abc123 exists
  • User user_12345 exists with role PARALEGAL

When:

  • Admin POSTs notary credential:
    {
    "credentialType": "NOTARY_PUBLIC",
    "issuingAuthority": "California Secretary of State",
    "credentialNumber": "NP-987654",
    "issueDate": "2024-03-01",
    "expirationDate": "2028-03-01",
    "jurisdictions": ["CA"],
    "status": "ACTIVE",
    "verificationStatus": "PENDING"
    }

Then:

  • Response status is 201 Created
  • Credential created with PENDING verification status
  • Can be verified later through separate workflow

Scenario 3: Duplicate credential rejection

Given:

  • Admin is authenticated with scope credentials:create
  • User user_12345 already has BAR_LICENSE credential with number 12345678

When:

  • Admin attempts to add same credential:
    {
    "credentialType": "BAR_LICENSE",
    "credentialNumber": "12345678",
    "issuingAuthority": "New York State Bar"
    }

Then:

  • Response status is 409 Conflict
  • Response body contains:
    {
    "error": "DUPLICATE_CREDENTIAL",
    "message": "User already has BAR_LICENSE credential with number '12345678'"
    }

Scenario 4: Missing required fields

Given:

  • Admin is authenticated with scope credentials:create
  • User exists

When:

  • Admin POSTs without required fields:
    {
    "credentialType": "BAR_LICENSE"
    }

Then:

  • Response status is 400 Bad Request
  • Response contains validation errors for missing fields:
    {
    "error": "VALIDATION_ERROR",
    "message": "Missing required fields",
    "details": [
    {
    "field": "issuingAuthority",
    "message": "Required field"
    },
    {
    "field": "credentialNumber",
    "message": "Required field"
    }
    ]
    }

Scenario 5: Invalid credential type

Given:

  • Admin is authenticated with scope credentials:create

When:

  • Admin POSTs with invalid credential type:
    {
    "credentialType": "INVALID_TYPE",
    "issuingAuthority": "Some Authority",
    "credentialNumber": "12345"
    }

Then:

  • Response status is 400 Bad Request
  • Response contains:
    {
    "error": "VALIDATION_ERROR",
    "message": "Invalid credential type",
    "details": [
    {
    "field": "credentialType",
    "message": "Must be one of: BAR_LICENSE, NOTARY_PUBLIC, PROFESSIONAL_CERTIFICATION"
    }
    ]
    }

Scenario 6: User not found

Given:

  • Admin is authenticated with scope credentials:create
  • No user with ID user_nonexistent

When:

  • Admin POSTs to /admin/law-firms/firm_abc123/users/user_nonexistent/credentials

Then:

  • Response status is 404 Not Found
  • Response body contains:
    {
    "error": "NOT_FOUND",
    "message": "User with ID 'user_nonexistent' not found in law firm 'firm_abc123'"
    }

Request Specification

Path Parameters

ParameterTypeRequiredDescription
lawFirmIdstringYesLaw firm identifier
userIdstringYesUser identifier

Request Body

FieldTypeRequiredConstraintsDescription
credentialTypestringYesEnum: BAR_LICENSE, NOTARY_PUBLIC, PROFESSIONAL_CERTIFICATIONType of credential
issuingAuthoritystringYes1-200 charsOrganization that issued credential
credentialNumberstringYes1-100 charsUnique credential identifier
issueDatestringNoISO 8601 dateDate credential was issued
expirationDatestringNoISO 8601 dateDate credential expires
jurisdictionsarrayNoArray of stringsJurisdictions where valid
statusstringNoEnum: ACTIVE, INACTIVE, SUSPENDED, REVOKEDCurrent status (defaults to ACTIVE)
verificationStatusstringNoEnum: VERIFIED, PENDING, FAILEDVerification state (defaults to PENDING)
metadataobjectNoJSON objectAdditional credential data

Validation Rules

RuleDescription
Credential uniquenessUser cannot have duplicate credential (same type + number)
Date validationexpirationDate must be after issueDate if both provided
Jurisdiction formatMust be valid 2-letter state/country codes
Status valuesMust be valid enum value

Response Specification

Success Response (201 Created)

{
"id": "cred_xyz789",
"userId": "user_12345",
"credentialType": "BAR_LICENSE",
"issuingAuthority": "New York State Bar",
"credentialNumber": "12345678",
"issueDate": "2020-01-15",
"expirationDate": "2025-12-31",
"jurisdictions": ["NY"],
"status": "ACTIVE",
"verificationStatus": "VERIFIED",
"metadata": {
"admissionDate": "2020-01-15",
"courtAdmissions": ["NY Supreme Court", "US District Court SDNY"]
},
"createdAt": "2025-10-19T10:00:00Z",
"updatedAt": "2025-10-19T10:00:00Z"
}

Response Fields

FieldTypeDescription
idstringUnique credential identifier (generated)
userIdstringUser this credential belongs to
credentialTypestringType of credential
issuingAuthoritystringIssuing organization
credentialNumberstringCredential number/ID
issueDatestring | nullIssue date (ISO 8601)
expirationDatestring | nullExpiration date (ISO 8601)
jurisdictionsarrayValid jurisdictions
statusstringCurrent status
verificationStatusstringVerification state
metadataobject | nullAdditional data
createdAtstringCreation timestamp
updatedAtstringLast update timestamp

Error Responses

StatusError CodeDescription
400VALIDATION_ERRORInvalid input or missing required fields
401UNAUTHORIZEDMissing or invalid auth token
403FORBIDDENMissing credentials:create scope
404NOT_FOUNDUser or law firm not found
409DUPLICATE_CREDENTIALUser already has this credential

Requirements Mapping

  • FR-030: Accept POST with credential details
  • FR-031: Validate credential type against allowed enum
  • FR-032: Ensure credential uniqueness per user (type + number)
  • FR-033: Support optional date fields (issue/expiration)
  • FR-034: Store verification status (VERIFIED, PENDING, FAILED)
  • FR-035: Support jurisdictions array for multi-state licenses
  • FR-036: Allow flexible metadata storage
  • FR-037: Return complete credential details with generated ID
  • FR-038: Prevent duplicate credentials (409 response)
  • FR-039: Link credential to user profile