> ## Documentation Index
> Fetch the complete documentation index at: https://devs.tapipay.la/llms.txt
> Use this file to discover all available pages before exploring further.

# Adherir un usuario a un producto

> Adhiere al usuario a un producto para que sus deudas se cobren solas con su medio de pago, o reactiva una adhesión dada de baja. Es idempotente: repetir la llamada no duplica la adhesión. Requiere que el usuario ya haya registrado su cuenta bancaria en la página de enrolamiento. Si tenía una adhesión antigua sin producto, se da de baja para que la misma deuda no se cobre dos veces.



## OpenAPI

````yaml /api-reference/openapi.yaml post /adhesions
openapi: 3.0.0
info:
  title: TapiPay API
  version: 1.0.0
  description: >
    API pública de la plataforma de cobranzas TapiPay.  Ofrece un contrato
    limpio y desacoplado del modelo interno de la plataforma, permitiendo a los
    clientes crear y gestionar cobros (deudas puntuales,  suscripciones
    recurrentes, links de pago, productos y contactos)  sin conocer detalles
    internos de Company, Modalities o GenerationData.
  contact:
    name: TapiPay Team
    url: https://www.tapipay.com
  license:
    name: Proprietary
servers:
  - url: https://tapipay-facade.homo.tapila.cloud
    description: Homologación
security:
  - apiKeyAuth: []
    tapiAuth: []
