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

# Descongelar una membresía

> Descongela una membresía hoy, o anula un congelamiento programado que todavía no empezó, igual que desde Craqui.
Si la vuelta cae después del fin del período, se abre un período nuevo con sus créditos y, si el plan cobra, su
cuota: la respuesta lo dice con `period_renewed` y `renewal_fee_id`. No le avisa nada al cliente: `notify`
responde `400 notify_not_supported`. Con `dry_run=true` responde `200` con la vista previa.

Rechazos (`409`): `membership_not_frozen`. Una membresía que no es de tu negocio responde `404 not_found`.

Una escritura de plata: lleva `Idempotency-Key` (salvo con `dry_run=true`), solo para estudios (en una academia
responde `wrong_vertical`), solo si tu negocio tiene habilitada la escritura por la API (`write_access_disabled`
si no) y además las escrituras de plata (`money_writes_not_enabled` si no). Cuenta para el límite de escrituras
(30 por minuto) y para el de escrituras de plata (10 por minuto; una vista previa no cuenta para este). Un campo
que el cuerpo no declara responde `validation_failed` con `unknown_field`. Un reintento con la misma
`Idempotency-Key`, también después de las 24 horas, responde lo que ya se hizo, con `Idempotent-Replayed: true`.




## OpenAPI

````yaml /estudios/openapi.yaml post /memberships/{id}/unfreeze
openapi: 3.1.0
info:
  title: API de Craqui
  version: '2026-12-01'
  summary: Conecta tu web o tu sistema a tu estudio o academia en Craqui.
  description: >
    API REST de Craqui. Cada clave pertenece a un solo negocio (un estudio o una
    academia) y todo lo

    que devuelve es de ese negocio.


    - **Autenticación:** `Authorization: Bearer <clave>`. Las claves se crean en
    Configuración →
      Integraciones. Una app de terceros usa, en lugar de una clave, el access token (`crq_oat_…`) que recibe
      cuando el propietario de un negocio la conecta (OAuth 2.1): vale para ese negocio y los permisos que el
      propietario aceptó. Una clave `crq_live_` usa los datos reales del negocio; una `crq_test_` usa su
      estudio de prueba: la misma configuración (sedes, clases, packs, planes) con clientes y
      reservas de ejemplo, que no le escribe a nadie. La URL es la misma: el entorno lo decide la
      clave. Todo objeto dice en `livemode` de cuál de los dos viene.
    - **Versión:** cada clave queda fijada a la versión vigente cuando se creó.
    El header
      `Craqui-Version` la sobrescribe por pedido, y la respuesta siempre dice cuál se usó.
    - **Errores:** `application/problem+json` (RFC 9457), con un `code` estable
    para programar y un
      `request_id` para buscar el pedido en tu panel.
    - **Montos:** enteros en `amount_cents` (siempre ×100) junto con la moneda
    ISO 4217.

    - **Fechas y horas:** instantes en RFC 3339 con el offset del negocio;
    fechas sin hora como
      `YYYY-MM-DD` en la fecha local del negocio.
  contact:
    name: Craqui
    url: https://craqui.com
servers:
  - url: https://api.craqui.com/v1
    description: Producción
security:
  - bearerAuth: []
tags:
  - name: business
    description: El negocio al que pertenece la clave (un estudio o una academia).
  - name: booking_policy
    description: Las reglas de reserva del estudio. Solo estudios.
  - name: centers
    description: Las sedes del estudio. Solo estudios.
  - name: rooms
    description: Los salones de cada sede. Solo estudios.
  - name: teachers
    description: Quienes dan clases en el estudio. Solo estudios.
  - name: courses
    description: Las disciplinas del estudio (Yoga, Pilates…). Solo estudios.
  - name: classes
    description: Las clases con su horario semanal. Solo estudios.
  - name: sessions
    description: El horario del estudio, sesión por sesión, con sus lugares. Solo estudios.
  - name: clients
    description: >-
      Los clientes del estudio, con el contacto separado en su propio permiso.
      Solo estudios.
  - name: bookings
    description: Las reservas de las sesiones. Solo estudios.
  - name: credit_packs
    description: Los packs de créditos que vende el estudio. Solo estudios.
  - name: membership_plans
    description: Los planes de membresía que vende el estudio. Solo estudios.
  - name: intro_offers
    description: Las ofertas de bienvenida que vende el estudio. Solo estudios.
  - name: credits
    description: Los créditos de los clientes, sus lotes y sus movimientos. Solo estudios.
  - name: memberships
    description: Las membresías de los clientes y sus congelamientos. Solo estudios.
  - name: fees
    description: Las cuotas que el estudio le cobra a cada cliente. Solo estudios.
  - name: payments
    description: Los pagos de las cuotas. Solo estudios.
  - name: refunds
    description: Los reembolsos de los pagos. Solo estudios.
  - name: events
    description: >
      Lo que cambió en el estudio en los últimos 30 días, para enterarte sin
      consultar cada lista. Solo

      estudios.
  - name: webhook_endpoints
    description: >
      Las URLs a las que Craqui le avisa cada vez que algo cambia en el estudio,
      firmado y con reintentos. Solo

      estudios y solo con una clave `crq_live_`.
