> ## Documentation Index
> Fetch the complete documentation index at: https://www.emissa.com.ar/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Verificar la conexión con ARCA

> Le pregunta a ARCA si la conexión está completa. **No emite ningún comprobante**: no consume número de serie ni tiene efecto fiscal.

⚠️ **Pero no es de sólo lectura.** Cuando el estado es `DELEGACION_NO_VISIBLE`, esta llamada **dispara un trabajo que escribe en ARCA**: Emissa entra a su Administrador de Relaciones y designa su Computador Fiscal para este contribuyente. El campo `designacion` te dice qué pasó con eso.

Decía sólo *"sin emitir nada"*, que es verdad sobre comprobantes y falso sobre ARCA. Un integrador lo leyó, lo puso a repetir, y doce verificaciones seguidas dispararon doce trabajos. **No lo pongas en un `useEffect` que refresque solo.** Hay un enfriamiento de 5 minutos del lado de Emissa —vas a ver `designacion: "en-enfriamiento"`— pero el enfriamiento es una red, no un permiso.

Antes, la forma de saberlo era intentar emitir y leer el rechazo — o sea descubrir que falta un punto de venta con un comprobante ya numerado.

Los estados posibles:

- `CONECTADO` — ARCA responde y el punto de venta está habilitado.
- `FALTA_PUNTO_DE_VENTA` — el `detalle` dice cuáles **sí** están.
- `DELEGACION_NO_VISIBLE` — o no se autorizó a Emissa, o se autorizó después del ticket de acceso vigente. **Son indistinguibles para ARCA**, así que el `detalle` trae la fecha que las separa: si autorizaste después de ella, se completa solo.
- `SIN_INSCRIPCION_ACTIVA` — el padrón no le registra IVA ni monotributo activos. **Gana sobre todo lo demás**: sin inscripción no puede tener punto de venta ni emitir, esté la autorización hecha o no.
- `SIN_CREDENCIAL` — no hay certificado con el que firmar.
- `INDETERMINADO` — ARCA no contestó. **No quiere decir que algo esté mal.**

Con `DELEGACION_NO_VISIBLE` la respuesta trae además `ticketVigenteHasta`: hasta ese momento una autorización recién hecha **no puede verse**, así que es cuándo tiene sentido volver a preguntar.

Consulta el catálogo de puntos de venta salteando el cache, así que refleja lo que ARCA dice ahora.

**Scope de API key:** `taxpayer:read`. Una key sin ese scope recibe `403 API_KEY_INSUFFICIENT_SCOPE`, aunque el rol de quien la creó lo permita: el permiso efectivo es la **intersección** de los dos.



## OpenAPI

