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

# Sincronizar movimientos (cursor)

> Sin cursor recorre la historia visible; con cursor entrega `added`, `modified` y `removed` desde la última llamada. El cursor es opaco.

Mantén una copia fiel de los movimientos sin releer rangos. Sin cursor, la
primera llamada recorre la historia visible de tu API key, página a página;
cuando se agota, el `next_cursor` pasa a apuntar al registro de cambios y cada
llamada siguiente entrega solo lo que cambió desde la anterior.

```bash theme={null}
# Primera vez: sin cursor
curl "https://api.tupana.ai/v2/movements/sync/?limit=200" \
  -H "Authorization: Api-Key TU_API_KEY"

# Después: siempre con el next_cursor de la respuesta anterior
curl "https://api.tupana.ai/v2/movements/sync/?cursor=Yzo0ODEyOjE3NjE2ODk..." \
  -H "Authorization: Api-Key TU_API_KEY"
```

```json theme={null}
{
  "added": [ { "id": "move_Qm8102938475612", "status": "posted", "...": "..." } ],
  "modified": [ { "id": "move_Qm8102938475001", "category": "customer_payment", "...": "..." } ],
  "removed": [
    { "id": "move_Qm8102938474990", "account_id": "bacc_7Hk2938475610293", "removed_at": "2026-10-28T09:12:03Z" }
  ],
  "next_cursor": "Yzo0ODEzOjE3NjE2ODk...",
  "has_more": false,
  "limit": 100
}
```

## Qué trae cada lista

