{
  "openapi": "3.1.0",
  "info": {
    "title": "Notsy Facturación — API de Agentes",
    "version": "1.0.0",
    "description": "Superficie pública para agentes de IA: resolver claves del SAT, previsualizar y emitir CFDIs 4.0 a nombre del negocio dueño de la API key. Solo incluye los endpoints de agente (los endpoints internos/humanos no se publican). Recomendado consumirla vía el servidor MCP: https://mcp.notsy.com.mx/mcp",
    "contact": { "name": "Notsy", "email": "heus@notsy.com.mx", "url": "https://facturacion.notsy.com.mx/developers" }
  },
  "servers": [{ "url": "https://facturacion-api.notsy.com.mx", "description": "Motor (producción)" }],
  "security": [{ "bearerAuth": [] }],
  "tags": [{ "name": "agente", "description": "Operaciones disponibles para agentes con API key" }],
  "paths": {
    "/agent/whoami": {
      "get": {
        "tags": ["agente"],
        "summary": "Identidad, permisos y límites del agente",
        "operationId": "whoami",
        "responses": {
          "200": { "description": "Contexto del agente", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WhoAmI" } } } },
          "401": { "$ref": "#/components/responses/NoAutorizado" }
        }
      }
    },
    "/agent/resolver-claves-sat": {
      "post": {
        "tags": ["agente"],
        "summary": "Lenguaje natural → claves del SAT",
        "description": "Convierte una descripción (p.ej. 'consultoría de marketing') en clave_prodserv, clave_unidad, objeto_imp, tasa_iva y uso_cfdi sugeridos.",
        "operationId": "resolverClavesSat",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResolverClavesReq" } } } },
        "responses": {
          "200": { "description": "Sugerencia + candidatos", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ResolverClavesResp" } } } },
          "401": { "$ref": "#/components/responses/NoAutorizado" },
          "403": { "$ref": "#/components/responses/SinPermiso" },
          "429": { "$ref": "#/components/responses/DemasiadasSolicitudes" }
        }
      }
    },
    "/agent/facturas/preview": {
      "post": {
        "tags": ["agente"],
        "summary": "Dry-run: valida y calcula totales SIN timbrar",
        "description": "No llama al SAT ni tiene costo. Devuelve totales (subtotal, IVA, retenciones, total), errores de validación y si excede algún límite. Úsalo antes de emitir.",
        "operationId": "previewFactura",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FacturaReq" } } } },
        "responses": {
          "200": { "description": "Resultado del dry-run", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PreviewResp" } } } },
          "401": { "$ref": "#/components/responses/NoAutorizado" },
          "403": { "$ref": "#/components/responses/SinPermiso" },
          "429": { "$ref": "#/components/responses/DemasiadasSolicitudes" }
        }
      }
    },
    "/agent/facturas": {
      "post": {
        "tags": ["agente"],
        "summary": "Emite y timbra un CFDI 4.0 (acción real, con costo)",
        "description": "Aplica los límites de la API key (puede requerir aprobación humana) y registra la acción en bitácora. Envía el header 'Idempotency-Key' para evitar timbrados duplicados en reintentos.",
        "operationId": "emitirCfdi",
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string" }, "description": "Clave única por solicitud lógica; reintentar con la misma devuelve el resultado original (no re-timbra)." }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FacturaReq" } } } },
        "responses": {
          "200": { "description": "CFDI timbrado", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CfdiEmitido" } } } },
          "401": { "$ref": "#/components/responses/NoAutorizado" },
          "403": { "description": "Sin permiso, o bloqueado por límite / requiere aprobación humana", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "409": { "description": "Una solicitud idéntica está en proceso (idempotencia)" },
          "422": { "description": "Datos inválidos o rechazo del PAC", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/DemasiadasSolicitudes" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": { "type": "http", "scheme": "bearer", "description": "API key del negocio (formato ntsy_...), generada en el panel de Notsy. Nunca el token de servicio." }
    },
    "responses": {
      "NoAutorizado": { "description": "API key faltante, inválida o revocada", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "SinPermiso": { "description": "La API key no tiene el scope requerido", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "DemasiadasSolicitudes": { "description": "Límite de frecuencia excedido; reintenta en un momento" }
    },
    "schemas": {
      "Cliente": {
        "type": "object",
        "description": "Receptor del CFDI. Alternativa: enviar 'cliente_id' de un cliente guardado.",
        "properties": {
          "rfc": { "type": "string" },
          "nombre": { "type": "string", "description": "Razón social EXACTA al SAT (morales sin 'SA DE CV')." },
          "regimen_fiscal": { "type": "string", "description": "Clave de régimen fiscal del receptor (p.ej. 601, 612, 616)." },
          "cp": { "type": "string", "description": "Código postal fiscal del receptor." },
          "uso_cfdi": { "type": "string", "default": "G03" },
          "email": { "type": "string" },
          "es_publico_general": { "type": "boolean", "default": false }
        },
        "required": ["rfc", "nombre", "regimen_fiscal", "cp"]
      },
      "Concepto": {
        "type": "object",
        "properties": {
          "descripcion": { "type": "string" },
          "clave_prodserv": { "type": "string", "description": "Clave SAT del producto/servicio (de resolver-claves-sat)." },
          "clave_unidad": { "type": "string", "description": "Clave SAT de unidad (p.ej. E48 servicio, H87 pieza)." },
          "cantidad": { "type": "number" },
          "valor_unitario": { "type": "number" },
          "precio_incluye_iva": { "type": "boolean", "default": true },
          "tasa_iva": { "type": "number", "default": 0.16, "description": "0.16, 0.08 o 0.0" },
          "objeto_imp": { "type": "string", "default": "02" },
          "retencion": { "type": "string", "description": "Preset opcional: honorarios | arrendamiento | comisiones | fletes" }
        },
        "required": ["descripcion", "clave_prodserv", "clave_unidad", "cantidad", "valor_unitario"]
      },
      "FacturaReq": {
        "type": "object",
        "properties": {
          "conceptos": { "type": "array", "items": { "$ref": "#/components/schemas/Concepto" } },
          "cliente": { "$ref": "#/components/schemas/Cliente" },
          "cliente_id": { "type": "string", "description": "Alternativa a 'cliente': id de un receptor guardado." },
          "forma_pago": { "type": "string", "default": "03", "description": "Clave SAT de forma de pago (03 transferencia, 01 efectivo, ...)." },
          "metodo_pago": { "type": "string", "default": "PUE", "enum": ["PUE", "PPD"], "description": "PUE de contado, PPD a crédito." },
          "observaciones": { "type": "string" },
          "emisor_id": { "type": "string", "description": "Multiemisor: desde qué RFC emitir (omitir = predeterminado)." },
          "anticipo_id": { "type": "string", "description": "Aplicar un anticipo previo (relación 07 + nota de crédito)." }
        },
        "required": ["conceptos"]
      },
      "ResolverClavesReq": {
        "type": "object",
        "properties": {
          "descripcion": { "type": "string" },
          "unidad_hint": { "type": "string", "description": "Opcional: unidad en palabras ('servicio', 'hora', 'pieza')." },
          "con_iva": { "type": "boolean", "default": true }
        },
        "required": ["descripcion"]
      },
      "ResolverClavesResp": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "sugerencia": {
            "type": "object",
            "properties": {
              "clave_prodserv": { "type": "string" },
              "descripcion_prodserv": { "type": "string" },
              "clave_unidad": { "type": "string" },
              "objeto_imp": { "type": "string" },
              "tasa_iva": { "type": "number" },
              "uso_cfdi": { "type": "string" }
            }
          },
          "candidatos": { "type": "object" }
        }
      },
      "Totales": {
        "type": "object",
        "properties": {
          "subtotal": { "type": "number" }, "iva": { "type": "number" },
          "iva_retenido": { "type": "number" }, "isr_retenido": { "type": "number" },
          "total": { "type": "number" }
        }
      },
      "PreviewResp": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "valido": { "type": "boolean" },
          "errores": { "type": "array", "items": { "type": "string" } },
          "totales": { "$ref": "#/components/schemas/Totales" },
          "limite": { "type": ["object", "null"], "description": "null si procede; objeto con motivo si excede tope/umbral/límite diario." }
        }
      },
      "CfdiEmitido": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" }, "uuid": { "type": "string" },
          "serie": { "type": "string" }, "folio": { "type": "string" },
          "total": { "type": "number" }, "metodo_pago": { "type": "string" }
        }
      },
      "WhoAmI": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" }, "agent_id": { "type": "string" },
          "nombre": { "type": "string" },
          "scopes": { "type": "array", "items": { "type": "string" } },
          "limites": { "type": "object" }, "uso_hoy": { "type": "object" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": { "type": "string", "description": "Identificador estable del error." },
              "message": { "type": "string" },
              "fields": { "type": "array", "items": { "type": "object" }, "description": "Errores por campo cuando aplican." },
              "retriable": { "type": "boolean" },
              "status": { "type": "integer" }
            }
          }
        }
      }
    }
  }
}