paths:
  /memberships/{id}/unfreeze:
    post:
      tags:
        - memberships
      summary: Descongelar una membresía
      description: >
        Descongela una membresía hoy, o anula un congelamiento programado que
        todavía no empezó, igual que desde Craqui.

        Si la vuelta cae después del fin del período, se abre un período nuevo
        con sus créditos y, si el plan cobra, su

        cuota: la respuesta lo dice con `period_renewed` y `renewal_fee_id`. No
        le avisa nada al cliente: `notify`

        responde `400 notify_not_supported`. Con `dry_run=true` responde `200`
        con la vista previa.


        Rechazos (`409`): `membership_not_frozen`. Una membresía que no es de tu
        negocio responde `404 not_found`.


        Una escritura de plata: lleva `Idempotency-Key` (salvo con
        `dry_run=true`), solo para estudios (en una academia

        responde `wrong_vertical`), solo si tu negocio tiene habilitada la
        escritura por la API (`write_access_disabled`

        si no) y además las escrituras de plata (`money_writes_not_enabled` si
        no). Cuenta para el límite de escrituras

        (30 por minuto) y para el de escrituras de plata (10 por minuto; una
        vista previa no cuenta para este). Un campo

        que el cuerpo no declara responde `validation_failed` con
        `unknown_field`. Un reintento con la misma

        `Idempotency-Key`, también después de las 24 horas, responde lo que ya
        se hizo, con `Idempotent-Replayed: true`.
      operationId: unfreezeMembership
      parameters:
        - $ref: '#/components/parameters/CraquiVersion'
        - $ref: '#/components/parameters/MembershipIdPath'
        - $ref: '#/components/parameters/IdempotencyKey'
        - $ref: '#/components/parameters/DryRun'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MembershipUnfreezeRequest'
            example: {}
      responses:
        '200':
          description: >-
            La membresía como quedó, o con `dry_run=true` la vista previa
            (`object` dice cuál).
          headers:
            Request-Id:
              $ref: '#/components/headers/RequestId'
            Craqui-Version:
              $ref: '#/components/headers/CraquiVersion'
            RateLimit:
              $ref: '#/components/headers/RateLimit'
            RateLimit-Policy:
              $ref: '#/components/headers/RateLimitPolicy'
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/MembershipUnfrozen'
                  - $ref: '#/components/schemas/PreviewMoneyWrite'
              example:
                id: mem_01j1q27t44qvr6v2s50cv4rbvq
                object: membership
                livemode: true
                client_id: cli_01jatfha5h4nkg77c3wfrvd3cw
                membership_plan_id: mpl_01jt05nvk11xa1zm1pwve2qj7r
                status: active
                current_period_start: '2026-11-14T00:00:00-03:00'
                current_period_end: '2026-12-14T00:00:00-03:00'
                renews_at: '2026-12-14T00:00:00-03:00'
                next_credit_date: '2026-12-14'
                started_at: '2025-09-14T00:00:00-03:00'
                cancelled_at: null
                suspension_reason: null
                discount: null
                active_freeze_id: null
                created_at: '2025-09-10T18:40:00-03:00'
                updated_at: '2026-12-01T11:50:08-03:00'
                period_renewed: false
                renewal_fee_id: null
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '422':
          $ref: '#/components/responses/UnprocessableEntity'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - bearerAuth: []
        - oauth2:
            - memberships:write
