# API Contract: Verified Credential Response Types

**Branch**: `003-credential-filtering-api` | **Date**: 2026-01-21

## Overview

This document defines the API contract for verified credential response types, including typed accessors on `VerifiedCredential`, the new `VerifiedResponse` wrapper, and parsed credential structs.

---

## VerifiedCredential Typed Accessors

New methods added to the existing `VerifiedCredential` struct.

### Singular Claim Accessors

Return the first element if the claim is an array.

```rust
impl VerifiedCredential {
    /// Get email as a single value.
    /// If stored as array, returns first element.
    ///
    /// # Example
    /// ```rust,no_run
    /// let email = credential.email();
    /// if let Some(e) = email {
    ///     println!("Email: {}", e);
    /// }
    /// ```
    #[must_use]
    pub fn email(&self) -> Option<&str>;

    /// Get phone number as a single value.
    #[must_use]
    pub fn phone_number(&self) -> Option<&str>;

    /// Get display name.
    #[must_use]
    pub fn display_name(&self) -> Option<&str>;

    /// Get given (first) name.
    #[must_use]
    pub fn given_name(&self) -> Option<&str>;

    /// Get family (last) name.
    #[must_use]
    pub fn family_name(&self) -> Option<&str>;

    /// Get company name.
    #[must_use]
    pub fn company(&self) -> Option<&str>;

    /// Get job title.
    #[must_use]
    pub fn job_title(&self) -> Option<&str>;
}
```

### Plural Claim Accessors

Return all values when claims may be arrays.

```rust
impl VerifiedCredential {
    /// Get all email addresses.
    /// Returns single-element vec if stored as string.
    #[must_use]
    pub fn emails(&self) -> Vec<&str>;

    /// Get all phone numbers.
    #[must_use]
    pub fn phone_numbers(&self) -> Vec<&str>;
}
```

### Metadata Accessors

```rust
impl VerifiedCredential {
    /// Get credential issuance timestamp.
    /// This is the `iat` claim from the credential.
    #[must_use]
    pub fn verified_at(&self) -> Option<DateTime<Utc>> {
        // Returns self.issued_at (already available)
    }

    /// Get device attestation data from KB-JWT.
    /// Returns None if not present in the credential claims.
    #[must_use]
    pub fn device_attestation(&self) -> Option<DeviceAttestation>;

    /// Get device security context from KB-JWT.
    /// Returns None if not present in the credential claims.
    #[must_use]
    pub fn device_security(&self) -> Option<DeviceSecurity>;

    /// Get verification metadata for a specific field.
    ///
    /// # Arguments
    /// * `field_name` - The field to get verification for (e.g., "email", "phone")
    #[must_use]
    pub fn field_verification(&self, field_name: &str) -> Option<FieldVerificationMetadata>;
}
```

---

## VerifiedResponse

Wrapper for multi-credential presentations.

```rust
/// Response from credential verification containing one or more credentials.
///
/// # Example
/// ```rust,no_run
/// // Iterate over all credentials
/// for cred in response.credentials() {
///     println!("Type: {}", cred.credential_type);
/// }
///
/// // Get specific credential by type
/// if let Some(persona) = response.credential_by_type("LastID.Persona") {
///     println!("Display name: {:?}", persona.display_name());
/// }
///
/// // Get the primary (leaf) credential
/// let primary = response.primary_credential().unwrap();
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VerifiedResponse {
    pub credentials: Vec<VerifiedCredential>,
    pub credential_chain: Vec<String>,
    pub device_attestation: Option<DeviceAttestation>,
    pub device_security: Option<DeviceSecurity>,
    pub field_verification: HashMap<String, FieldVerificationMetadata>,
}

impl VerifiedResponse {
    /// Iterate over all verified credentials.
    #[must_use]
    pub fn credentials(&self) -> impl Iterator<Item = &VerifiedCredential>;

    /// Get a credential by its type (vct).
    ///
    /// # Arguments
    /// * `credential_type` - The credential type (e.g., "LastID.Persona")
    #[must_use]
    pub fn credential_by_type(&self, credential_type: &str) -> Option<&VerifiedCredential>;

    /// Get the credential chain in order (root to leaf).
    #[must_use]
    pub fn credential_chain(&self) -> &[String];