````yaml https://api.emissa.com.ar/openapi.json post /v1/taxpayers/{id}/conexion/verificar
openapi: 3.0.0
info:
  title: Emissa API
  description: >-
    API de facturación electrónica ante ARCA (ex-AFIP) para la República
    Argentina.


    ## Autenticación


    Dos credenciales, **dos headers distintos**:


    | Quién | Header |

    |---|---|

    | Integración de un sistema de terceros | `X-API-Key: emi_test_… /
    emi_live_…` |

    | Panel de Emissa | `Authorization: Bearer <JWT>` |


    Una API key en `Authorization` devuelve **401 `API_KEY_WRONG_HEADER`** con
    la

    indicación exacta, no un "token inválido" genérico.


    La key se crea desde el panel y **pertenece a la organización**, con una

    restricción **opcional** a un subconjunto de contribuyentes: sin restricción

    alcanza a todos los de la organización, incluidos los que se den de alta

    después. Lleva scopes, y no se puede crear una API key con otra API key:
    sería

    una escalada de privilegio.


    ## Identificá tu producto: `X-Emissa-Producto`


    Cabecera **opcional**, sólo con API key: el producto que origina el pedido

    (`inmos`, por ejemplo). Formato `^[a-z0-9-]{1,32}$`; cualquier otro valor

    devuelve `400 INVALID_INPUT`. Ausente o vacía, no pasa nada: queda `null`.


    Queda en el comprobante (`producto`, que no cambia después: un reintento

    idempotente con otra etiqueta devuelve el original), en el consumo

    (`GET /v1/consumo` trae `porProducto` y acepta `?producto=`), en el registro

    de pedidos y en el payload de `invoice.resolved`. `GET /v1/invoices/search`

    acepta `?producto=`. **Es atribución, no autorización**: no cambia lo que la
    key

    puede hacer.


    ## Sandbox: keys `test` y `live`


    **El modo está en el texto de la key**, así que se ve sin ejecutar nada:


    | Prefijo | Opera contra | Qué emite |

    |---|---|---|

    | `emi_test_` | contribuyentes de `HOMOLOGACION` | CAE real de ARCA, **sin
    validez fiscal** |

    | `emi_live_` | contribuyentes de `PRODUCCION` | comprobantes fiscales |


    El modo **no se elige**: se deriva del ambiente del contribuyente al crear
    la

    key, y se verifica en **cada** llamada. Una key `test` contra un
    contribuyente

    de producción devuelve `403 API_KEY_WRONG_MODE` — nunca emite.


    **Toda respuesta autenticada con API key lleva `X-Emissa-Mode: test|live`**,

    y el comprobante trae `environment`. Si tu suite de integración chequea una

    sola cosa, que sea ésta.


    > **En `PRODUCCION` lo que emitís es un comprobante fiscal real.** Entra en
    los

    > registros del contribuyente ante ARCA, pesa en su declaración y no se
    borra: se

    > revierte con una nota de crédito. Para probar está `HOMOLOGACION`, que
    también

    > da CAE real del organismo y no tiene consecuencia fiscal.


    ## Emitir un comprobante


    ```bash

    curl -X POST https://api.ejemplo/v1/invoices/authorize \
      -H 'X-API-Key: emi_...' \
      -H 'Idempotency-Key: pedido-8421' \
      -H 'Content-Type: application/json' \
      -d '{
        "taxpayerId": "...",
        "cbteTipo": 1, "concepto": 1,
        "receiverDocTipo": 80, "receiverDocNro": "30500010912",
        "receiverName": "Cliente SA", "receiverVatConditionId": 1,
        "issueDate": "2026-07-26",
        "items": [{ "description": "Consultoría", "unit": "UNIDADES",
                    "quantity": 1, "unitPrice": 1000,
                    "tratamiento": "GRAVADO", "vatRateId": 5 }]
      }'
    ```


    **`Idempotency-Key` es obligatorio.** Un duplicado fiscal no se deshace, así

    que el default tiene que ser el seguro: reintentar con la misma clave
    devuelve el

    comprobante original y la respuesta lleva `Idempotent-Replay: true`.


    ## Cuota y consumo


    **La unidad es el comprobante autorizado por ARCA.** No la creación, no el

    request. Un rechazo **no consume cuota**; el padrón, la constatación de CAE
    y los

    PDF se miden pero no se cobran.


    Cada emisión responde con:


    | Cabecera | Qué dice |

    |---|---|

    | `X-Cuota-Limite` | incluidos en el período, o `ilimitado` |

    | `X-Cuota-Usada` | comprobantes autorizados en el período, éste incluido |

    | `X-Cuota-Restante` | nunca negativo |

    | `X-Cuota-Reinicio` | ISO 8601 del primer instante del período siguiente |

    | `X-Cuota-Excedente-Unitario` | **sólo si te excediste**: a qué precio se
    factura cada uno |


    > **La cuota es blanda: llegar a cero NO bloquea nada.** Se sigue emitiendo
    y el

    > excedente se factura al unitario publicado. No la confundas con

    > `X-RateLimit-*`, que sí es duro y sí devuelve 429.


    `GET /v1/consumo?periodo=YYYY-MM` da el total de la organización y el
    desglose

    por CUIT. Un **CUIT activo** es el que emitió al menos un comprobante en el

    período: se deriva de los eventos, así que un contribuyente dado de alta que
    no

    facturó no cuenta.


    ## Reintentos


    | Código | Qué hacer |

    |---|---|

    | `503 SERIES_BUSY` | **Reintentá.** Las emisiones de una serie se
    serializan para que los números lleguen a ARCA en orden; el 503 es esa
    espera hecha explícita. |

    | `429 RATE_LIMIT_EXCEEDED` | Reintentá con backoff. |

    | `409 IDEMPOTENCY_IN_PROGRESS` | El pedido original sigue corriendo. Esperá
    su resultado; **no** cambies la clave. |

    | cortes de red sin respuesta | Reintentá **con la misma
    `Idempotency-Key`**. Es exactamente para lo que está. |


    Si un comprobante quedó en `PENDING` o `UNKNOWN`, `POST
    /v1/invoices/{id}/reconcile`

    le pregunta a ARCA qué pasó. Es el camino de salida: sin él la serie queda
    trabada.


    ## Errores


    Todos tienen la misma forma. Lo estable para ramificar es `errorCode`; el

    `message` es texto para mostrar y puede cambiar. `details` **no viaja en

    producción** cuando es material de diagnóstico.


    ## Webhooks


    Para lo que **no podés saber por la respuesta HTTP**. Se configuran desde el

    panel —una vez por organización, no por cliente— y hay cinco eventos:


    | Evento | Cuándo |

    |---|---|

    | `invoice.resolved` | un comprobante que quedó `PENDING` o `UNKNOWN` llegó
    a un estado final |

    | `connection.established` | un contribuyente pasó a `CONECTADO`: ya puede
    facturar |

    | `credential.expiring` | al certificado de ARCA le quedan 30 días o menos |

    | `api_key.expiring` | a una API key le quedan 30 días o menos |

    | `quota.exceeded` | se pasó la cuota del plan: **no bloquea**, se sigue
    emitiendo y el excedente se factura al unitario. Sale una vez por período |


    `connection.established` se emite **sólo en la transición**, no en cada

    verificación: quien está esperando la conexión verifica varias veces
    seguidas, y

    un aviso por cada una sería ruido. Es lo que le permite a una plataforma
    avisarle

    a su cliente sin encuestar `/conexion/verificar`.


    **No hay `invoice.authorized`**: `POST /v1/invoices/authorize` ya devuelve
    el

    comprobante con su CAE. Un evento por eso te obligaría a procesar dos veces
    lo

    mismo y a resolver cuál llegó primero.


    ### Verificar la firma


    ```

    POST /tu-receptor

    X-Emissa-Signature: t=1769472000,v1=5257a869e7...

    X-Emissa-Event-Id: inv_cmft8x2k30001_authorized

    X-Emissa-Event-Type: invoice.resolved

    X-Emissa-Delivery-Attempt: 1

    ```


    `v1` es `HMAC-SHA256(secreto, "${t}.${cuerpo crudo}")` en hexadecimal.

    Compará **con el cuerpo sin parsear**: reserializar cambia los bytes y la
    firma

    deja de dar. Rechazá lo que llegue con más de 5 minutos de diferencia — el
    `t`

    está dentro del HMAC justamente para que una captura no se pueda repetir
    mañana.


    ```js

    const [t, v1] = cabecera.split(',').map((p) => p.split('=')[1]);

    const esperado = crypto.createHmac('sha256', secreto)
      .update(`${t}.${cuerpoCrudo}`).digest('hex');
    const ok = crypto.timingSafeEqual(Buffer.from(v1,'hex'),
    Buffer.from(esperado,'hex'))
      && Math.abs(Date.now()/1000 - Number(t)) <= 300;
    ```


    ### Deduplicá por `id`


    **La entrega es "al menos una vez".** *Exactamente una vez* no existe sobre

    HTTP: si procesás el evento y te morís antes de contestar 200, desde acá es

    indistinguible de que nunca te llegó, así que se reintenta.


    El `id` del evento es estable entre reintentos. Guardalo y descartá lo
    repetido.


    ### Contestá rápido


    Un 2xx cuenta como entregado. Cualquier otra cosa se reintenta a los 30 s, 2
    min,

    10 min, 1 h, 6 h y 24 h; después queda en la cola de dead-letter para
    reenviarla

    a mano desde el panel. El timeout es de **10 segundos**: encolá y contestá,
    no

    proceses adentro del handler.


    ## Trazabilidad


    **Toda** respuesta lleva `X-Request-Id`. Si lo mandás vos y respeta

    `[A-Za-z0-9._:-]{1,128}`, se te devuelve el mismo, para que tu traza y la

    nuestra sean una sola. Es el dato que hay que citar en un ticket.


    ## Ambientes


    `HOMOLOGACION` es el ambiente de pruebas de ARCA: emite comprobantes con CAE

    real pero **sin validez fiscal**. Es donde se integra y se prueba.


    `PRODUCCION` emite **comprobantes fiscales reales**. Requiere certificado

    productivo, punto de venta de tipo "Factura Electrónica – Web Service" y el

    servicio autorizado en ARCA; si falta alguno, contesta ARCA con el motivo.


    Los dos son conjuntos **disjuntos** de claves: una key `test` contra un

    contribuyente de `PRODUCCION` se rechaza con 403, así que no podés emitir un

    comprobante real con la clave con la que estabas probando.


    ## Compatibilidad


    Mientras `/v1` esté vigente se pueden **agregar** campos, parámetros

    opcionales y endpoints. No se quita ni se renombra un campo, no se le cambia
    el

    tipo ni la semántica, no se vuelve obligatorio algo opcional y no se cambia
    un

    código de estado. **Tu cliente tiene que tolerar campos que no conoce.**
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.emissa.com.ar
    description: Producción
  - url: http://localhost:3001
    description: Local
