{
  "openapi": "3.0.3",
  "info": {
    "title": "HACS FI API",
    "description": "Consent-gated B2B credit-score API for financial institutions (soft-pull only). Deny-by-default: data is returned only under an ACTIVE user consent. The only subject identifier on this surface is the partner pairwise subject identifier (pairwiseSub). Score scale 0-850, tier cuts 750/650/550/450, reason codes catalog of 31 with at most 4 per response, attestation validity 90 days, rate limit 60 req/min.",
    "version": "1.0.0"
  },
  "servers": [
    { "url": "https://trendy.storydot.kr/superapp/api", "description": "Production (LIVE and SANDBOX share this URL; environment is bound to the API key)" }
  ],
  "security": [
    { "clientId": [], "apiKey": [] }
  ],
  "components": {
    "securitySchemes": {
      "clientId": { "type": "apiKey", "in": "header", "name": "X-Client-Id" },
      "apiKey": { "type": "apiKey", "in": "header", "name": "X-API-Key" }
    },
    "schemas": {
      "StandardResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean" },
          "message": { "type": "string" },
          "errorCode": { "type": "string", "nullable": true },
          "data": { "nullable": true }
        },
        "required": ["success"]
      },
      "FiScoreQueryRequest": {
        "type": "object",
        "required": ["pairwiseSub", "purpose"],
        "properties": {
          "pairwiseSub": { "type": "string", "description": "Partner pairwise subject identifier" },
          "purpose": { "type": "string", "description": "Query purpose, e.g. UNDERWRITING / ACCOUNT_REVIEW / PREQUALIFY" }
        }
      },
      "FiReasonCode": {
        "type": "object",
        "properties": {
          "code": { "type": "string" },
          "labelEn": { "type": "string" },
          "labelLocalized": { "type": "string" },
          "category": { "type": "string" },
          "impactPoints": { "type": "integer", "nullable": true },
          "rank": { "type": "integer" }
        }
      },
      "FiDataSufficiency": {
        "type": "object",
        "properties": {
          "featuresObserved": { "type": "integer" },
          "featuresTotal": { "type": "integer" },
          "coveragePercent": { "type": "number" }
        }
      },
      "FiScoreResponse": {
        "type": "object",
        "properties": {
          "pairwiseSub": { "type": "string" },
          "score": { "type": "integer", "minimum": 0, "maximum": 850 },
          "scoreMin": { "type": "integer", "example": 0 },
          "scoreMax": { "type": "integer", "example": 850 },
          "tier": { "type": "string", "enum": ["EXCELLENT", "GOOD", "FAIR", "BELOW_AVERAGE", "POOR"], "description": "EXCELLENT 750-850, GOOD 650-749, FAIR 550-649, BELOW_AVERAGE 450-549, POOR 0-449" },
          "pullType": { "type": "string", "example": "SOFT" },
          "reasonCodes": { "type": "array", "maxItems": 4, "items": { "$ref": "#/components/schemas/FiReasonCode" } },
          "modelVersion": { "type": "string" },
          "scoredAt": { "type": "string", "example": "2026-08-20 14:32:11" },
          "decisionHash": { "type": "string", "nullable": true, "description": "Decision provenance hash, present when available. Metadata fields are outside the immutable HACS_DECISION_V1 canonical hash." },
          "environment": { "type": "string", "enum": ["LIVE", "SANDBOX"] },
          "confidence": { "type": "string", "enum": ["HIGH", "MEDIUM", "LOW"] },
          "dataSufficiency": { "$ref": "#/components/schemas/FiDataSufficiency" },
          "payloadSignature": { "type": "string", "nullable": true, "description": "Detached issuer signature over the canonical FI payload (LIVE responses only; SANDBOX responses are unsigned)." },
          "signatureAlg": { "type": "string", "nullable": true },
          "signerKeyId": { "type": "string", "nullable": true },
          "zkp": { "type": "object", "nullable": true, "description": "Tier commitment metadata for offline threshold-proof verification, when available." },
          "contributions": { "type": "object", "nullable": true, "description": "Feature contribution detail; present only when the consent's data categories include 'contributions'." }
        }
      },
      "FiDecisionRequest": {
        "type": "object",
        "required": ["pairwiseSub", "purpose", "productCode"],
        "properties": {
          "pairwiseSub": { "type": "string", "description": "Partner pairwise subject identifier" },
          "purpose": { "type": "string", "description": "Query purpose; consent is checked per (client, purpose)" },
          "productCode": { "type": "string", "minLength": 1, "maxLength": 40, "pattern": "^[A-Z0-9_]+$", "description": "Uppercase letters, digits and underscores only. A malformed value is rejected with VALIDATION_ERROR (SW1001)." }
        }
      },
      "FiDecisionTerms": {
        "type": "object",
        "description": "Terms taken from the matched terms-matrix row. Omitted entirely on DECLINE and when no row matches.",
        "properties": {
          "depositRatePct": { "type": "number", "nullable": true, "description": "Required deposit / prepayment percentage" },
          "creditLimit": { "type": "number", "nullable": true },
          "currency": { "type": "string", "nullable": true, "description": "3-letter currency code" },
          "maxTenorMonths": { "type": "integer", "nullable": true },
          "interestAprPct": { "type": "number", "nullable": true, "description": "Annual percentage rate" }
        }
      },
      "FiFiredRule": {
        "type": "object",
        "properties": {
          "ruleCode": { "type": "string" },
          "ruleType": { "type": "string", "enum": ["KNOCKOUT", "REVIEW"] },
          "messageEn": { "type": "string" }
        }
      },
      "FiDecisionResponse": {
        "type": "object",
        "properties": {
          "pairwiseSub": { "type": "string" },
          "productCode": { "type": "string" },
          "segmentKey": { "type": "string", "description": "Segment actually applied, from the chain {country}:{productCode} -> {country} -> GLOBAL" },
          "decision": { "type": "string", "enum": ["APPROVE", "REVIEW", "DECLINE"] },
          "score": { "type": "integer", "minimum": 0, "maximum": 850 },
          "tier": { "type": "string", "enum": ["EXCELLENT", "GOOD", "FAIR", "BELOW_AVERAGE", "POOR"] },
          "confidence": { "type": "string", "enum": ["HIGH", "MEDIUM", "LOW"] },
          "terms": { "$ref": "#/components/schemas/FiDecisionTerms" },
          "firedRules": { "type": "array", "items": { "$ref": "#/components/schemas/FiFiredRule" }, "description": "Rules that matched; empty array when none did" },
          "reasonCodes": { "type": "array", "maxItems": 4, "items": { "$ref": "#/components/schemas/FiReasonCode" } },
          "modelVersion": { "type": "string" },
          "scoredAt": { "type": "string", "example": "2026-08-20 14:32:11" },
          "environment": { "type": "string", "enum": ["LIVE", "SANDBOX"] }
        }
      },
      "FiBatchScoreRequest": {
        "type": "object",
        "required": ["subjects", "purpose"],
        "properties": {
          "subjects": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "type": "string" } },
          "purpose": { "type": "string" }
        }
      },
      "FiBatchScoreResponse": {
        "type": "object",
        "properties": {
          "batchId": { "type": "string", "format": "uuid" },
          "summary": {
            "type": "object",
            "properties": {
              "requested": { "type": "integer" },
              "returned": { "type": "integer" },
              "denied": { "type": "integer" }
            }
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pairwiseSub": { "type": "string" },
                "status": { "type": "string", "enum": ["OK", "DENIED"], "description": "NO_CONSENT / REVOKED / EXPIRED / NOT_FOUND are all reported as the single label DENIED" },
                "score": { "type": "integer", "nullable": true },
                "tier": { "type": "string", "nullable": true },
                "reasonCodes": { "type": "array", "nullable": true, "items": { "$ref": "#/components/schemas/FiReasonCode" } },
                "modelVersion": { "type": "string", "nullable": true },
                "scoredAt": { "type": "string", "nullable": true },
                "decisionHash": { "type": "string", "nullable": true }
              }
            }
          }
        }
      },
      "FiVerifyRequest": {
        "type": "object",
        "description": "At least one of vcId / reputationHash is required.",
        "properties": {
          "vcId": { "type": "string", "nullable": true },
          "reputationHash": { "type": "string", "nullable": true },
          "tierCommitment": { "type": "string", "nullable": true },
          "tier": { "type": "string", "nullable": true }
        }
      },
      "FiVerifyResponse": {
        "type": "object",
        "properties": {
          "valid": { "type": "boolean" },
          "reason": { "type": "string", "enum": ["VALID", "NOT_FOUND", "REVOKED", "EXPIRED", "COMMITMENT_MISMATCH", "TIER_MISMATCH"] },
          "tier": { "type": "string", "nullable": true },
          "status": { "type": "string", "nullable": true, "description": "ISSUED | ANCHORED | ANCHOR_DEFERRED | REVOKED" },
          "issuedAt": { "type": "string", "nullable": true },
          "expiresAt": { "type": "string", "nullable": true, "description": "Attestation validity is 90 days from issuance" },
          "anchoredTxHash": { "type": "string", "nullable": true },
          "modelVersion": { "type": "string", "nullable": true }
        }
      },
      "FiScoreHistoryResponse": {
        "type": "object",
        "properties": {
          "pairwiseSub": { "type": "string" },
          "entries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "score": { "type": "integer" },
                "tier": { "type": "string" },
                "modelVersion": { "type": "string" },
                "scoredAt": { "type": "string" },
                "decisionHash": { "type": "string", "nullable": true },
                "anchorTxHash": { "type": "string", "nullable": true }
              }
            }
          }
        }
      },
      "FiWhoamiResponse": {
        "type": "object",
        "properties": {
          "clientId": { "type": "string" },
          "name": { "type": "string" },
          "environment": { "type": "string", "enum": ["LIVE", "SANDBOX"] },
          "rateLimitTier": { "type": "string" },
          "status": { "type": "string" }
        }
      },
      "FiUsageResponse": {
        "type": "object",
        "properties": {
          "clientId": { "type": "string" },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": { "type": "string", "format": "date" },
                "endpoint": { "type": "string", "enum": ["QUERY", "BATCH", "VERIFY", "ISSUE", "HISTORY", "DECISION"] },
                "callCount": { "type": "integer" },
                "unitCount": { "type": "integer", "description": "Unit weights: score/query=1, batch per subject=1, verify=1, issue=1, history=2, decision=2" }
              }
            }
          }
        }
      },
      "FiConsentRequestRequest": {
        "type": "object",
        "required": ["pairwiseSub", "purpose"],
        "properties": {
          "pairwiseSub": { "type": "string" },
          "purpose": { "type": "string" }
        }
      },
      "FiConsentStatusResponse": {
        "type": "object",
        "properties": {
          "consentReceiptId": { "type": "string" },
          "pairwiseSub": { "type": "string" },
          "clientId": { "type": "string" },
          "purpose": { "type": "string" },
          "scope": { "type": "string" },
          "dataCategories": { "type": "string", "description": "CSV string; default 'score,tier,reason-codes'; optional extensions: contributions, history, monitoring" },
          "status": { "type": "string", "enum": ["ACTIVE", "REVOKED", "EXPIRED"] },
          "grantedAt": { "type": "string" },
          "expiresAt": { "type": "string" }
        }
      },
      "IssuerApplicationRequest": {
        "type": "object",
        "required": ["orgName", "orgType", "contactEmail", "requestedCredentialTypes"],
        "properties": {
          "orgName": { "type": "string", "maxLength": 200, "description": "Institution name; trimmed before storage" },
          "orgType": { "type": "string", "enum": ["GOVERNMENT", "UNIVERSITY", "ASSOCIATION", "COMPANY"] },
          "countryCode": { "type": "string", "pattern": "^[A-Za-z]{2}$", "nullable": true, "description": "Two letters; stored uppercased" },
          "contactEmail": { "type": "string", "format": "email", "maxLength": 255, "description": "Stored lowercased" },
          "requestedCredentialTypes": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "description": "Credential types this issuer may later issue. Duplicates are rejected; the JSON-encoded array must stay within 500 characters.",
            "items": { "type": "string", "maxLength": 100, "pattern": "^[A-Za-z][A-Za-z0-9_.:-]*$" }
          },
          "purposeNote": { "type": "string", "maxLength": 1000, "nullable": true }
        }
      },
      "IssuerApplicationReceipt": {
        "type": "object",
        "properties": {
          "id": { "type": "integer", "format": "int64", "description": "Application receipt id" }
        }
      },
      "IssuerCredentialIssueRequest": {
        "type": "object",
        "required": ["holderDid", "credentialType", "claims"],
        "properties": {
          "holderDid": { "type": "string", "maxLength": 255, "description": "Holder DID address (did:sw:0x...); must exist and be ACTIVE" },
          "credentialType": { "type": "string", "maxLength": 100, "pattern": "^[A-Za-z][A-Za-z0-9_.:-]*$", "description": "Must be one of the approved requestedCredentialTypes" },
          "claims": {
            "type": "object",
            "maxProperties": 20,
            "description": "String-to-string claims. Claim names max 100 chars, values max 500 chars and never null. The server overwrites the reserved names credentialType and delegatedIssuer.",
            "additionalProperties": { "type": "string", "maxLength": 500 }
          },
          "validityDays": { "type": "integer", "minimum": 1, "maximum": 3650, "nullable": true, "description": "Defaults server-side to 365 days when omitted" }
        }
      },
      "IssuerIssuanceResponse": {
        "type": "object",
        "properties": {
          "credentialId": { "type": "integer", "format": "int64" }
        }
      },
      "IssuerCredentialSummary": {
        "type": "object",
        "properties": {
          "credentialId": { "type": "integer", "format": "int64" },
          "credentialUid": { "type": "string" },
          "holderDid": { "type": "string" },
          "credentialType": { "type": "string" },
          "status": { "type": "string", "description": "Credential status, e.g. ACTIVE, PENDING_ANCHOR, ANCHOR_DEFERRED, SUSPENDED, REVOKED, EXPIRED" },
          "issuedAt": { "type": "string", "format": "date-time", "description": "ISO-8601 (this surface does not use the FI 'yyyy-MM-dd HH:mm:ss' format)" },
          "expiresAt": { "type": "string", "format": "date-time", "nullable": true }
        }
      },
      "IssuerCredentialPage": {
        "type": "object",
        "properties": {
          "page": { "type": "integer", "description": "Zero-based page index" },
          "size": { "type": "integer", "description": "Fixed page size of 20" },
          "totalElements": { "type": "integer", "format": "int64" },
          "totalPages": { "type": "integer", "description": "0 when this issuer has no credentials" },
          "content": { "type": "array", "items": { "$ref": "#/components/schemas/IssuerCredentialSummary" } }
        }
      },
      "IssuerWhoamiResponse": {
        "type": "object",
        "properties": {
          "applicationId": { "type": "integer", "format": "int64" },
          "clientId": { "type": "string" },
          "orgName": { "type": "string" },
          "orgType": { "type": "string", "enum": ["GOVERNMENT", "UNIVERSITY", "ASSOCIATION", "COMPANY"] },
          "countryCode": { "type": "string", "nullable": true },
          "contactEmail": { "type": "string" },
          "issuerDid": { "type": "string", "nullable": true, "description": "null until an operator has registered the issuer DID in the trust registry and linked it to the application; delegated issuance stays disabled while null" },
          "status": { "type": "string", "enum": ["APPLIED", "APPROVED", "REJECTED", "SUSPENDED"] },
          "credentialTypes": { "type": "array", "items": { "type": "string" }, "description": "The complete set of types this issuer may issue" }
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded (60 req/min). Retry after the number of seconds in the Retry-After header.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer", "minimum": 1 }, "description": "Seconds to wait before retrying" }
        }
      }
    }
  },
  "paths": {
    "/fi/score/query": {
      "post": {
        "summary": "Query a consented credit score (soft-pull)",
        "operationId": "queryScore",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FiScoreQueryRequest" } } }
        },
        "responses": {
          "200": { "description": "Score retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiScoreResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/decision/evaluate": {
      "post": {
        "summary": "Evaluate policy rules and product terms for a consented subject",
        "description": "Reuses the consent-gated score path (identical consent and access-log rules to /fi/score/query), then applies the client's configured policy rules and terms matrix. Evaluation order: (1) consent check and score retrieval, (2) segment chain {country}:{productCode} -> {country} -> GLOBAL, (3) KNOCKOUT rules - the first match returns DECLINE immediately with no terms, (4) REVIEW rules - every match is accumulated into firedRules, (5) terms matrix - per segment the client-specific row wins over the shared row, matching on minScore <= score <= maxScore and confidence >= minConfidence (LOW < MEDIUM < HIGH), (6) when the matrix is not configured or nothing matches the result is REVIEW with no terms (never an implicit approval), (7) the matched row's decision applies, except that an APPROVE row is downgraded to REVIEW when any REVIEW rule fired. SANDBOX clients get a synthetic score with the client's real configured rules and terms; a synthetic score carries no country context, so only the GLOBAL segment applies. Metered in the DECISION usage bucket at 2 units per call.",
        "operationId": "evaluateDecision",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FiDecisionRequest" } } }
        },
        "responses": {
          "200": { "description": "Decision evaluated", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiDecisionResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/score/batch": {
      "post": {
        "summary": "Batch score query (1-100 subjects)",
        "operationId": "queryScoreBatch",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FiBatchScoreRequest" } } }
        },
        "responses": {
          "200": { "description": "Batch completed; per-subject consent checks, denied subjects reported with the single label DENIED", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiBatchScoreResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/verify": {
      "post": {
        "summary": "Verify a presented score attestation (consent-free)",
        "operationId": "verifyAttestation",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FiVerifyRequest" } } }
        },
        "responses": {
          "200": { "description": "Verification completed; a non-genuine credential is a 200 with valid=false", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiVerifyResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/score/history": {
      "get": {
        "summary": "Score history (requires the 'history' data category on the consent; window up to 365 days)",
        "operationId": "getScoreHistory",
        "parameters": [
          { "name": "pairwiseSub", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "fromDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date" }, "description": "Defaults server-side when omitted" },
          { "name": "toDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date" }, "description": "Defaults server-side when omitted" }
        ],
        "responses": {
          "200": { "description": "History retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiScoreHistoryResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/whoami": {
      "get": {
        "summary": "Identify the authenticated client (auth only, no consent required)",
        "operationId": "whoami",
        "responses": {
          "200": { "description": "Client identity", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiWhoamiResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/usage": {
      "get": {
        "summary": "Self-service usage metering (window up to 92 days)",
        "operationId": "getUsage",
        "parameters": [
          { "name": "fromDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date" }, "description": "Defaults server-side when omitted" },
          { "name": "toDate", "in": "query", "required": false, "schema": { "type": "string", "format": "date" }, "description": "Defaults server-side when omitted" }
        ],
        "responses": {
          "200": { "description": "Usage retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiUsageResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/consent/requests": {
      "post": {
        "summary": "Request a user's score-sharing consent (in-app push)",
        "operationId": "requestConsent",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FiConsentRequestRequest" } } }
        },
        "responses": {
          "200": { "description": "Consent request sent", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StandardResponse" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/consent/{consentReceiptId}": {
      "get": {
        "summary": "Get consent status by receipt id",
        "operationId": "getConsentStatus",
        "parameters": [
          { "name": "consentReceiptId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Consent status retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/FiConsentStatusResponse" } } }] } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/fi/health": {
      "get": {
        "summary": "FI API health (public, no PII)",
        "operationId": "health",
        "security": [],
        "responses": {
          "200": { "description": "OK", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/StandardResponse" } } } }
        }
      }
    },
    "/public/issuer-onboarding/apply": {
      "post": {
        "summary": "Apply to become a credential issuer (public, no authentication)",
        "description": "Open intake for institutions (ministry, university, association). Returns a receipt id; the outcome is communicated to contactEmail. Approval creates a partner client with the credential:issue scope and delivers the plaintext API key exactly once - it is stored hashed and can never be read back. Approval alone does NOT enable issuance: an operator must additionally register the issuer DID and public JWK in the trust registry (VERIFIED trust for GOVERNMENT, PROVISIONAL otherwise) and link that DID to the application. Rate limited at 60 req/min per calling IP.",
        "operationId": "applyIssuerOnboarding",
        "security": [],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IssuerApplicationRequest" } } }
        },
        "responses": {
          "200": { "description": "Application received", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IssuerApplicationReceipt" } } }] } } } },
          "400": { "description": "Validation failed (SW1001) - format, length, item count, duplicate credential type or malformed email" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/issuer/credentials/issue": {
      "post": {
        "summary": "Issue a credential under the institution's own name (delegated issuance)",
        "description": "Fail-closed. The call is rejected unless every condition holds: the client carries the credential:issue scope, is linked to an APPROVED application, the requested credentialType is in that application's approved list, the issuer DID is registered AND trusted in the issuer registry, and the holder DID exists and is ACTIVE. A missing or untrusted issuer DID returns SW1701 (registration is a manual operator step) - retrying does not help. The server sets the reserved claims credentialType and delegatedIssuer (the institution's issuer DID), overwriting any same-named claim in the request. No idempotency key exists: a repeated request issues a second credential. Successful issuance is metered at request time in the ISSUE usage bucket at 1 unit per call; a metering failure never rolls back the issuance.",
        "operationId": "issueDelegatedCredential",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/IssuerCredentialIssueRequest" } } }
        },
        "responses": {
          "200": { "description": "Credential issued", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IssuerIssuanceResponse" } } }] } } } },
          "400": { "description": "Validation failed (SW1001)" },
          "401": { "description": "Missing or invalid X-Client-Id / X-API-Key (SW2102)" },
          "403": { "description": "Client suspended (SW2101); missing credential:issue scope, unlinked or non-APPROVED application, or unapproved credentialType (SW2304); holder DID not active (SW1403)" },
          "404": { "description": "Holder DID not found (SW1402) or no civil record linked to it (SW1002)" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "description": "Issuer DID not registered or not trusted in the issuer registry (SW1701) - delegated issuance stays disabled until an operator completes registration" }
        }
      }
    },
    "/issuer/credentials": {
      "get": {
        "summary": "List credentials issued under this institution's name",
        "description": "Returns only credentials whose delegatedIssuer claim matches this institution's issuer DID. Fixed page size of 20; a page beyond the range returns empty content. Requires the credential:issue scope, an APPROVED application and a registered issuer DID.",
        "operationId": "listIssuerCredentials",
        "parameters": [
          { "name": "page", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "Zero-based page index; a negative value returns SW1001" }
        ],
        "responses": {
          "200": { "description": "Issuer credentials retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IssuerCredentialPage" } } }] } } } },
          "401": { "description": "Missing or invalid X-Client-Id / X-API-Key (SW2102)" },
          "403": { "description": "Client suspended (SW2101) or not authorized for credential:issue / not an APPROVED issuer (SW2304)" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "description": "Issuer DID not registered or not trusted (SW1701)" }
        }
      }
    },
    "/issuer/whoami": {
      "get": {
        "summary": "Identify the authenticated issuer and its approved credential types",
        "description": "Callable before the issuer DID is registered - issuerDid is then null, which is how an institution checks whether the manual registration step is complete. Requires the credential:issue scope and an APPROVED application.",
        "operationId": "issuerWhoami",
        "responses": {
          "200": { "description": "Issuer information retrieved", "content": { "application/json": { "schema": { "allOf": [{ "$ref": "#/components/schemas/StandardResponse" }, { "type": "object", "properties": { "data": { "$ref": "#/components/schemas/IssuerWhoamiResponse" } } }] } } } },
          "401": { "description": "Missing or invalid X-Client-Id / X-API-Key (SW2102)" },
          "403": { "description": "Client suspended (SW2101) or not authorized for credential:issue / not an APPROVED issuer (SW2304)" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  }
}
