{
  "openapi": "3.1.0",
  "info": {
    "title": "TRCN Inter-Agency Verification API",
    "version": "1.0.0",
    "description": "Authenticated, JWS-signed teacher verification API for partner agencies (WAEC, NECO, JAMB, federal ministries, approved employers). Credentials (partner ID + API key and/or mTLS client certificate, with scopes and allowed purposes) are provisioned by TRCN. Every authenticated response is an RS256-signed envelope verifiable against the published JWKS. See https://trcn.gov.ng/developers/partner-api",
    "contact": {
      "name": "TRCN Integrations",
      "url": "https://trcn.gov.ng/contact"
    }
  },
  "servers": [{ "url": "https://api.trcn.gov.ng" }],
  "security": [{ "bearerAuth": [] }, { "apiKeyAuth": [] }],
  "tags": [
    { "name": "Verification", "description": "Eligibility and status lookups" },
    { "name": "Discovery", "description": "Public, unauthenticated endpoints" }
  ],
  "paths": {
    "/v1/inter-agency/eligibility-check": {
      "post": {
        "tags": ["Verification"],
        "summary": "Eligibility check with purpose-of-use",
        "description": "Resolves a teacher and evaluates eligibility for the supplied purpose. Requires the `eligibility` scope and that `purpose` is in the partner's allowed purposes. Supports the `Idempotency-Key` header.",
        "operationId": "eligibilityCheck",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/EligibilityCheckRequest" }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/SignedEnvelope" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/inter-agency/lookup/by-reg-no/{regNo}": {
      "get": {
        "tags": ["Verification"],
        "summary": "Lookup by TRCN registration number",
        "description": "Requires the `lookup_by_reg_no` scope. URL-encode the slashes in the registration number.",
        "operationId": "lookupByRegNo",
        "parameters": [
          {
            "name": "regNo",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "example": "TRCN/ABJ/2024/123456"
          }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/SignedEnvelope" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/inter-agency/lookup/by-tid/{tId}": {
      "get": {
        "tags": ["Verification"],
        "summary": "Lookup by numeric Teacher ID",
        "description": "Requires the `lookup_by_tid` scope. A non-numeric tId returns 400.",
        "operationId": "lookupByTid",
        "parameters": [
          {
            "name": "tId",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "example": 1234567
          }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/SignedEnvelope" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/inter-agency/lookup/by-nin": {
      "post": {
        "tags": ["Verification"],
        "summary": "Lookup by NIN (in the request body)",
        "description": "Requires the `lookup_by_nin` scope. The 11-digit NIN is sent in the body, never the URL, so it never appears in access logs (NIMC Act s.26 / NDPA s.30).",
        "operationId": "lookupByNin",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["nin"],
                "properties": {
                  "nin": {
                    "type": "string",
                    "pattern": "^\\d{11}$",
                    "example": "12345678901"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/SignedEnvelope" },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/inter-agency/batch": {
      "post": {
        "tags": ["Verification"],
        "summary": "Batch eligibility check (max 100 items)",
        "description": "Requires the `batch` scope. Returns one signed envelope per item. Supports the `Idempotency-Key` header.",
        "operationId": "batchCheck",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BatchCheckRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item signed envelopes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": { "type": "integer" },
                    "results": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/SignedEnvelope" }
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/inter-agency/.well-known/jwks.json": {
      "get": {
        "tags": ["Discovery"],
        "summary": "JSON Web Key Set for response-signature verification",
        "operationId": "getJwks",
        "security": [],
        "responses": {
          "200": {
            "description": "Public keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "keys": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "kty": { "type": "string" },
                          "use": { "type": "string", "example": "sig" },
                          "alg": { "type": "string", "example": "RS256" },
                          "kid": { "type": "string", "example": "trcn-ia-2026-06" },
                          "n": { "type": "string" },
                          "e": { "type": "string" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/inter-agency/health": {
      "get": {
        "tags": ["Discovery"],
        "summary": "Liveness probe (public)",
        "operationId": "getHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "Service status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": { "type": "string", "example": "ok" },
                    "service": { "type": "string", "example": "inter-agency-verification" },
                    "issuer": { "type": "string", "example": "https://api.trcn.gov.ng" },
                    "activeKid": { "type": "string", "example": "trcn-ia-2026-06" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "statusChange": {
      "post": {
        "summary": "Teacher status-change event",
        "description": "TRCN POSTs this to your subscribed endpoint when a verified teacher's license or certificate status changes. Verify the `X-TRCN-Signature` HMAC-SHA256 header over `${t}.${rawBody}` using your whsec_ secret; reject events older than 5 minutes.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookEventEnvelope" }
            }
          }
        },
        "parameters": [
          { "name": "X-TRCN-Signature", "in": "header", "schema": { "type": "string" }, "example": "t=1749291731,v1=3a9f...c12e" },
          { "name": "X-TRCN-Event-Id", "in": "header", "schema": { "type": "string" } },
          { "name": "X-TRCN-Event-Type", "in": "header", "schema": { "type": "string" } },
          { "name": "X-TRCN-Delivery-Id", "in": "header", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Acknowledged (any 2xx). Non-2xx is retried with exponential back-off, then dead-lettered." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "Authorization: Bearer <api key>" },
      "apiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-trcn-api-key" }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": { "type": "string" },
        "description": "Unique per logical request. A retry with the same key and same body replays the original response; reuse with a different body is rejected."
      }
    },
    "schemas": {
      "Identifier": {
        "type": "object",
        "description": "Exactly one of regNo, tId, or nin.",
        "properties": {
          "regNo": { "type": "string", "example": "TRCN/ABJ/2024/123456" },
          "tId": { "type": "integer", "example": 1234567 },
          "nin": { "type": "string", "pattern": "^\\d{11}$" }
        }
      },
      "EligibilityCheckRequest": {
        "type": "object",
        "required": ["identifier", "purpose"],
        "properties": {
          "identifier": { "$ref": "#/components/schemas/Identifier" },
          "purpose": { "$ref": "#/components/schemas/VerificationPurpose" },
          "context": {
            "type": "object",
            "properties": {
              "examBatch": { "type": "string" },
              "employerName": { "type": "string" },
              "correlationId": { "type": "string" }
            }
          }
        }
      },
      "BatchCheckRequest": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": { "$ref": "#/components/schemas/EligibilityCheckRequest" }
          }
        }
      },
      "VerificationPurpose": {
        "type": "string",
        "enum": [
          "WAEC_INVIGILATOR_ELIGIBILITY",
          "WAEC_EXAMINER_CREDENTIAL_CHECK",
          "NECO_INVIGILATOR_ELIGIBILITY",
          "NECO_EXAMINER_CREDENTIAL_CHECK",
          "JAMB_CBT_STAFF_VERIFICATION",
          "JAMB_INVIGILATOR_ELIGIBILITY",
          "JAMB_ENDORSEMENT_ELIGIBILITY",
          "SCHOOL_EMPLOYMENT_VERIFICATION",
          "EMPLOYER_BACKGROUND_CHECK",
          "GENERAL_PUBLIC_VERIFICATION"
        ]
      },
      "EligibilityResult": {
        "type": "object",
        "properties": {
          "found": { "type": "boolean" },
          "source": { "type": ["string", "null"], "enum": ["new_system", "legacy", null] },
          "status": { "type": "string", "enum": ["VALID", "EXPIRED", "NOT_FOUND", "REVOKED"] },
          "eligible": { "type": "boolean" },
          "reasons": { "type": "array", "items": { "type": "string" } },
          "teacher": {
            "type": ["object", "null"],
            "properties": {
              "name": { "type": "string" },
              "tId": { "type": ["integer", "null"] },
              "state": { "type": ["string", "null"] },
              "category": { "type": ["string", "null"] }
            }
          },
          "license": {
            "type": ["object", "null"],
            "properties": {
              "status": { "type": "string", "enum": ["VALID", "EXPIRED", "NOT_FOUND", "REVOKED"] },
              "issueDate": { "type": ["string", "null"] },
              "expiryDate": { "type": ["string", "null"] },
              "daysRemaining": { "type": ["integer", "null"] }
            }
          },
          "certificate": {
            "type": ["object", "null"],
            "properties": {
              "status": { "type": "string", "enum": ["VERIFIED", "NOT_FOUND", "CLAIMED", "LEGACY_RECORD"] },
              "regNo": { "type": ["string", "null"] },
              "issueDate": { "type": ["string", "null"] }
            }
          },
          "profile": {
            "type": ["object", "null"],
            "description": "Full federal profile for new-system lookups. Raw NIN is never present — only ninVerified."
          }
        }
      },
      "SignedEnvelope": {
        "type": "object",
        "description": "result is also encoded inside the RS256 JWS `signature`. Verify the signature and trust the signed copy.",
        "properties": {
          "verificationId": { "type": "string", "example": "ver_7f3a1c9e8b2d4f60" },
          "verifiedAt": { "type": "string", "format": "date-time" },
          "issuer": { "type": "string", "example": "https://api.trcn.gov.ng" },
          "audience": { "type": "string", "description": "Your partner ID", "example": "waec" },
          "result": { "$ref": "#/components/schemas/EligibilityResult" },
          "signature": { "type": "string", "description": "Compact RS256 JWS (JWT). Claims: iss, aud, sub (verificationId), iat, result." },
          "signatureKid": { "type": "string", "example": "trcn-ia-2026-06" }
        }
      },
      "WebhookEventEnvelope": {
        "type": "object",
        "properties": {
          "eventId": { "type": "string" },
          "eventType": {
            "type": "string",
            "enum": [
              "license.verified",
              "license.expired",
              "license.revoked",
              "license.reinstated",
              "certificate.verified",
              "certificate.revoked",
              "teacher.deactivated"
            ]
          },
          "occurredAt": { "type": "string", "format": "date-time" },
          "data": {
            "type": "object",
            "description": "No raw PII — public identifiers and status strings only.",
            "properties": {
              "tId": { "type": ["integer", "null"] },
              "regNo": { "type": ["string", "null"] },
              "licenseStatus": { "type": "string", "enum": ["VALID", "EXPIRED", "NOT_FOUND", "REVOKED"] },
              "certificateStatus": { "type": "string", "enum": ["VERIFIED", "NOT_FOUND", "CLAIMED", "LEGACY_RECORD"] },
              "previousStatus": { "type": "string" }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "statusCode": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "RateLimitError": {
        "type": "object",
        "properties": {
          "statusCode": { "type": "integer", "example": 429 },
          "message": { "type": "string" },
          "retryAfterSeconds": { "type": "integer", "example": 23 }
        }
      }
    },
    "responses": {
      "SignedEnvelope": {
        "description": "Signed verification result",
        "headers": {
          "X-Verification-Id": { "schema": { "type": "string" } },
          "X-Signature-Kid": { "schema": { "type": "string" } },
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset": { "schema": { "type": "integer" }, "description": "Unix epoch seconds" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/SignedEnvelope" }
          }
        }
      },
      "BadRequest": {
        "description": "Malformed request",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "No valid credential (mTLS cert, Bearer key, or x-trcn-api-key)",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Missing scope for the endpoint, or purpose not in allowed purposes",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Per-minute or per-day quota exceeded",
        "headers": {
          "X-RateLimit-Limit": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RateLimitError" } } }
      }
    }
  }
}