components:
  parameters:
    CraquiVersion:
      name: Craqui-Version
      in: header
      required: false
      description: >
        Versión de la API para este pedido (`YYYY-MM-DD`). Si no la mandas, se
        usa la versión fijada en

        la clave cuando se creó.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
        examples:
          - '2026-12-01'
    MembershipIdPath:
      name: id
      in: path
      required: true
      description: El id de la membresía (`mem_…`).
      schema:
        $ref: '#/components/schemas/MembershipId'
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: >
        Obligatorio en todo pedido que crea o cambia algo, salvo con
        `dry_run=true`. Un valor único por

        operación (por ejemplo un UUID). Si repites el pedido con la misma clave
        y el mismo cuerpo dentro de 24

        horas, recibes la misma respuesta (con el header `Idempotent-Replayed:
        true`) sin que se repita el efecto,

        también la misma respuesta de error: para intentar de nuevo algo que se
        rechazó, usa una clave nueva.

        Con un `dry_run=true` la clave se ignora y no se guarda: el pedido real
        puede usar la misma. Pasadas las 24

        horas, repetir una reserva con la misma clave y el mismo cuerpo sigue
        devolviendo la reserva que se hizo

        (como está ahora), y la misma clave con otro cuerpo responde
        `idempotency_key_reused`.
      schema:
        type: string
        minLength: 1
        maxLength: 255
    DryRun:
      name: dry_run
      in: query
      required: false
      description: >
        `true` para ver qué pasaría sin hacerlo: Craqui evalúa el pedido con las
        mismas reglas que el real y

        responde `200` con un objeto `preview`, sin escribir nada ni avisarle a
        nadie. Cuenta para el límite de

        escrituras. `false` (o sin el parámetro) hace el pedido real. Es el
        único parámetro de consulta de una

        escritura.
      schema:
        type: boolean
  schemas:
    MembershipUnfreezeRequest:
      type: object
      additionalProperties: false
      description: Sin campos.
      properties: {}
    MembershipUnfrozen:
      description: La membresía como quedó, con lo que pasó con su período.
      allOf:
        - $ref: '#/components/schemas/Membership'
        - type: object
          required:
            - period_renewed
            - renewal_fee_id
          properties:
            period_renewed:
              type: boolean
              description: >-
                `true` si la vuelta cayó después del fin del período y se abrió
                uno nuevo, con sus créditos.
            renewal_fee_id:
              description: >-
                La cuota del período nuevo (`fee_…`), si el plan cobra; `null`
                si no se abrió uno o no cobra.
              oneOf:
                - $ref: '#/components/schemas/FeeId'
                - type: 'null'
    PreviewMoneyWrite:
      type: object
      description: >
        Lo que haría la escritura de plata, sin hacerla: Craqui corre la misma
        operación que el pedido real y la

        deshace, así que una vista previa que se rechazaría responde el mismo
        error (por ejemplo `price_changed` o

        `amount_mismatch`, con el monto de hoy). No consume la `Idempotency-Key`
        ni cuenta para el límite de escrituras

        de plata.
      required:
        - object
        - livemode
        - action
        - result
        - effects
        - warnings
        - summary
      properties:
        object:
          type: string
          const: preview
        livemode:
          type: boolean
          description: >-
            `false` si la vista previa es de tu estudio de prueba (claves
            `crq_test_`); `true` con claves `crq_live_`.
        action:
          type: string
          description: >
            `credit_grant.create`, `credit_grant.revoke`, `membership.create`,
            `membership.cancel`, `membership.freeze`,

            `membership.unfreeze` o `payment.create`. La lista puede crecer.
          enum:
            - credit_grant.create
            - credit_grant.revoke
            - membership.create
            - membership.cancel
            - membership.freeze
            - membership.unfreeze
            - payment.create
          x-extensible-enum: true
        result:
          description: El objeto como está hoy; `null` si la escritura lo crearía.
          oneOf:
            - $ref: '#/components/schemas/CreditGrant'
            - $ref: '#/components/schemas/Membership'
            - type: 'null'
        effects:
          $ref: '#/components/schemas/PreviewMoneyEffects'
        warnings:
          type: array
          description: >-
            `no_change` (la baja ya estaba programada: el pedido real no cambia
            nada). La lista puede crecer.
          items:
            type: string
            enum:
              - no_change
            x-extensible-enum: true
        summary:
          type: string
          description: >-
            Lo mismo en una frase en español, para mostrarle a una persona. No
            nombra al cliente.
    MembershipId:
      type: string
      description: Id de una membresía de un cliente.
      pattern: ^mem_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - mem_01j1q27t44qvr6v2s50cv4rbvq
    Membership:
      type: object
      description: >
        La membresía de un cliente a un plan. Una membresía anulada o una compra
        que no se terminó no existen

        para la API: no se listan y su id responde `not_found`.
      required:
        - id
        - object
        - livemode
        - client_id
        - membership_plan_id
        - status
        - current_period_start
        - current_period_end
        - renews_at
        - next_credit_date
        - started_at
        - cancelled_at
        - suspension_reason
        - discount
        - active_freeze_id
        - created_at
        - updated_at
      properties:
        id:
          $ref: '#/components/schemas/MembershipId'
        object:
          type: string
          const: membership
        livemode:
          type: boolean
          description: >-
            `false` si el objeto es de tu estudio de prueba (claves
            `crq_test_`); `true` con claves `crq_live_`.
        client_id:
          description: >-
            El cliente. `null` si la membresía es de alguien que no es cliente
            del estudio.
          oneOf:
            - $ref: '#/components/schemas/ClientId'
            - type: 'null'
        membership_plan_id:
          $ref: '#/components/schemas/MembershipPlanId'
        status:
          type: string
          description: >
            `active`, `paused` (congelada), `suspended`, `pending_payment`,
            `cancelled` o `expired`. La lista

            puede crecer: trata un valor que no conoces como una membresía que
            no da créditos.
          enum:
            - active
            - paused
            - suspended
            - pending_payment
            - cancelled
            - expired
          x-extensible-enum: true
        current_period_start:
          $ref: '#/components/schemas/Instant'
        current_period_end:
          $ref: '#/components/schemas/Instant'
        renews_at:
          description: >
            Cuándo se renueva sola: el fin del período actual si la membresía
            está `active` y su plan se

            renueva solo; si no, `null`.
          oneOf:
            - $ref: '#/components/schemas/Instant'
            - type: 'null'
        next_credit_date:
          description: >-
            La fecha local en que recibe los próximos créditos; `null` si no hay
            una prevista.
          oneOf:
            - $ref: '#/components/schemas/LocalDate'
            - type: 'null'
        started_at:
          $ref: '#/components/schemas/Instant'
        cancelled_at:
          oneOf:
            - $ref: '#/components/schemas/Instant'
            - type: 'null'
        suspension_reason:
          type:
            - string
            - 'null'
          description: Por qué se suspendió, como texto; `null` si no está suspendida.
        discount:
          description: El descuento que tiene la membresía; `null` si no tiene.
          oneOf:
            - $ref: '#/components/schemas/MembershipDiscount'
            - type: 'null'
        active_freeze_id:
          description: >-
            El congelamiento en curso o programado; `null` si no hay. El detalle
            está en `GET /v1/memberships/{id}/freezes`.
          oneOf:
            - $ref: '#/components/schemas/MembershipFreezeId'
            - type: 'null'
        created_at:
          $ref: '#/components/schemas/Instant'
        updated_at:
          $ref: '#/components/schemas/Instant'
    FeeId:
      type: string
      description: Id de una cuota.
      pattern: ^fee_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - fee_01jxycdc0zf2nxw7n2azer6wqp
    CreditGrant:
      type: object
      description: >
        Un lote de créditos de un cliente: un pack que compró, una oferta de
        bienvenida o los créditos de un

        período de su membresía. Los créditos se usan del lote que vence
        primero.
      required:
        - id
        - object
        - livemode
        - client_id
        - kind
        - credit_pack_id
        - credits_granted
        - credits_remaining
        - granted_at
        - revoked_at
        - expires_at
        - status
        - course_ids
        - fee_id
        - price
        - created_at
      properties:
        id:
          $ref: '#/components/schemas/CreditGrantId'
        object:
          type: string
          const: credit_grant
        livemode:
          type: boolean
          description: >-
            `false` si el objeto es de tu estudio de prueba (claves
            `crq_test_`); `true` con claves `crq_live_`.
        client_id:
          description: >-
            El cliente. `null` si el lote es de alguien que no es cliente del
            estudio (por ejemplo, alguien del equipo).
          oneOf:
            - $ref: '#/components/schemas/ClientId'
            - type: 'null'
        kind:
          type:
            - string
            - 'null'
          description: >
            `pack`, `intro_offer` o `membership_period`. `null` solo en lotes
            muy viejos sin tipo. La lista

            puede crecer.
          enum:
            - pack
            - intro_offer
            - membership_period
            - null
          x-extensible-enum: true
        credit_pack_id:
          description: El pack que se compró, solo si `kind` es `pack`.
          oneOf:
            - $ref: '#/components/schemas/CreditPackId'
            - type: 'null'
        credits_granted:
          type: integer
          description: Los créditos que dio el lote.
        credits_remaining:
          type: integer
          description: Los créditos que le quedan.
        granted_at:
          $ref: '#/components/schemas/Instant'
        revoked_at:
          description: Cuándo el estudio anuló el lote; `null` si no se anuló.
          oneOf:
            - $ref: '#/components/schemas/Instant'
            - type: 'null'
        expires_at:
          description: >
            Cuándo vencen los créditos, con los días de gracia ya sumados.
            `null` si no vencen.
          oneOf:
            - $ref: '#/components/schemas/Instant'
            - type: 'null'
        status:
          type: string
          description: >
            `revoked` si se anuló; si no, `expired` si venció; si no, `depleted`
            si no le quedan créditos; si

            no, `active`. La lista puede crecer: trata un valor que no conoces
            como un lote que no se puede usar.
          enum:
            - active
            - depleted
            - expired
            - revoked
          x-extensible-enum: true
        course_ids:
          description: >-
            Las disciplinas en las que sirven los créditos; `null` si sirven en
            todas.
          type:
            - array
            - 'null'
          items:
            $ref: '#/components/schemas/CourseId'
        fee_id:
          description: >-
            La cuota con la que se cobró el lote; `null` si no tiene (por
            ejemplo, créditos de regalo).
          oneOf:
            - $ref: '#/components/schemas/FeeId'
            - type: 'null'
        price:
          description: Lo que se pagó por el lote; `null` si no se registró un precio.
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        created_at:
          $ref: '#/components/schemas/Instant'
    PreviewMoneyEffects:
      type: object
      description: >-
        Lo que pasaría con la plata, los créditos y los avisos. Cada acción trae
        sus campos.
      required:
        - notifies_client
      properties:
        notifies_client:
          type: boolean
          description: '`true` si al cliente le llegaría un aviso.'
        credits_added:
          type: integer
          description: Créditos que se cargarían (packs y membresías).
        expires_at:
          type:
            - string
            - 'null'
          description: Cuándo vencerían los créditos del pack.
        fee:
          description: >-
            La cuota que se crearía para el pack; `null` si el pack no tiene
            precio.
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        no_fee_reason:
          type:
            - string
            - 'null'
          enum:
            - no_charge
            - null
        bookings_cancelled:
          type: integer
          description: Reservas que se cancelarían.
        status:
          type: string
          description: >-
            El estado con que nacería la membresía (`active`, o
            `pending_payment` si empieza más adelante).
        period_start:
          type:
            - string
            - 'null'
          description: Inicio del primer período.
        period_end:
          type:
            - string
            - 'null'
          description: Fin del primer período.
        first_fee:
          $ref: '#/components/schemas/Money'
        first_fee_due_date:
          type:
            - string
            - 'null'
          description: Vencimiento de la primera cuota.
        next_fee:
          description: >-
            La cuota del período siguiente, si el estudio la emite por
            adelantado.
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        changed:
          type: boolean
          description: '`false` si la baja ya estaba programada.'
        ends_at_paid_period_end:
          type: boolean
          description: >-
            `true` si una baja de hoy termina al cierre del período siguiente
            porque ya está pagado.
        mode:
          type: string
          enum:
            - created
            - end_changed
          description: >-
            `created`: un congelamiento nuevo; `end_changed`: cambia el día de
            vuelta del que está abierto.
        freeze:
          type: string
          enum:
            - active
            - scheduled
          description: >-
            `active`: se descongela hoy; `scheduled`: se anula un congelamiento
            programado.
        period_renewed:
          type: boolean
          description: >-
            `true` si la vuelta abriría un período nuevo, con sus créditos y, si
            el plan cobra, su cuota.
        amount:
          $ref: '#/components/schemas/Money'
        paid_on:
          $ref: '#/components/schemas/LocalDate'
        fee_status_after:
          type: string
          description: >-
            El estado de la cuota después del pago (`paid_on_time` o
            `paid_late`).
        membership_activated:
          type: boolean
          description: >-
            `true` si el pago activaría una membresía que esperaba su primer
            pago.
        membership_reactivated:
          type: boolean
          description: '`true` si el pago reactivaría una membresía suspendida.'
        receipt:
          type: boolean
          description: '`true` si se le mandaría el recibo al cliente.'
        einvoice:
          type: boolean
          description: >-
            `true` si se emitiría la factura electrónica (`notify: false` nunca
            la apaga).
        notifications_not_suppressed:
          type: array
          description: 'Avisos que `notify: false` no apagaría (`membership_reactivated`).'
          items:
            type: string
    Problem:
      type: object
      description: >
        Un error. Programa contra `code`, que no cambia; `title` y `detail` son
        para mostrarle a una

        persona y pueden cambiar de redacción.
      required:
        - type
        - title
        - status
        - code
        - request_id
      properties:
        type:
          type: string
          format: uri
          description: URL de la página de la documentación de este error.
          examples:
            - https://docs.craqui.com/errors/invalid_api_key
        title:
          type: string
          description: Resumen corto, en español.
          examples:
            - La clave no es válida
        status:
          type: integer
          description: El mismo código HTTP de la respuesta.
          examples:
            - 401
        code:
          type: string
          description: >
            Código estable del error. La lista puede crecer: trata un `code` que
            no conoces como un

            error genérico de su `status`.
          examples:
            - invalid_api_key
        detail:
          type: string
          description: Qué pasó y qué hacer, en español.
          examples:
            - >-
              Revisa que copiaste la clave completa, incluido el prefijo
              (crq_live_ o crq_test_).
        request_id:
          type: string
          pattern: ^req_[0-9a-hjkmnp-tv-z]{26}$
          description: >-
            Identificador del pedido. Búscalo en Configuración → Integraciones →
            Actividad de la API.
          examples:
            - req_01hzx8m4k2a7d9q3f5w6y8z0bc
        doc_url:
          type: string
          format: uri
          description: >-
            Igual a `type`, por compatibilidad con clientes que buscan este
            campo.
        required_scope:
          type: string
          description: >-
            Solo en `scope_required` y `event_type_not_permitted`. El permiso
            que le falta a la clave.
          examples:
            - business:read
        errors:
          type: array
          description: Solo en errores de validación. Un elemento por campo con problemas.
          items:
            $ref: '#/components/schemas/FieldError'
        current_price:
          $ref: '#/components/schemas/Money'
          description: >-
            Solo en `price_changed`. El precio de hoy, en centésimos y con su
            moneda.
        expected_amount:
          $ref: '#/components/schemas/Money'
          description: >-
            Solo en `amount_mismatch`. El saldo de hoy de la cuota, en
            centésimos y con su moneda.
        amount_at_stake:
          $ref: '#/components/schemas/Money'
          description: >-
            Solo en `advance_payment_choice_required`. Lo que falta cobrar del
            pago por adelantado.
        existing_membership_id:
          $ref: '#/components/schemas/MembershipId'
          description: >
            Solo en `active_membership_exists`, `membership_suspended`,
            `membership_paused` y `membership_scheduled` al

            asignar una membresía: la que el cliente ya tiene.
        booking_ids:
          type: array
          description: >-
            Solo en `bookings_in_freeze_window` y `bookings_changed`. Las
            reservas en esas fechas.
          items:
            $ref: '#/components/schemas/BookingId'
    ClientId:
      type: string
      description: Id de un cliente del estudio.
      pattern: ^cli_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - cli_01jatfha5h4nkg77c3wfrvd3cw
    MembershipPlanId:
      type: string
      description: Id de un plan de membresía.
      pattern: ^mpl_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - mpl_01jt05nvk11xa1zm1pwve2qj7r
    Instant:
      type: string
      format: date-time
      description: Instante en RFC 3339 con el offset del negocio en ese momento.
      examples:
        - '2026-12-02T19:00:00-03:00'
    LocalDate:
      type: string
      format: date
      description: Fecha local del negocio, `YYYY-MM-DD`.
      pattern: ^\d{4}-\d{2}-\d{2}$
      examples:
        - '2026-12-02'
    MembershipDiscount:
      type: object
      required:
        - percent
        - fixed_amount
        - periods_remaining
      properties:
        percent:
          type:
            - number
            - 'null'
          description: >-
            El porcentaje de descuento (por ejemplo `15` es 15 %); `null` si es
            un monto fijo.
        fixed_amount:
          description: >-
            El monto fijo que se descuenta de cada cuota; `null` si es un
            porcentaje.
          oneOf:
            - $ref: '#/components/schemas/Money'
            - type: 'null'
        periods_remaining:
          type:
            - integer
            - 'null'
          description: Cuántos períodos más se aplica; `null` si se aplica siempre.
    MembershipFreezeId:
      type: string
      description: Id de un congelamiento de una membresía.
      pattern: ^frz_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - frz_01jtk6e3z2vm9ych2cc7kadyja
    CreditGrantId:
      type: string
      description: >-
        Id de un lote de créditos (un pack, una oferta o el período de una
        membresía).
      pattern: ^cgr_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - cgr_01jdwsxvjgc7y12y0h12f43149
    CreditPackId:
      type: string
      description: Id de un pack de créditos.
      pattern: ^cpk_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - cpk_01j63ec7mff1rr7pbk3z1b8ke1
    CourseId:
      type: string
      description: Id de una disciplina.
      pattern: ^crs_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - crs_01jff81dk7rxtacmye5nanfp0h
    Money:
      type: object
      required:
        - amount_cents
        - currency
      properties:
        amount_cents:
          type: integer
          description: >
            El monto multiplicado por 100. `250000` con `UYU` es $ 2.500,00. Un
            entero: `250000.5` o `"250000"` es

            `validation_failed` (`250000.0` es el mismo número que `250000`).
          examples:
            - 250000
        currency:
          $ref: '#/components/schemas/CurrencyCode'
    FieldError:
      type: object
      required:
        - field
        - code
      properties:
        field:
          type: string
          description: El campo, como lo mandaste (por ejemplo `limit`).
          examples:
            - limit
        code:
          type: string
          description: Qué tiene mal el campo.
          examples:
            - invalid_limit
        detail:
          type: string
          description: Explicación en español.
    BookingId:
      type: string
      description: Id de una reserva.
      pattern: ^bkg_[0-7][0-9a-hjkmnp-tv-z]{25}$
      examples:
        - bkg_01jx9eak0437zdf0yw0atg7g0r
    CurrencyCode:
      type: string
      description: Moneda ISO 4217, en mayúsculas.
      pattern: ^[A-Z]{3}$
      examples:
        - UYU
  headers:
    RequestId:
      description: Identificador del pedido. Aparece también en el cuerpo de los errores.
      schema:
        type: string
        pattern: ^req_[0-9a-hjkmnp-tv-z]{26}$
    CraquiVersion:
      description: La versión de la API con la que se respondió.
      schema:
        type: string
        pattern: ^\d{4}-\d{2}-\d{2}$
    RateLimit:
      description: >-
        Pedidos que quedan en la ventana actual y segundos hasta que se renueva
        (draft IETF httpapi-ratelimit-headers).
      schema:
        type: string
        examples:
          - '"read";r=97;t=42'
    RateLimitPolicy:
      description: Límite de la clave por ventana (draft IETF httpapi-ratelimit-headers).
      schema:
        type: string
        examples:
          - '"read";q=100;w=60'
    IdempotentReplayed:
      description: >-
        `true` cuando la respuesta es la guardada de un pedido anterior con la
        misma `Idempotency-Key`.
      schema:
        type: string
        enum:
          - 'true'
    RetryAfter:
      description: Segundos que hay que esperar antes de reintentar.
      schema:
        type: integer
        minimum: 0
  responses:
    BadRequest:
      description: >
        El pedido está mal formado. Códigos posibles: `invalid_api_version`,
        `idempotency_key_required`,

        `validation_failed` (con un elemento de `errors` por parámetro:
        `unknown_parameter`,

        `invalid_prefix`, `invalid_id`, `invalid_date`, `invalid_boolean`,
        `invalid_enum`,

        `invalid_limit`, `duplicate_parameter`, `expand_not_supported`…) e
        `invalid_cursor` (un

        `starting_after` de otra búsqueda: repite la primera página con los
        mismos filtros). En las URLs de

        avisos, `webhook_url_invalid` (en `errors`, la causa: `not_https`,
        `port_not_allowed`,

        `credentials_in_url`, `ip_literal`, `internal_hostname`,
        `private_address`, `craqui_host` o `too_long`).

        Al cancelar una sesión, `notify_not_supported` (esa escritura no lleva
        `notify`: siempre avisa); también al

        revocar un pack y al congelar o descongelar una membresía (no le avisan
        nada al cliente). En las escrituras de

        plata, `fee_decision_required` y `current_fee_choice_required` (la API
        no decide sobre plata por ti).

        Un token en la URL o en un cuerpo de formulario responde
        `invalid_request`: va solo en el header

        `Authorization`. El detalle de un error nunca repite el valor que
        mandaste.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Unauthorized:
      description: >
        Falta la clave o no sirve. Códigos posibles: `missing_api_key`,
        `invalid_api_key`,

        `revoked_api_key`, `expired_api_key`, `legacy_api_key`. Con el token de
        una app: `invalid_access_token` (no

        es válido o venció: pide otro con el refresh token) y
        `app_connection_revoked` (el propietario desconectó la

        app); los dos llevan el header `WWW-Authenticate: Bearer
        error="invalid_token"`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            revoked:
              value:
                type: https://docs.craqui.com/errors/revoked_api_key
                title: La clave fue revocada
                status: 401
                code: revoked_api_key
                detail: Crea una clave nueva en Configuración → Integraciones.
                request_id: req_01hzx8m4k2a7d9q3f5w6y8z0bc
    Forbidden:
      description: >
        La clave es válida pero no puede hacer esto. Códigos posibles:
        `scope_required`,

        `plan_required`, `api_key_suspended`, `browser_not_allowed`,
        `wrong_vertical`,

        `event_type_not_permitted` (eventos de un recurso que la clave no puede
        leer; `required_scope` dice cuál) y

        `write_access_disabled` (en una ruta que crea o cambia datos, si tu
        negocio no tiene habilitada la escritura por

        la API) y `money_writes_not_enabled` (en una ruta de packs, membresías o
        pagos, si tu negocio no tiene

        habilitadas las escrituras de plata). Con el token de una app, además:
        `app_suspended` (Craqui suspendió la

        app) y `app_connection_suspended` (quien conectó la app ya no es
        propietario del negocio).
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    NotFound:
      description: >-
        No existe, o no es de tu negocio. Código `not_found` o
        `route_not_found`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Conflict:
      description: >
        Solo con una clave `crq_test_`: tu estudio de prueba no está listo.
        Código

        `sandbox_unavailable`. Restablece tus datos de prueba en Configuración →
        Integraciones y vuelve

        a intentar. Una clave de prueba nunca responde con los datos reales del
        negocio. En los pedidos que

        crean o modifican algo, también `idempotency_in_progress` (el mismo
        pedido todavía se está procesando) y,

        al crear una URL de avisos, `webhook_endpoint_limit_reached` (ya hay
        10). En una ruta que crea o cambia datos

        del estudio, además, sus reglas no dejan hacerlo: la descripción de cada
        ruta lista sus códigos.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            sandbox_unavailable:
              value:
                type: https://docs.craqui.com/errors/sandbox_unavailable
                title: Tu estudio de prueba no está listo
                status: 409
                code: sandbox_unavailable
                detail: >-
                  Restablece tus datos de prueba en Configuración →
                  Integraciones y vuelve a intentar.
                request_id: req_01hzx8m4k2a7d9q3f5w6y8z0bc
    UnprocessableEntity:
      description: >
        El pedido se entendió pero no se puede aplicar así. Códigos posibles:
        `idempotency_key_reused` (esa

        `Idempotency-Key` ya se usó con otro pedido: genera una nueva),
        `sandbox_contact_not_allowed` y

        `sandbox_account_reserved` (en tu estudio de prueba, un contacto que no
        es ficticio). En las escrituras de

        plata, además: `currency_mismatch`, `fee_decision_mismatch`,
        `fee_action_not_available`,

        `start_option_not_applicable` y `paid_on_out_of_range`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    TooManyRequests:
      description: >-
        Se pasó el límite de pedidos por minuto de la clave. Código
        `rate_limited`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        RateLimit:
          $ref: '#/components/headers/RateLimit'
        RateLimit-Policy:
          $ref: '#/components/headers/RateLimitPolicy'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    InternalError:
      description: >-
        Falló algo de nuestro lado. Código `internal_error`. Reintenta; si
        sigue, escríbenos con el `request_id`.
      headers:
        Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: crq_live_sk_… / crq_live_rk_…
      description: >
        Clave del negocio. `sk` es una clave completa (todo lo que el rol de
        quien la creó puede dar)

        y `rk` una restringida a los permisos que se eligieron al crearla. Solo
        se usa desde un

        servidor: un pedido desde un navegador se rechaza con
        `browser_not_allowed`.
    oauth2:
      type: oauth2
      description: >
        Una app de terceros conectada a un negocio por su propietario (OAuth
        2.1, Authorization Code con PKCE). El

        access token (`crq_oat_…`) vale 1 hora, va solo en el header
        `Authorization: Bearer` y es de un solo negocio.

        Una app nunca recibe `notifications:suppress` ni `webhooks:manage`. Ver
        https://docs.craqui.com/apps.
      flows:
        authorizationCode:
          authorizationUrl: https://www.craqui.com/oauth/authorize
          tokenUrl: https://api.craqui.com/oauth/token
          refreshUrl: https://api.craqui.com/oauth/token
          scopes:
            business:read: Ver los datos básicos del negocio (siempre incluido).
            centers:read: Ver las sedes.
            rooms:read: Ver los salones.
            teachers:read: Ver a quienes dan clases.
            courses:read: Ver las disciplinas.
            classes:read: Ver las clases y su horario.
            sessions:read: Ver el horario, sesión por sesión.
            sessions:write: Cancelar sesiones.
            clients:read: Ver los clientes.
            clients.pii:read: Ver el email y el teléfono de los clientes.
            clients:write: Crear, cambiar, dar de baja y reactivar clientes.
            bookings:read: Ver las reservas.
            bookings:write: Reservar, cancelar reservas y marcar asistencia.
            credits:read: Ver los créditos de los clientes.
            credits:write: Asignar y revocar packs de créditos.
            memberships:read: Ver las membresías.
            memberships:write: Asignar, cancelar, congelar y descongelar membresías.
            fees:read: Ver las cuotas.
            payments:read: Ver los pagos.
            refunds:read: Ver los reembolsos.
            payments:write: Registrar pagos.
            events:read: Ver la lista de cambios recientes.

````

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