security: []
tags:
  - name: Comprobantes
    description: Emitir, consultar, anular y exportar
  - name: Contribuyentes
    description: Los CUIT desde los que se factura
  - name: Clientes
    description: Receptores y validación contra el padrón de ARCA
  - name: Credenciales ARCA
    description: Certificado y clave privada del emisor
  - name: Consumo
    description: Cuánto se emitió en el período, y con qué cuota
  - name: Panel
    description: 'Operaciones internas del panel: sesión, 2FA, keys'
  - name: Salud
    description: Sondas de vida y de disponibilidad
paths:
  /v1/taxpayers/{id}/conexion/verificar:
    post:
      tags:
        - Contribuyentes
      summary: Verificar la conexión con ARCA
      description: >-
        Le pregunta a ARCA si la conexión está completa. **No emite ningún
        comprobante**: no consume número de serie ni tiene efecto fiscal.


        ⚠️ **Pero no es de sólo lectura.** Cuando el estado es
        `DELEGACION_NO_VISIBLE`, esta llamada **dispara un trabajo que escribe
        en ARCA**: Emissa entra a su Administrador de Relaciones y designa su
        Computador Fiscal para este contribuyente. El campo `designacion` te
        dice qué pasó con eso.


        Decía sólo *"sin emitir nada"*, que es verdad sobre comprobantes y falso
        sobre ARCA. Un integrador lo leyó, lo puso a repetir, y doce
        verificaciones seguidas dispararon doce trabajos. **No lo pongas en un
        `useEffect` que refresque solo.** Hay un enfriamiento de 5 minutos del
        lado de Emissa —vas a ver `designacion: "en-enfriamiento"`— pero el
        enfriamiento es una red, no un permiso.


        Antes, la forma de saberlo era intentar emitir y leer el rechazo — o sea
        descubrir que falta un punto de venta con un comprobante ya numerado.


        Los estados posibles:


        - `CONECTADO` — ARCA responde y el punto de venta está habilitado.

        - `FALTA_PUNTO_DE_VENTA` — el `detalle` dice cuáles **sí** están.

        - `DELEGACION_NO_VISIBLE` — o no se autorizó a Emissa, o se autorizó
        después del ticket de acceso vigente. **Son indistinguibles para ARCA**,
        así que el `detalle` trae la fecha que las separa: si autorizaste
        después de ella, se completa solo.

        - `SIN_INSCRIPCION_ACTIVA` — el padrón no le registra IVA ni monotributo
        activos. **Gana sobre todo lo demás**: sin inscripción no puede tener
        punto de venta ni emitir, esté la autorización hecha o no.

        - `SIN_CREDENCIAL` — no hay certificado con el que firmar.

        - `INDETERMINADO` — ARCA no contestó. **No quiere decir que algo esté
        mal.**


        Con `DELEGACION_NO_VISIBLE` la respuesta trae además
        `ticketVigenteHasta`: hasta ese momento una autorización recién hecha
        **no puede verse**, así que es cuándo tiene sentido volver a preguntar.


        Consulta el catálogo de puntos de venta salteando el cache, así que
        refleja lo que ARCA dice ahora.


        **Scope de API key:** `taxpayer:read`. Una key sin ese scope recibe `403
        API_KEY_INSUFFICIENT_SCOPE`, aunque el rol de quien la creó lo permita:
        el permiso efectivo es la **intersección** de los dos.
      operationId: TaxpayersController_verificarConexion
      parameters:
        - name: id
          required: true
          in: path
          schema:
            type: string
        - name: X-Emissa-Producto
          in: header
          description: >-
            El producto que origina el pedido (`inmos`, por ejemplo). Opcional,
            sólo con API key. Formato `^[a-z0-9-]{1,32}$`; otro valor da `400
            INVALID_INPUT`. Queda en el comprobante, en el consumo y en el
            registro de pedidos.


            Con una key **sin** producto es sólo atribución: no autoriza ni
            restringe nada. Con una key **atada a un producto** el pedido se
            etiqueta con el de la key aunque no mandes la cabecera, y si mandás
            otro valor la respuesta es `403 API_KEY_PRODUCTO_DISTINTO`.
          required: false
          schema:
            type: string
            pattern: ^[a-z0-9-]{1,32}$
            example: inmos
      responses:
        '200':
          description: El diagnóstico, con un `detalle` para mostrarle a una persona.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DiagnosticoDeConexionDto'
              example:
                estado: FALTA_PUNTO_DE_VENTA
                detalle: >-
                  El punto de venta 1 no figura entre los habilitados para web
                  service. ARCA informa: 3, 7. Dalo de alta en ARCA como
                  "Factura Electrónica - Web Service", o configurá uno de esos.
                puntosDeVenta:
                  - 3
                  - 7
                verificadoEn: '2026-08-11T20:15:00.000Z'
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
        '400':
          description: |-
            Pedido mal formado o inválido.

            `errorCode`: `INVALID_INPUT`
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorDto'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        enum:
                          - INVALID_INPUT
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
            X-Emissa-Mode:
              description: >-
                `test` o `live`. **Va en toda respuesta autenticada con API
                key**, y es la forma más barata de verificar que no estás
                emitiendo contra el ambiente equivocado. No aparece cuando se
                entra con sesión de panel: ahí el ambiente lo decide cada
                contribuyente, no la credencial.
              schema:
                type: string
                enum:
                  - test
                  - live
        '401':
          description: >-
            Falta la credencial o no es válida.


            `errorCode`: `UNAUTHORIZED`, `INVALID_API_KEY`,
            `API_KEY_WRONG_HEADER`
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorDto'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        enum:
                          - UNAUTHORIZED
                          - INVALID_API_KEY
                          - API_KEY_WRONG_HEADER
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
            X-Emissa-Mode:
              description: >-
                `test` o `live`. **Va en toda respuesta autenticada con API
                key**, y es la forma más barata de verificar que no estás
                emitiendo contra el ambiente equivocado. No aparece cuando se
                entra con sesión de panel: ahí el ambiente lo decide cada
                contribuyente, no la credencial.
              schema:
                type: string
                enum:
                  - test
                  - live
        '403':
          description: >-
            La credencial es válida pero no alcanza. Con API key, también: el
            **modo de la key no coincide con el ambiente del contribuyente**.
            Una `emi_test_` contra un contribuyente de `PRODUCCION` nunca emite.


            `errorCode`: `FORBIDDEN`, `API_KEY_INSUFFICIENT_PERMISSIONS`,
            `API_KEY_INSUFFICIENT_SCOPE`, `API_KEY_WRONG_MODE`
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorDto'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        enum:
                          - FORBIDDEN
                          - API_KEY_INSUFFICIENT_PERMISSIONS
                          - API_KEY_INSUFFICIENT_SCOPE
                          - API_KEY_WRONG_MODE
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
            X-Emissa-Mode:
              description: >-
                `test` o `live`. **Va en toda respuesta autenticada con API
                key**, y es la forma más barata de verificar que no estás
                emitiendo contra el ambiente equivocado. No aparece cuando se
                entra con sesión de panel: ahí el ambiente lo decide cada
                contribuyente, no la credencial.
              schema:
                type: string
                enum:
                  - test
                  - live
        '429':
          description: |-
            Se excedió el límite de peticiones. **Reintentá con backoff.**

            `errorCode`: `RATE_LIMIT_EXCEEDED`
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorDto'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        enum:
                          - RATE_LIMIT_EXCEEDED
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
            X-Emissa-Mode:
              description: >-
                `test` o `live`. **Va en toda respuesta autenticada con API
                key**, y es la forma más barata de verificar que no estás
                emitiendo contra el ambiente equivocado. No aparece cuando se
                entra con sesión de panel: ahí el ambiente lo decide cada
                contribuyente, no la credencial.
              schema:
                type: string
                enum:
                  - test
                  - live
        '500':
          description: |-
            Error interno. Citá el `requestId` al reportarlo.

            `errorCode`: `INTERNAL_ERROR`
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorDto'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        enum:
                          - INTERNAL_ERROR
          headers:
            X-Request-Id:
              description: >-
                Identificador del pedido. Va en TODA respuesta. Si lo mandás en
                el pedido y respeta `[A-Za-z0-9._:-]{1,128}`, se te devuelve el
                mismo; si no, se genera uno. Es lo que hay que citar en un
                ticket.
              schema:
                type: string
            X-Emissa-Mode:
              description: >-
                `test` o `live`. **Va en toda respuesta autenticada con API
                key**, y es la forma más barata de verificar que no estás
                emitiendo contra el ambiente equivocado. No aparece cuando se
                entra con sesión de panel: ahí el ambiente lo decide cada
                contribuyente, no la credencial.
              schema:
                type: string
                enum:
                  - test
                  - live
      security:
        - api-key: []
        - panel-jwt: []