paths:
  /adhesions:
    post:
      tags:
        - Adhesions
      summary: Adherir un usuario a un producto
      description: >-
        Adhiere al usuario a un producto para que sus deudas se cobren solas con
        su medio de pago, o reactiva una adhesión dada de baja. Es idempotente:
        repetir la llamada no duplica la adhesión. Requiere que el usuario ya
        haya registrado su cuenta bancaria en la página de enrolamiento. Si
        tenía una adhesión antigua sin producto, se da de baja para que la misma
        deuda no se cobre dos veces.
      operationId: createAdhesion
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AdhesionRequest'
      responses:
        '200':
          description: >-
            La adhesión ya existía (activa con el medio actual, reactivada o
            repuntada al medio actual).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Adhesion'
                  requestId:
                    type: string
        '201':
          description: Primera adhesión del usuario a ese producto.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Adhesion'
                  requestId:
                    type: string
        '400':
          description: >-
            Validación fallida: campo fuera del contrato, productId y
            externalProductId juntos o ninguno, productId sin formato
            `prd_<número>`, externalClientId faltante o con caracteres no
            permitidos. También BAD_REQUEST si tu cliente tiene varias companies
            y no enviaste companyCode.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Producto referenciado inexistente (RESOURCE_NOT_FOUND).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            RESOURCE_CONFLICT si el usuario cambió su medio de pago mientras se
            creaba la adhesión. Reintenta.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '422':
          description: >-
            NO_ACTIVE_PAYMENT_METHOD si el usuario no tiene un medio de pago
            vigente (cuenta bancaria) o tu compañía no tiene habilitado el medio
            de pago default. COMPANY_WITHOUT_PRODUCT_MODALITY si tu compañía no
            tiene configurada la modalidad de productos para domiciliación.
            RESOLUTION_ERROR si el companyCode no pertenece a tu cliente.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    AdhesionRequest:
      type: object
      required:
        - externalClientId
      description: >-
        Body de POST /adhesions y POST /adhesions/cancel. Solo acepta
        externalClientId, productId, externalProductId y companyCode: cualquier
        otro campo devuelve 400 con el campo en `details[].field`. Debes enviar
        uno de productId o externalProductId (no ambos, no ninguno).
      properties:
        externalClientId:
          type: string
          maxLength: 100
          pattern: ^[\w-]{1,100}$
          description: >-
            Tu identificador del usuario final: el mismo del enrolamiento. Solo
            letras, dígitos, `_` y `-`, hasta 100 caracteres.
        productId:
          type: string
          pattern: ^prd_\d+$
          description: >-
            ID del producto en Tapi, formato `prd_<número>`. Excluyente con
            externalProductId.
        externalProductId:
          type: string
          description: >-
            Tu identificador del producto. Excluyente con productId. No admite
            los caracteres `'`, `"`, `\`, `;` (400).
        companyCode:
          type: string
          description: >-
            Opcional. Solo si tu cliente tiene varias companies; si no, se
            resuelve desde el token.
    Adhesion:
      type: object
      description: Adhesión del usuario a un producto. No incluye el medio de pago.
      properties:
        externalClientId:
          type: string
          example: CLI-00042
        productId:
          type: string
          example: prd_7
        status:
          type: string
          enum:
            - ACTIVE
            - INACTIVE
        createdAt:
          type: string
          format: date-time
          nullable: true
          description: >-
            Timestamp de creación (ISO 8601 en UTC con milisegundos y Z). Puede
            ser null si no se pudo releer la adhesión recién escrita; la
            adhesión igual quedó registrada.
    Error:
      type: object
      required:
        - error
        - requestId
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - INVALID_REQUEST
                - BAD_REQUEST
                - RESOURCE_NOT_FOUND
                - RESOURCE_CONFLICT
                - RESOLUTION_ERROR
                - BUSINESS_RULE_VIOLATION
                - RATE_LIMITED
                - INTERNAL_ERROR
                - PARTIAL_UPDATE
              description: >-
                Código de error estándar. BAD_REQUEST se usa cuando tu cliente
                tiene varias companies y no se envía `companyCode`.
                PARTIAL_UPDATE solo aparece en el 207 de PATCH
                /subscriptions/{id}: la suscripción se actualizó, pero alguna de
                sus deudas no. Los 401 y 403 los emite el API Gateway y no usan
                este envelope (ver GatewayError).
            message:
              type: string
              description: >-
                Mensaje legible del error. Es informativo: no lo parsees, usa
                `code` y `details[].field`.
            details:
              type: array
              description: >-
                Array de detalles adicionales (validaciones por campo, etc.). Un
                JSON malformado devuelve `[{ "field": "body", "issue": "Invalid
                JSON syntax" }]`.
              items:
                type: object
                properties:
                  field:
                    type: string
                    description: Campo que falló la validación
                  issue:
                    type: string
                    description: Descripción del problema
                  index:
                    type: integer
                    description: >-
                      Solo en el modo lote de POST /v2/debts. Posición del item
                      en `debts`.
                  externalRequestId:
                    type: string
                    description: >-
                      Solo en el modo lote de POST /v2/debts. externalRequestId
                      del item.
        requestId:
          type: string
          pattern: ^req_[a-zA-Z0-9]+$
          description: ID único del request para trazabilidad
    GatewayError:
      type: object
      description: >-
        Respuesta de error emitida por el API Gateway, antes de llegar al
        servicio. No lleva el envelope `error` ni `requestId`. Casos: token
        faltante o inválido en `x-authorization-token` devuelve `401 {"message":
        "Unauthorized"}`; `x-api-key` faltante o inválida devuelve `403
        {"message": "Forbidden"}`; token válido sin permiso sobre el recurso
        devuelve `403 {"Message": "User is not authorized to access this
        resource with an explicit deny"}`. Ojo al tipar: en este último caso el
        campo viene con mayúscula (`Message`), no `message`.
      properties:
        message:
          type: string
          example: Unauthorized
        Message:
          type: string
          description: Solo en el 403 de explicit deny (con mayúscula).
          example: User is not authorized to access this resource with an explicit deny
  responses:
    Unauthorized:
      description: >-
        No autenticado. Lo emite el API Gateway cuando falta o es inválido el
        token en `x-authorization-token`. Body `{"message": "Unauthorized"}`,
        sin envelope `error` ni `requestId`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          example:
            message: Unauthorized
    Forbidden:
      description: >-
        Prohibido. Lo emite el API Gateway, sin envelope `error` ni `requestId`.
        Si falta o es inválida la `x-api-key`, el body es `{"message":
        "Forbidden"}`. Si el token es válido pero no tiene permiso sobre el
        recurso, el body es `{"Message": "User is not authorized to access this
        resource with an explicit deny"}` (campo con mayúscula).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/GatewayError'
          examples:
            apiKey:
              summary: x-api-key faltante o inválida
              value:
                message: Forbidden
            explicitDeny:
              summary: Token sin permiso sobre el recurso
              value:
                Message: >-
                  User is not authorized to access this resource with an
                  explicit deny
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >
        API key del API Gateway de Tapi. Requerida en todas las operaciones.
        Distinta por ambiente (desarrollo, homologación, producción).
    tapiAuth:
      type: apiKey
      in: header
      name: x-authorization-token
      description: >
        Token TAPI JWT. Requerido en todas las operaciones. Se envía sin prefijo
        "Bearer". Ej: x-authorization-token: eyJhbGci...

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.