{
  "openapi": "3.1.0",
  "info": {
    "title": "TwinKYC Anchor API",
    "version": "1.2.0",
    "description": "API para anclar hashes de verificaciones KYC en una red EVM privada. Nunca se envian ni almacenan datos personales: solo referencias opacas (subject_ref) y hashes (payload_hash)."
  },
  "servers": [
    {
      "url": "https://kyc.twinbot.io/api/public/v1",
      "description": "Produccion"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": [],
      "Timestamp": [],
      "Signature": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key"
      },
      "Timestamp": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Timestamp"
      },
      "Signature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Signature"
      }
    },
    "schemas": {
      "Bytes32": {
        "type": "string",
        "pattern": "^0x[0-9a-fA-F]{64}$",
        "example": "0xabababababababababababababababababababababababababababababababab"
      },
      "StampRequest": {
        "type": "object",
        "required": [
          "subject_ref",
          "payload_hash"
        ],
        "properties": {
          "subject_ref": {
            "$ref": "#/components/schemas/Bytes32"
          },
          "payload_hash": {
            "$ref": "#/components/schemas/Bytes32"
          },
          "meta_hash": {
            "$ref": "#/components/schemas/Bytes32"
          },
          "kyc_level": {
            "type": "integer",
            "minimum": 0,
            "maximum": 255,
            "default": 1
          },
          "country_code": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "provider": {
            "type": "string"
          },
          "verified_at": {
            "type": "string",
            "format": "date-time"
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128
          }
        }
      },
      "Anchor": {
        "type": "object",
        "properties": {
          "subject_ref": {
            "$ref": "#/components/schemas/Bytes32"
          },
          "payload_hash": {
            "$ref": "#/components/schemas/Bytes32"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "submitted",
              "confirmed",
              "failed",
              "revoked"
            ]
          },
          "tx_hash": {
            "type": "string",
            "nullable": true
          },
          "block_number": {
            "type": "integer",
            "nullable": true
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Estado del servicio y de la cadena",
        "security": [],
        "tags": [
          "Sistema"
        ],
        "responses": {
          "200": {
            "description": "Estado del sistema"
          }
        }
      }
    },
    "/stats": {
      "get": {
        "summary": "Metricas agregadas de anclajes",
        "tags": [
          "Sistema"
        ],
        "responses": {
          "200": {
            "description": "Totales por estado, ultimas 24h y 7d"
          }
        }
      }
    },
    "/contract": {
      "get": {
        "summary": "Contrato y red activos (opcionalmente el ABI)",
        "tags": [
          "Sistema"
        ],
        "parameters": [
          {
            "name": "abi",
            "in": "query",
            "schema": {
              "type": "boolean"
            },
            "description": "Incluye el ABI completo"
          }
        ],
        "responses": {
          "200": {
            "description": "Datos del contrato"
          },
          "503": {
            "description": "Sin contrato activo"
          }
        }
      }
    },
    "/kyc/stamp": {
      "post": {
        "summary": "Encola el anclaje de un KYC",
        "tags": [
          "KYC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StampRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          },
          "401": {
            "description": "Firma invalida"
          },
          "422": {
            "description": "Payload invalido"
          }
        }
      }
    },
    "/kyc/bulk-stamp": {
      "post": {
        "summary": "Encola hasta 200 anclajes en una sola llamada",
        "tags": [
          "KYC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "maxItems": 200,
                    "items": {
                      "$ref": "#/components/schemas/StampRequest"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Resultado por item"
          }
        }
      }
    },
    "/kyc/revoke": {
      "post": {
        "summary": "Revoca un anclaje existente",
        "tags": [
          "KYC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "reason_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Revocacion encolada"
          }
        }
      }
    },
    "/kyc/verify": {
      "post": {
        "summary": "Comprueba si un payload_hash coincide con lo anclado on-chain",
        "tags": [
          "KYC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "payload_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "payload_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ valid, stamped_at, contract, chain_id }"
          }
        }
      }
    },
    "/kyc/bulk-status": {
      "post": {
        "summary": "Estado de hasta 500 subject_ref en una llamada",
        "tags": [
          "KYC"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_refs"
                ],
                "properties": {
                  "subject_refs": {
                    "type": "array",
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/Bytes32"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista de anclajes por referencia"
          }
        }
      }
    },
    "/kyc/{subject_ref}": {
      "get": {
        "summary": "Historial de anclajes de una referencia",
        "tags": [
          "KYC"
        ],
        "parameters": [
          {
            "name": "subject_ref",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Bytes32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Anclajes"
          },
          "404": {
            "description": "No encontrado"
          }
        }
      }
    },
    "/kyc/{subject_ref}/proof": {
      "get": {
        "summary": "Prueba Merkle de inclusion",
        "tags": [
          "KYC"
        ],
        "parameters": [
          {
            "name": "subject_ref",
            "in": "path",
            "required": true,
            "schema": {
              "$ref": "#/components/schemas/Bytes32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "leaf_hash, leaf_index, proof[], batch"
          }
        }
      }
    },
    "/kyc/level": {
      "post": {
        "summary": "Cambiar nivel KYC (upgrade/downgrade)",
        "description": "Encola un cambio de nivel on-chain. No acepta ni devuelve datos personales: solo la referencia opaca.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "kyc_level"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "kyc_level": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255
                  },
                  "reason_code": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535,
                    "default": 0
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "kyc_level": 3,
                "reason_code": 10
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Cambio de nivel encolado",
            "content": {
              "application/json": {
                "example": {
                  "anchor_id": "6f6d0a0e-1f6c-4a1b-9a3d-6c1c0f2b7f11",
                  "status": "level_change_queued",
                  "new_level": 3
                }
              }
            }
          },
          "404": {
            "description": "subject_ref desconocido"
          },
          "422": {
            "description": "Payload invalido"
          },
          "429": {
            "description": "Limite de tasa excedido"
          }
        }
      }
    },
    "/kyc/freeze": {
      "post": {
        "summary": "Congelar o descongelar un anclaje",
        "description": "Bloquea temporalmente la validez del anclaje sin revocarlo (p. ej. investigacion AML abierta).",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "frozen"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "frozen": {
                    "type": "boolean"
                  },
                  "reason_code": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535,
                    "default": 0
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "frozen": true,
                "reason_code": 42
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Operacion encolada",
            "content": {
              "application/json": {
                "example": {
                  "anchor_id": "6f6d0a0e-1f6c-4a1b-9a3d-6c1c0f2b7f11",
                  "status": "freeze_queued"
                }
              }
            }
          },
          "404": {
            "description": "subject_ref desconocido"
          },
          "422": {
            "description": "Payload invalido"
          }
        }
      }
    },
    "/kyc/extend": {
      "post": {
        "summary": "Extender la vigencia de un anclaje",
        "description": "Actualiza expiresAt on-chain. Util para renovaciones periodicas de KYC.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "expires_at"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "expires_at": "2027-01-31T00:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Extension encolada",
            "content": {
              "application/json": {
                "example": {
                  "anchor_id": "6f6d0a0e-1f6c-4a1b-9a3d-6c1c0f2b7f11",
                  "status": "extend_queued",
                  "expires_at": "2027-01-31T00:00:00.000Z"
                }
              }
            }
          },
          "404": {
            "description": "subject_ref desconocido"
          }
        }
      }
    },
    "/kyc/expiring": {
      "get": {
        "summary": "Anclajes proximos a vencer",
        "description": "Devuelve unicamente referencias opacas, nivel y fecha de vencimiento.",
        "tags": [
          "Cumplimiento"
        ],
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 30,
              "minimum": 1,
              "maximum": 365
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "minimum": 1,
              "maximum": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Listado de referencias opacas por vencer",
            "content": {
              "application/json": {
                "example": {
                  "days": 30,
                  "count": 1,
                  "items": [
                    {
                      "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                      "kyc_level": 2,
                      "expires_at": "2026-09-01T00:00:00.000Z",
                      "status": "confirmed"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/compliance/report": {
      "get": {
        "summary": "Reporte agregado de cumplimiento (sin datos personales)",
        "description": "Conteos agregados por estado, nivel, pais, jurisdiccion, riesgo y proveedor, mas la politica y el contrato vigentes. Nunca incluye datos personales.",
        "tags": [
          "Cumplimiento"
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Por defecto 30 dias atras"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Por defecto ahora"
          }
        ],
        "responses": {
          "200": {
            "description": "Conteos por estado, nivel, pais, jurisdiccion y riesgo",
            "content": {
              "application/json": {
                "example": {
                  "period": {
                    "from": "2026-07-06T00:00:00.000Z",
                    "to": "2026-08-05T00:00:00.000Z"
                  },
                  "generated_at": "2026-08-05T22:00:00.000Z",
                  "privacy_notice": "Reporte agregado. No contiene datos personales: solo conteos, hashes opacos y codigos.",
                  "totals": {
                    "records": 128,
                    "by_status": {
                      "confirmed": 120,
                      "pending": 5,
                      "revoked": 3
                    },
                    "by_level": {
                      "1": 40,
                      "2": 70,
                      "3": 18
                    },
                    "by_country": {
                      "MX": 90,
                      "US": 30,
                      "desconocido": 8
                    },
                    "by_jurisdiction_code": {
                      "484": 90,
                      "840": 30
                    },
                    "by_risk_bucket": {
                      "100": 88,
                      "600": 40
                    },
                    "by_provider": {
                      "sumsub": 128
                    }
                  },
                  "expiring_30d": 12,
                  "gas_used_total": 4210000,
                  "policy": {
                    "version": 3,
                    "policy_hash": "0xcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd",
                    "anchored_at": "2026-07-01T10:00:00.000Z"
                  },
                  "contract": {
                    "address": "0x1234abcd...",
                    "version": "2",
                    "deploy_block": 91234
                  }
                }
              }
            }
          }
        }
      }
    },
    "/kyc/screening": {
      "post": {
        "summary": "Atestiguar screening AML (sanciones, PEP, adverse media)",
        "description": "Ancla el hash del resultado del screening junto con codigos numericos agregados. Nunca se envian nombres ni listas: solo hashes y codigos.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "screening_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "screening_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "sanctions_result": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535,
                    "default": 0,
                    "description": "0 = limpio"
                  },
                  "pep_tier": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535,
                    "default": 0
                  },
                  "aml_risk": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255,
                    "default": 0
                  },
                  "screened_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "screening_hash": "0xabababababababababababababababababababababababababababababababab",
                "sanctions_result": 0,
                "pep_tier": 0,
                "aml_risk": 12
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado",
            "content": {
              "application/json": {
                "example": {
                  "anchor_id": "0f0c...",
                  "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                  "job_type": "attest_screening",
                  "status": "queued"
                }
              }
            }
          },
          "404": {
            "description": "subject_ref desconocido"
          }
        }
      }
    },
    "/kyc/source-of-funds": {
      "post": {
        "summary": "Anclar evidencia de origen de fondos",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "source_of_funds_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "source_of_funds_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "aml_risk": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 255,
                    "default": 0
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "source_of_funds_hash": "0xabababababababababababababababababababababababababababababababab",
                "aml_risk": 20
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/kyc/consent": {
      "post": {
        "summary": "Registrar consentimiento del usuario (hash del documento firmado)",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "consent_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "consent_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "consent_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "consent_hash": "0xabababababababababababababababababababababababababababababababab"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/kyc/review": {
      "post": {
        "summary": "Programar la proxima revision periodica (CDD/EDD)",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "next_review_at"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "next_review_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "next_review_at": "2027-01-01T00:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/kyc/evidence": {
      "post": {
        "summary": "Adjuntar el hash de un paquete de evidencia cifrada",
        "description": "Los documentos permanecen cifrados en el exchange; on-chain solo viaja el hash y un codigo de tipo.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "evidence_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "evidence_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "evidence_type_code": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 65535,
                    "default": 0
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "evidence_hash": "0xabababababababababababababababababababababababababababababababab",
                "evidence_type_code": 10
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/kyc/travel-rule": {
      "post": {
        "summary": "Anclar el hash del registro Travel Rule (FATF R.16)",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref",
                  "travel_rule_hash"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "travel_rule_hash": {
                    "$ref": "#/components/schemas/Bytes32"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "travel_rule_hash": "0xabababababababababababababababababababababababababababababababab"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/kyc/compliance-check": {
      "post": {
        "summary": "Chequeo integral de cumplimiento leido del contrato",
        "description": "Evalua on-chain vigencia, nivel minimo, sanciones, frescura del screening, riesgo AML y revision vencida. Devuelve un booleano y un codigo de motivo.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "min_level": {
                    "type": "integer",
                    "default": 1
                  },
                  "max_screening_age_days": {
                    "type": "integer",
                    "default": 365
                  },
                  "max_aml_risk": {
                    "type": "integer",
                    "default": 0,
                    "description": "0 = sin limite"
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "min_level": 2,
                "max_screening_age_days": 365,
                "max_aml_risk": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultado del chequeo",
            "content": {
              "application/json": {
                "example": {
                  "compliant": false,
                  "reason_code": 8,
                  "reason": "screening vencido",
                  "contract": "0x1234abcd",
                  "chain_id": 4242
                }
              }
            }
          }
        }
      }
    },
    "/kyc/erase": {
      "post": {
        "summary": "Derecho al olvido (GDPR art. 17)",
        "description": "Borra los hashes del sujeto en el contrato dejando solo una lapida con la marca de tiempo. Las pruebas historicas siguen en los lotes Merkle.",
        "tags": [
          "Cumplimiento"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "subject_ref"
                ],
                "properties": {
                  "subject_ref": {
                    "$ref": "#/components/schemas/Bytes32"
                  },
                  "reason_code": {
                    "type": "integer",
                    "default": 0
                  }
                }
              },
              "example": {
                "subject_ref": "0xabababababababababababababababababababababababababababababababab",
                "reason_code": 100
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Encolado"
          }
        }
      }
    },
    "/batches/{batch_id}": {
      "get": {
        "summary": "Detalle de un lote Merkle",
        "tags": [
          "Lotes"
        ],
        "parameters": [
          {
            "name": "batch_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lote"
          }
        }
      }
    }
  }
}