{
	"openapi": "3.0.0",
	"info": {
		"title": "Kamoa Identity API",
		"version": "1.0.0",
		"description": "Resolves a Congolese MSISDN to the identity attributes the mobile operator holds, so a platform can prefill an onboarding form instead of asking the citizen to key in and prove every attribute.\n\nTwo endpoints. `/v1/identity/lookup` returns the attributes themselves; `/v1/identity/match-score` returns how confidently the number maps to a real, stable identity, without disclosing the record. Score first when you only need to decide whether to trust the number — it discloses no personal data.\n\n**This is a stub.** Responses come from a fixed catalogue of reserved test numbers, not from live operator data — the contract, the authentication, the validation and the error catalogue are real; the identities are fictional. Every reserved number and what it returns is on the test-numbers page.\n\nIngress uses standard TLS (HTTPS) — no client certificate is needed or accepted."
	},
	"components": {
		"schemas": {
			"LookupResponse": {
				"type": "object",
				"properties": {
					"matchStatus": {
						"type": "string",
						"enum": [
							"FOUND",
							"PARTIAL",
							"NOT_FOUND"
						]
					},
					"firstName": {
						"type": "string",
						"nullable": true
					},
					"lastName": {
						"type": "string",
						"nullable": true
					},
					"otherNames": {
						"type": "string",
						"nullable": true
					},
					"nationality": {
						"type": "string",
						"nullable": true
					},
					"dateOfBirth": {
						"type": "string",
						"nullable": true,
						"example": "1990-06-22"
					},
					"idDocumentType": {
						"type": "string",
						"nullable": true,
						"enum": [
							"CNI",
							"PASSEPORT",
							"CARTE_SEJOUR",
							"PERMIS_CONDUIRE",
							"AUTRE",
							null
						]
					},
					"idDocumentNumber": {
						"type": "string",
						"nullable": true
					},
					"expiryDate": {
						"type": "string",
						"nullable": true
					},
					"operator": {
						"type": "string",
						"enum": [
							"MTN",
							"AIRTEL"
						]
					},
					"recordUpdatedAt": {
						"type": "string"
					}
				},
				"required": [
					"matchStatus",
					"firstName",
					"lastName",
					"otherNames",
					"nationality",
					"dateOfBirth",
					"idDocumentType",
					"idDocumentNumber",
					"expiryDate",
					"operator",
					"recordUpdatedAt"
				]
			},
			"Error": {
				"type": "object",
				"properties": {
					"error_code": {
						"type": "string",
						"example": "INVALID_MSISDN"
					},
					"error_message": {
						"type": "string"
					}
				},
				"required": [
					"error_code",
					"error_message"
				]
			},
			"LookupRequest": {
				"type": "object",
				"properties": {
					"msisdn": {
						"type": "string",
						"example": "+242060000000"
					},
					"consentReference": {
						"type": "string",
						"minLength": 1,
						"example": "otp-7f31c9e2-4b10-4a5e-9c33-6d1e2f8a0b74"
					}
				},
				"required": [
					"msisdn"
				]
			},
			"MatchScoreResponse": {
				"type": "object",
				"properties": {
					"score_output": {
						"$ref": "#/components/schemas/ScoreOutput"
					},
					"action": {
						"$ref": "#/components/schemas/Action"
					},
					"errors": {
						"type": "array",
						"items": {
							"$ref": "#/components/schemas/ScoreError"
						}
					}
				},
				"required": [
					"score_output",
					"action",
					"errors"
				]
			},
			"ScoreOutput": {
				"type": "object",
				"properties": {
					"entity_id": {
						"type": "string",
						"nullable": true
					},
					"identity_confidence_score": {
						"type": "integer",
						"minimum": 0,
						"maximum": 100
					},
					"identity_confidence_tier": {
						"type": "integer",
						"minimum": 1,
						"maximum": 3
					},
					"onboarding_verification": {
						"type": "string",
						"nullable": true,
						"enum": [
							"STRONG_VIDEO_KYC",
							"MEDIA_KYC",
							"LEGACY_KYC",
							null
						]
					},
					"personal_info": {
						"$ref": "#/components/schemas/PersonalInfo"
					},
					"device_info": {
						"$ref": "#/components/schemas/DeviceInfo"
					},
					"location_info": {
						"$ref": "#/components/schemas/LocationInfo"
					}
				},
				"required": [
					"entity_id",
					"identity_confidence_score",
					"identity_confidence_tier",
					"onboarding_verification",
					"personal_info",
					"device_info",
					"location_info"
				]
			},
			"PersonalInfo": {
				"type": "object",
				"properties": {
					"operator_record_found": {
						"type": "boolean"
					},
					"operator_record_status": {
						"type": "string",
						"enum": [
							"ACTIVE",
							"PENDING_SYNC",
							"NOT_FOUND"
						]
					},
					"name_match_status": {
						"type": "string",
						"nullable": true
					},
					"name_inversion_detected": {
						"type": "boolean",
						"nullable": true
					},
					"dob_match_status": {
						"type": "string",
						"nullable": true
					},
					"shared_universal_id_flag": {
						"type": "boolean",
						"nullable": true
					},
					"shared_id_cluster_size": {
						"type": "integer"
					}
				},
				"required": [
					"operator_record_found",
					"operator_record_status",
					"shared_universal_id_flag"
				]
			},
			"DeviceInfo": {
				"type": "object",
				"nullable": true,
				"properties": {
					"imei_match_status": {
						"type": "string",
						"enum": [
							"MATCH",
							"NO_MATCH"
						]
					},
					"device_sharing_level": {
						"type": "string",
						"enum": [
							"LOW",
							"HIGH"
						]
					},
					"distinct_devices_90d": {
						"type": "integer"
					}
				},
				"required": [
					"imei_match_status",
					"device_sharing_level",
					"distinct_devices_90d"
				]
			},
			"LocationInfo": {
				"type": "object",
				"nullable": true,
				"properties": {
					"location_match_status": {
						"type": "string",
						"enum": [
							"MATCH",
							"PARTIAL",
							"NO_MATCH"
						]
					},
					"nighttime_residency_tower_match": {
						"type": "boolean"
					},
					"mobility_entropy": {
						"type": "string",
						"enum": [
							"STABLE",
							"ERRATIC"
						]
					}
				},
				"required": [
					"location_match_status",
					"nighttime_residency_tower_match",
					"mobility_entropy"
				]
			},
			"Action": {
				"type": "object",
				"properties": {
					"code": {
						"type": "string",
						"nullable": true,
						"enum": [
							"RECORD_NOT_FOUND",
							"RECORD_PENDING_SYNC",
							null
						]
					},
					"message": {
						"type": "string",
						"nullable": true
					}
				},
				"required": [
					"code",
					"message"
				]
			},
			"ScoreError": {
				"type": "object",
				"properties": {
					"error_code": {
						"type": "string",
						"enum": [
							"RECORD_NOT_FOUND",
							"RECORD_PENDING_SYNC"
						]
					},
					"error_message": {
						"type": "string"
					}
				},
				"required": [
					"error_code",
					"error_message"
				]
			},
			"MatchScoreRequest": {
				"type": "object",
				"properties": {
					"msisdn": {
						"type": "string",
						"example": "+242060000000"
					},
					"consentObtained": {
						"type": "boolean",
						"example": true
					}
				},
				"required": [
					"msisdn",
					"consentObtained"
				]
			}
		},
		"parameters": {}
	},
	"paths": {
		"/v1/identity/lookup": {
			"post": {
				"summary": "Resolve a phone number to the subscriber's identity attributes",
				"description": "Returns the KYC record the operator holds for this MSISDN. Every field except `matchStatus`, `operator` and `recordUpdatedAt` is nullable — a `PARTIAL` result is defined as one with a mandatory attribute missing, so treat nulls as expected rather than exceptional.",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"requestBody": {
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/LookupRequest"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Resolved, partially resolved, or no record",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/LookupResponse"
								}
							}
						}
					},
					"400": {
						"description": "Validation failure",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"401": {
						"description": "Missing or invalid bearer token",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"429": {
						"description": "Rate limited — retry after the Retry-After interval",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"500": {
						"description": "Internal error",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"503": {
						"description": "Upstream unavailable",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					}
				}
			}
		},
		"/v1/identity/match-score": {
			"post": {
				"summary": "Score how confidently this MSISDN maps to a real, stable identity",
				"description": "Returns a 0-100 confidence score, a tier from 1 (strongest) to 3 (weakest), and the feature blocks behind them. A number we hold no record for still scores — zero, tier 2, with `action.code` explaining why — so an absent record and a suspicious one stay distinguishable. `device_info` and `location_info` are nullable as whole blocks: a subscriber who roams outside the towers we score against is unscored on location, not scored badly.",
				"security": [
					{
						"bearerAuth": []
					}
				],
				"requestBody": {
					"content": {
						"application/json": {
							"schema": {
								"$ref": "#/components/schemas/MatchScoreRequest"
							}
						}
					}
				},
				"responses": {
					"200": {
						"description": "Scored, including the no-record case",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/MatchScoreResponse"
								}
							}
						}
					},
					"400": {
						"description": "Validation failure",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"401": {
						"description": "Missing or invalid bearer token",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"403": {
						"description": "consentObtained was false",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"429": {
						"description": "Rate limited — retry after the Retry-After interval",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"500": {
						"description": "Internal error",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					},
					"503": {
						"description": "Upstream unavailable",
						"content": {
							"application/json": {
								"schema": {
									"$ref": "#/components/schemas/Error"
								}
							}
						}
					}
				}
			}
		}
	}
}