components:
  schemas:
    DiagnosticoDeConexionDto:
      type: object
      properties:
        estado:
          type: string
          enum:
            - CONECTADO
            - FALTA_PUNTO_DE_VENTA
            - DELEGACION_NO_VISIBLE
            - SIN_CREDENCIAL
            - INDETERMINADO
            - SIN_INSCRIPCION_ACTIVA
          example: FALTA_PUNTO_DE_VENTA
          description: >-
            Qué le falta al contribuyente. **`INDETERMINADO` no quiere decir que
            algo esté mal**: quiere decir que ARCA no contestó y no se sabe.
        detalle:
          type: string
          example: >-
            El punto de venta 1 no figura entre los habilitados para web
            service. ARCA informa: 3, 7. Dalo de alta en ARCA como "Factura
            Electrónica - Web Service", o configurá uno de esos.
          description: >-
            Para mostrarle a una persona. Explica la consecuencia y el próximo
            paso, no el código interno de ARCA.
        puntosDeVenta:
          example:
            - 3
            - 7
          description: >-
            Los puntos de venta que ARCA informó. **Vacío en homologación**,
            donde ARCA nunca los informa — ahí no se puede concluir nada del
            punto de venta.
          type: array
          items:
            type: number
        cuitAAutorizar:
          type: string
          description: >-
            La CUIT que el contribuyente tiene que autorizar en ARCA, en
            "Administrador de Relaciones de Clave Fiscal", para el servicio
            "Facturación Electrónica".


            ⚠️ **Es la del ambiente de ESTE contribuyente, y los dos ambientes
            tienen CUIT distinta.** Emissa firma con un certificado por
            ambiente. Llevar a ARCA la CUIT que devolvió una verificación de
            `HOMOLOGACION` para hacer el trámite de un contribuyente de
            `PRODUCCION` **le otorga representación a quien no corresponde**, y
            es una operación real que después hay que revocar. Sacá el número de
            la verificación del mismo contribuyente que vas a conectar.


            Sólo viaja cuando **falta hacer ese trámite**
            (`DELEGACION_NO_VISIBLE`). Está aparte del `detalle` para que una
            integración pueda mostrarla en su propia pantalla sin parsear texto.


            **No se publica un ejemplo a propósito:** un número de ejemplo en
            este campo se lee como la respuesta. El que había —`20416564222`— no
            coincidía con el que devuelve homologación, y un integrador frenó su
            prueba para preguntar cuál de los dos era el bueno. Hizo bien.
        designacion:
          type: string
          enum:
            - lanzado
            - ya-hay-uno
            - en-enfriamiento
            - no-disponible
          example: lanzado
          description: >-
            Qué pasó con la mitad del trámite que hace Emissa: designar su
            Computador Fiscal ante ARCA para este contribuyente. **Sólo viaja
            con `DELEGACION_NO_VISIBLE`**, que es cuando hay algo que designar.


            - `lanzado` — arrancó. Tarda ~90 segundos y **no se espera**: el
            resultado se sabe volviendo a verificar, o por el webhook
            `connection.established`.

            - `ya-hay-uno` — hay una corrida en curso y no se lanza otra. **No
            es un error**: esa corrida atiende a todos los pendientes, incluido
            éste.

            - `en-enfriamiento` — se lanzó hace menos de 5 minutos. Tampoco es
            un error, y es el que vas a ver si verificás seguido: **el trabajo
            ya está hecho o en curso.** La corrida anterior atiende a todos los
            pendientes, incluido uno que haya aparecido después de que arrancó,
            porque los recalcula al empezar.

            - `no-disponible` — no se pudo lanzar. El diagnóstico que estás
            leyendo es válido igual; lo que falta es el trabajo de Emissa, y es
            nuestro problema, no tuyo. Reintentar la verificación más tarde es
            lo correcto.


            El campo existía y **no estaba documentado**: un integrador recibió
            `no-disponible` y no tenía cómo distinguir "no aplica acá" de
            "falló". Era lo segundo.
        ticketVigenteDesde:
          type: string
          format: date-time
          example: '2026-08-14T18:00:00.000Z'
          description: >-
            Cuándo se emitió el ticket de acceso de ARCA que está vigente.


            **Es la fecha que desempata** un `DELEGACION_NO_VISIBLE`: la lista
            de quiénes autorizaron a Emissa viaja adentro del ticket, así que
            una autorización hecha **después** de este instante todavía no puede
            verse. Si se hizo antes, falta de verdad.
        ticketVigenteHasta:
          type: string
          format: date-time
          example: '2026-08-15T06:00:00.000Z'
          description: >-
            Cuándo vence ese ticket: 12 horas después de emitido, y no se puede
            forzar ni revocar.


            **No es cuándo volver a preguntar** — para eso está
            `reintentarDesde`, que llega unas once horas y tres cuartos antes.
            Esta descripción decía lo contrario, y era el texto anterior a que
            `reintentarDesde` existiera: un integrador siguió el campo
            equivocado y programó una espera de medio día para algo que tarda
            quince minutos.


            Sólo viaja con `DELEGACION_NO_VISIBLE`, que es el único estado que
            el ticket explica.
        reintentarDesde:
          type: string
          format: date-time
          example: '2026-08-21T17:37:35.919Z'
          description: >-
            Cuándo vuelve a tener sentido preguntar. **No es
            `ticketVigenteHasta`**, y confundirlos cuesta medio día de espera.


            El vencimiento es el **techo**: el ticket se intenta renovar mucho
            antes, a los quince minutos de emitido, y un ticket nuevo trae la
            lista de delegaciones actual. Antes de este instante, verificar **no
            le pregunta nada a ARCA** — devuelve el ticket guardado sin salir a
            la red.


            Medido: trámite en ARCA a las 14:28, ticket vigente de las 14:22:35,
            estado cambiado a las 14:43. **Quince minutos**, no las doce horas
            que sugería el vencimiento.


            Puede estar en el pasado, y ahí significa que ya se intenta en cada
            verificación. Renovar igual puede fallar: ARCA se niega a emitir un
            ticket nuevo mientras el anterior siga dentro de su ventana, y en
            ese caso se sigue usando el vigente.
        firmadoCon:
          type: string
          enum:
            - propio
            - plataforma
          example: plataforma
          description: >-
            **Con qué certificado se le habló a ARCA**, y por lo tanto qué
            estados son siquiera posibles para este contribuyente.


            - `plataforma` — firma el certificado de Emissa en su nombre. La
            delegación importa y **todos** los estados pueden ocurrir.

            - `propio` — el contribuyente cargó el suyo. Autentica **como sí
            mismo**: no hay delegación en juego, así que `DELEGACION_NO_VISIBLE`
            **no puede pasarle** y revocar una autorización en ARCA no cambia
            nada para él.


            Existe porque el diagnóstico contestaba `CONECTADO` sin decir **cómo
            llegó ahí**: los dos mecanismos se veían idénticos. Un integrador
            construyó una prueba sobre esa confusión y encuestó cinco horas
            esperando un cambio que era imposible.
        padron:
          description: >-
            **Lo que ARCA dice de este contribuyente hoy**, para poder detectar
            que se desfasó de lo que está guardado.


            Si alguien pasa de monotributo a responsable inscripto y no avisa,
            la conexión **no se rompe** —el estado sigue `CONECTADO`, la
            delegación no tiene nada que ver— y se le sigue emitiendo la letra
            vieja hasta que se lo objetan. Comparando estos dos campos contra
            los guardados, se ve.


            La comparación es de quien integra: acá viaja el hecho, no la
            conclusión.


            **Sólo en `PRODUCCION` y sólo si el padrón contestó.** En
            homologación no se consulta a propósito: ese padrón no describe al
            contribuyente real, y afirmar algo sobre su situación fiscal con
            datos de prueba sería peor que no decir nada.
          allOf:
            - $ref: '#/components/schemas/DatosDelPadronDto'
        verificadoEn:
          type: string
          format: date-time
          example: '2026-08-11T20:15:00.000Z'
          description: >-
            Cuándo se preguntó. La consulta saltea el cache, así que refleja lo
            que ARCA decía en este instante.
      required:
        - estado
        - detalle
        - verificadoEn
    ErrorDto:
      type: object
      properties:
        statusCode:
          type: number
          example: 401
          description: >-
            El mismo código HTTP de la respuesta. Va repetido en el cuerpo para
            que un cliente que sólo loguea el JSON no pierda el dato.
        errorCode:
          type: string
          example: UNAUTHORIZED
          description: >-
            Siempre una entrada del catálogo de errores. Es el campo por el que
            se ramifica.
        message:
          type: string
          example: No autorizado
          description: '**Siempre un string**, nunca un arreglo.'
        details:
          description: >-
            Detalle adicional. Lo contractual viaja siempre; lo de diagnóstico
            **no viaja en producción**. No ramifiques por este campo.
          oneOf:
            - type: string
            - type: object
              additionalProperties: true
        retryable:
          type: boolean
          example: false
          description: >-
            **¿Tiene sentido reintentar?** Si viene, le gana a cualquier
            heurística basada en el status.


            Existe porque el status no alcanza: `ARCA_TICKET_ALREADY_ISSUED` es
            un 409 que **se resuelve esperando**, y
            `ARCA_COMPUTER_NOT_AUTHORIZED` es un 502 que **no se arregla nunca
            esperando** — hay que autorizar el certificado en ARCA. Un cliente
            que ramifique sólo por 4xx/5xx acierta al revés en los dos.


            **Ausente significa "no lo sabemos"**, y es distinto de `false`: ahí
            corresponde la heurística de siempre. Sólo se declara donde la
            respuesta es certera.
        retryAfterSeconds:
          type: number
          example: 43200
          description: >-
            Cuántos segundos esperar antes de reintentar. **Sólo cuando el
            número es real** —el vencimiento de un ticket, la ventana de un
            límite—, nunca una estimación.


            El 429 y el bloqueo del segundo factor ya lo mandaban como
            `details.reintentarEnSegundos`, en castellano y anidado. Sigue ahí
            para no romper a nadie, y ahora **también arriba**, con el nombre
            del resto del sobre.
        requestId:
          type: string
          example: 0f2c1a5e-1d9b-4c7a-9a55-7f1a2b3c4d5e
          description: >-
            El mismo valor de la cabecera `X-Request-Id`. Es lo que hay que
            pegar en un ticket.
        timestamp:
          type: string
          example: '2026-07-26T18:30:00.000Z'
          description: Cuándo falló, en UTC. Sirve para ubicarlo en los logs.
        path:
          type: string
          example: /v1/invoices/authorize
          description: La ruta que se pidió, sin el host ni la query.
      required:
        - statusCode
        - errorCode
        - message
        - timestamp
        - path
    DatosDelPadronDto:
      type: object
      properties:
        legalName:
          type: string
          nullable: true
          example: LIOTTA CAMILO
          description: La razón social según ARCA. Se compara contra `legalName`.
        vatStatus:
          type: string
          nullable: true
          example: Responsable Inscripto
          description: >-
            La condición frente al IVA **como emisor**, en el mismo vocabulario
            que `vatStatus`. `null` cuando el padrón no permite deducirla: no se
            adivina, porque esa deducción decide la letra del comprobante.
      required:
        - legalName
        - vatStatus
  securitySchemes:
    api-key:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        API key de integración. **El modo va en el prefijo**: `emi_test_` opera
        contra homologación, `emi_live_` contra producción. Después, 64
        hexadecimales.
    panel-jwt:
      scheme: bearer
      bearerFormat: JWT
      type: http
      description: Sesión del panel. **No** es donde va la API key.

````

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