    /// Get the primary (leaf) credential from the presentation.
    /// This is the target credential that was requested.
    #[must_use]
    pub fn primary_credential(&self) -> Option<&VerifiedCredential>;

    /// Get device attestation data.
    #[must_use]
    pub fn device_attestation(&self) -> Option<&DeviceAttestation>;

    /// Get device security context.
    #[must_use]
    pub fn device_security(&self) -> Option<&DeviceSecurity>;

    /// Get field verification metadata for a specific field.
    #[must_use]
    pub fn field_verification(&self, field: &str) -> Option<&FieldVerificationMetadata>;
}
```

---

## Parsed Credential Structs

All implement `TryFrom<&VerifiedCredential>`.

### ParsedPersona

```rust
/// Parsed LastID.Persona credential.
///
/// # Example
/// ```rust,no_run
/// use lastid_sdk::ParsedPersona;
///
/// let persona = ParsedPersona::try_from(&credential)?;
/// println!("Name: {:?}", persona.display_name);
/// println!("Email: {:?}", persona.email);
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ParsedPersona {
    pub display_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub company: Option<String>,
    pub job_title: Option<String>,
    pub avatar_url: Option<String>,
    pub website: Option<String>,
    pub location: Option<String>,
    pub visibility: Option<String>,
}

impl TryFrom<&VerifiedCredential> for ParsedPersona {
    type Error = LastIDError;

    /// Parse a VerifiedCredential into a ParsedPersona.
    ///
    /// # Errors
    /// Returns `CredentialParse` error if credential type is not LastID.Persona.
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

### ParsedEmployment

```rust
/// Parsed LastID.Employment credential.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ParsedEmployment {
    pub company: Option<String>,
    pub title: Option<String>,
    pub department: Option<String>,
    pub start_date: Option<String>,
    pub end_date: Option<String>,
    pub is_current: Option<bool>,
    pub responsibilities: Option<String>,
}

impl TryFrom<&VerifiedCredential> for ParsedEmployment {
    type Error = LastIDError;
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

### ParsedVerifiedEmail

```rust
/// Parsed LastID.VerifiedEmail credential.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ParsedVerifiedEmail {
    pub email: String, // Required field
    pub verified_at: Option<i64>,
    pub verification_method: Option<String>,
    pub provider: Option<String>,
}

impl TryFrom<&VerifiedCredential> for ParsedVerifiedEmail {
    type Error = LastIDError;

    /// # Errors
    /// Returns error if credential type is wrong OR email field is missing.
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

### ParsedVerifiedPhone

```rust
/// Parsed LastID.VerifiedPhone credential.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ParsedVerifiedPhone {
    pub phone_number: String, // Required field
    pub verified_at: Option<i64>,
    pub verification_method: Option<String>,
    pub provider: Option<String>,
}

impl TryFrom<&VerifiedCredential> for ParsedVerifiedPhone {
    type Error = LastIDError;

    /// # Errors
    /// Returns error if credential type is wrong OR phone_number field is missing.
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

### ParsedVerifiedPersona

```rust
/// Parsed LastID.VerifiedPersona credential.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ParsedVerifiedPersona {
    pub display_name: Option<String>,
    pub given_name: Option<String>,
    pub family_name: Option<String>,
    pub email: Option<String>,
    pub phone: Option<String>,
    pub verification_level: Option<String>,
    pub verified_at: Option<i64>,
}

impl TryFrom<&VerifiedCredential> for ParsedVerifiedPersona {
    type Error = LastIDError;
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

### ParsedAgeProof

```rust
/// Parsed LastID.AgeProof credential.
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ParsedAgeProof {
    pub age: Option<i64>,
    pub age_verified: bool,
    pub verification_date: Option<String>,
    pub minimum_age_met: Option<i64>,
}

impl TryFrom<&VerifiedCredential> for ParsedAgeProof {
    type Error = LastIDError;
    fn try_from(cred: &VerifiedCredential) -> Result<Self, Self::Error>;
}
```

---

## Error Types

New error variants added to `LastIDError`:

```rust
impl LastIDError {
    /// Create a credential parsing error.
    ///
    /// Used when TryFrom fails due to type mismatch or missing required fields.
    pub fn credential_parse(message: impl Into<String>) -> Self;
}
```

Error messages follow the pattern:
- Type mismatch: `"Expected {expected}, got {actual}"`
- Missing field: `"Missing required field: {field}"`