* `added`: movimientos nuevos, con la [misma forma](https://docs.tupana.ai/api-reference/v2/movements/list.md) del listado.
* `modified`: movimientos que cambiaron algo visible después de creados: una
  pendiente que se posteó, una contraparte que se resolvió, una categoría nueva.
  Vienen completos; reemplaza tu copia.
* `removed`: movimientos que el banco retiró o que fueron reemplazados por una
  posteada (`posted_movement_id` en el detalle). Solo `id`, `account_id` y `removed_at`.

Un movimiento aparece **una vez por página**: si en el mismo tramo se creó y se
modificó, viene en `added`; si se retiró, solo en `removed`.

## El cursor

Es opaco: guárdalo tal cual y mándalo en la próxima llamada. Mientras
`has_more` sea `true`, sigue llamando con el `next_cursor` que recibes; cuando
sea `false`, guarda el cursor y vuelve cuando quieras (o cuando llegue
`movement.created` por webhook).

| Respuesta | Qué significa | Qué hacer |
| - | - | - |
| `400 cursor_invalid` | El cursor no es uno que emitimos. | Revisa que lo guardaste completo. |
| `410 cursor_expired` | El cursor es más antiguo que la retención de cambios (400 días): pudo perder cambios ya purgados. | Vuelve a sincronizar sin cursor y reconstruye la copia. |

`limit` va de 1 a 500 (default 100). La sincronización respeta el ambiente de tu
key: una key sandbox recorre solo el [dataset sandbox](https://docs.tupana.ai/api-reference/v2/banking/guides/sandbox.md).

> [!NOTE]
>
> Lee la guía [Sincronizar con cursor](https://docs.tupana.ai/api-reference/v2/banking/guides/sync.md)
> para la receta completa: persistencia del cursor, qué hacer ante `cursor_expired`
> y cómo tratar `pending` y `posted`.


## OpenAPI

````yaml api-reference/openapi-v2.json GET /movements/sync/
openapi: 3.0.0
info:
  title: Tupana API — v2
  description: >-
    API v2 de Tupana: facturación electrónica, cesiones, recaudación, consultas,
    boleta de honorarios y nómina. Los identificadores de recursos son public
    IDs opacos con prefijo (docu_, ment_, cred_, doce_, empl_…).
  version: 2.0.0
servers:
  - url: https://api.tupana.ai/v2
    description: Producción (v2)
security:
  - apiKeyAuth: []
paths:
  /movements/sync/:
    get:
      tags:
        - Movimientos
      summary: Sincronizar movimientos con cursor
      description: >-
        Sin cursor recorre la historia visible; con cursor entrega `added`,
        `modified` y `removed` desde la última llamada. El cursor es opaco.
      operationId: syncMovements_v2
      parameters:
        - name: cursor
          in: query
          required: false
          schema:
            type: string
          description: El `next_cursor` de la respuesta anterior.
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 500
      responses:
        '200':
          description: Cambios desde el cursor
          content:
            application/json:
              schema:
                type: object
                properties:
                  added:
                    type: array
                    items:
                      $ref: '#/components/schemas/Movement'
                  modified:
                    type: array
                    items:
                      $ref: '#/components/schemas/Movement'
                  removed:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        account_id:
                          type: string
                        removed_at:
                          type: string
                          format: date-time
                          nullable: true
                  next_cursor:
                    type: string
                  has_more:
                    type: boolean
                  limit:
                    type: integer
        '400':
          description: '`cursor_invalid`: el cursor no es uno emitido por la API'
        '403':
          description: >-
            La API key no tiene `banking:read` explícito (`code:
            banking_permission_required`)
        '410':
          description: >-
            `cursor_expired`: el cursor es más antiguo que la retención;
            resincroniza sin cursor
      security:
        - apiKeyAuth: []
components:
  schemas:
    Movement:
      type: object
      properties:
        id:
          type: string
          example: move_Qm8102938475612
        account_id:
          type: string
          example: bacc_7Hk2938475610293
        company_id:
          type: string
          example: ment_3672486436546233854
        amount:
          type: number
          example: 450000
          description: Sin signo. Entero en CLP.
        direction:
          type: string
          enum:
            - inbound
            - outbound
        currency:
          type: string
          example: CLP
        status:
          type: string
          enum:
            - pending
            - posted
            - removed
        posted_movement_id:
          type: string
          nullable: true
          description: >-
            En una pendiente retirada: el id del movimiento posteado que la
            reemplazó.
        transaction_date:
          type: string
          format: date
          nullable: true
        post_date:
          type: string
          format: date
          nullable: true
        description:
          type: string
          description: La glosa tal cual la entrega el banco.
        balance_after:
          type: number
          nullable: true
          description: >-
            Saldo de la cuenta tras el movimiento, cuando la fuente lo entrega
            (cartolas).
        counterparty:
          $ref: '#/components/schemas/Counterparty'
        category:
          type: string
          nullable: true
          enum:
            - customer_payment
            - supplier_payment
            - payroll
            - taxes
            - bank_fees
            - internal_transfer
            - card_settlement
          description: >-
            Clasificación Tupana del movimiento. `null` si no se clasificó o no
            se identificó.
        card:
          type: object
          nullable: true
          description: Solo en movimientos de una cuenta de crédito.
          properties:
            last_four:
              type: string
              example: '2105'
            merchant:
              type: string
              nullable: true
              example: AMAZON WEB SERVICES
            installments:
              type: integer
              nullable: true
              description: '`null` hasta que la fuente lo entregue.'
    Counterparty:
      type: object
      nullable: true
      description: >-
        La contraparte resuelta por Tupana. `null` cuando no hay nada que
        resolver.
      properties:
        tax_id:
          type: string
          nullable: true
          example: 76.543.169-7
        name:
          type: string
          nullable: true
          example: Comercial Andina SpA
        institution:
          type: object
          nullable: true
          properties:
            code:
              type: string
              example: cl_banco_de_chile
            name:
              type: string
              example: Banco de Chile
        account_number_mask:
          type: string
          nullable: true
          example: '0912'
          description: Últimos 4 dígitos. El número completo nunca sale.
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        API Key para autenticación. Debe proporcionarse en el header
        Authorization con el formato: 'Api-Key YOUR-API-KEY' (incluye el prefijo
        'Api-Key ' seguido de tu API key)

````

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