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

# Update a location

> Edits a location. Scope: `locations:write`.

## What's writable - and what owns the rest

| You can change | Owner of the rest |
|---|---|
| `name` | - |
| `address` / `coordinates` (interplay below) | - |
| `geo_message`, `geo_message_enabled` | - |
| `enabled_card_ids` (SET - toggle cards on/off) | - |
| POS mapping | Dashboard Integrations flow - **never via API** |
| Team assignment | `PATCH /v1/team_members/{id}` (`location_ids`) |
| Lifecycle (`is_active`) & billing | Dashboard - each location is a billing unit |

## Address ↔ coordinates

Mirrors the dashboard (type address → geocode → drag pin to fine-tune):
- **`address` alone** → server geocodes → `coordinates` + `country` update. Ungeocodable → `422 address_not_geocodable`.
- **`coordinates` alone** → the pin moves, the displayed address stays - the "drag to fine-tune" gesture as an API call.
- **Both** → stored exactly as sent, no geocode (you already resolved them - this is what the dashboard submits).

## Card toggles

`enabled_card_ids` is SET semantics: send the complete list you want active. Enabling a card pushes this location's geofence onto that card's wallet passes; disabling removes it and the card stops appearing in `cards[]` (its signup URL for this location stops accepting signups). Unknown card → `422 card_not_found`. A location must keep at least one card enabled (`422 location_needs_card`; delete the location instead). A card must stay enabled at at least one location: disabling it at its last one returns `422 card_needs_location` (pause the card to stop offering it; a paused or archived card with members also keeps one location so they can still redeem).



## OpenAPI

````yaml /openapi.json patch /v1/locations/{id}
openapi: 3.1.0
info:
  title: VEXiON cards Public API
  description: >-
    ## Rate limiting


    Two independent layers, checked in this order:


    1. **Per-IP throttle (pre-authentication).** Applied to every `/v1` request
    BEFORE the API key is checked, keyed on the caller's IP address:
       - **600 requests/minute** per IP - a flood ceiling, `429 rate_limit_error` / `rate_limited`.
       - **20 failed authentications per 15 minutes** per IP - a key-guessing ceiling. Only responses that are `401` (missing/invalid/revoked key) count against this ceiling; a `403` from a valid key that simply lacks a scope does not. `429 rate_limit_error` / `too_many_failed_attempts`.

       Both ceilings send `Retry-After` (seconds).
    2. **Per-key, per-bucket limiter (post-authentication).** Documented per
    endpoint (see each operation's `429` response and the `rate_limit` field on
    the API Key resource) - e.g. 60/min default, 120/min for transactions, plus
    the notification-broadcast and lookup-filter buckets described on their own
    endpoints.


    ### Rate-limit response headers


    Every rate-limited response carries the legacy `X-RateLimit-Limit` /
    `X-RateLimit-Remaining` / `X-RateLimit-Reset` family (the last is an **epoch
    timestamp**) AND the IETF standards-track headers, side by side:


    - `RateLimit-Policy: "<bucket>";q=<quota>;w=<window seconds>` - the static
    policy for the bucket that was hit.

    - `RateLimit: "<bucket>";r=<remaining>;t=<seconds until reset>` - live
    state. **`t` is a DURATION (seconds remaining), not the epoch timestamp
    `X-RateLimit-Reset` carries - the two are never the same value.**
  version: 1.0.0
servers:
  - url: https://api.vexioncards.one
    description: Production
  - url: https://migration.api.vexioncards.one
    description: Sandbox
security:
  - apiKey: []
tags:
  - name: Customers
  - name: Cards
  - name: Transactions
  - name: Notifications
  - name: Locations
  - name: Products
  - name: API keys
  - name: Organization
  - name: Webhook endpoints
  - name: Events
  - name: Custom fields
  - name: Team members
  - name: Icons
paths:
  /v1/locations/{id}:
    patch:
      tags:
        - Locations
      summary: Update a location
      description: >-
        Edits a location. Scope: `locations:write`.


        ## What's writable - and what owns the rest


        | You can change | Owner of the rest |

        |---|---|

        | `name` | - |

        | `address` / `coordinates` (interplay below) | - |

        | `geo_message`, `geo_message_enabled` | - |

        | `enabled_card_ids` (SET - toggle cards on/off) | - |

        | POS mapping | Dashboard Integrations flow - **never via API** |

        | Team assignment | `PATCH /v1/team_members/{id}` (`location_ids`) |

        | Lifecycle (`is_active`) & billing | Dashboard - each location is a
        billing unit |


        ## Address ↔ coordinates


        Mirrors the dashboard (type address → geocode → drag pin to fine-tune):

        - **`address` alone** → server geocodes → `coordinates` + `country`
        update. Ungeocodable → `422 address_not_geocodable`.

        - **`coordinates` alone** → the pin moves, the displayed address stays -
        the "drag to fine-tune" gesture as an API call.

        - **Both** → stored exactly as sent, no geocode (you already resolved
        them - this is what the dashboard submits).


        ## Card toggles


        `enabled_card_ids` is SET semantics: send the complete list you want
        active. Enabling a card pushes this location's geofence onto that card's
        wallet passes; disabling removes it and the card stops appearing in
        `cards[]` (its signup URL for this location stops accepting signups).
        Unknown card → `422 card_not_found`. A location must keep at least one
        card enabled (`422 location_needs_card`; delete the location instead). A
        card must stay enabled at at least one location: disabling it at its
        last one returns `422 card_needs_location` (pause the card to stop
        offering it; a paused or archived card with members also keeps one
        location so they can still redeem).
      operationId: patchLocation
      parameters:
        - name: id
          in: path
          description: Location identifier (`loc_` prefix).
          required: true
          example: loc_m3hz3sp22bicbbrbzddjkihsq4
          schema:
            type: string
            pattern: ^loc_[A-Za-z0-9]+$
        - name: Idempotency-Key
          in: header
          description: Recommended on PATCH and DELETE for safe retries.
          required: false
          example: 8d37b133-db25-412b-9ae3-4c84e142591c
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LocationPatchRequest'
            examples:
              New push message:
                summary: Geo copy + enable
                value:
                  geo_message: Fresh batch out of the oven - come say hi! 🥐
                  geo_message_enabled: true
              Move to a new address:
                summary: Server geocodes the pin
                value:
                  address: Obchodná 44, 811 06 Bratislava, Slovakia
              Fine-tune the pin:
                summary: Drag-the-map as an API call
                value:
                  coordinates:
                    latitude: 48.14871
                    longitude: 17.10768
              Toggle cards:
                summary: SET the active card list
                value:
                  enabled_card_ids:
                    - crd_56gd3jpqoipsfjoa7cjabfqope
                    - crd_2hh2fekk5zazfyyvian4uhepyq
              Rename:
                summary: Display name only
                value:
                  name: Demo Coffee - Main Square
      responses:
        '200':
          description: Updated location (full object echoed).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Location'
              examples:
                Updated:
                  summary: Full DTO after the edit
                  value:
                    id: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                    object: location
                    name: Demo Coffee - City Center
                    address: Hlavná 12, 811 01 Bratislava, Slovakia
                    coordinates:
                      latitude: 48.1486
                      longitude: 17.1077
                    country: SK
                    geo_message: Fresh batch out of the oven - come say hi! 🥐
                    geo_message_enabled: true
                    enabled_card_ids:
                      - crd_56gd3jpqoipsfjoa7cjabfqope
                      - crd_2hh2fekk5zazfyyvian4uhepyq
                    cards:
                      - card_id: crd_56gd3jpqoipsfjoa7cjabfqope
                        name: Coffee Stamp Card
                        type: stamp
                        signup_url: >-
                          https://vexioncards.one/signup/?id=crd_56gd3jpqoipsfjoa7cjabfqope&loc=loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                      - card_id: crd_2hh2fekk5zazfyyvian4uhepyq
                        name: Coffee Points
                        type: reward
                        signup_url: >-
                          https://vexioncards.one/signup/?id=crd_2hh2fekk5zazfyyvian4uhepyq&loc=loc_01HW9R4P2ZK7N8MTX1RVLOCS01
                    team_members:
                      - team_member_id: tm_01HW9R6M4XQ2N8MTX1RVTM0001
                        name: Anna K.
                        role: scanner
                      - team_member_id: tm_01HW9R6M4XQ2N8MTX1RVTM0002
                        name: Peter V.
                        role: manager
                    pos_connected: true
                    is_default: true
                    is_active: true
                    created_at: 1735689600
                    updated_at: 1751600000
        '400':
          description: Malformed request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing, invalid, or revoked API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Key lacks the required scope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: Location not found in your organization.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            Idempotency-key reuse with a different body, or a request with the
            same key already in flight.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: invalid_request_error
                  code: idempotency_key_in_use
                  message: >-
                    An earlier request with the same Idempotency-Key is still
                    processing. Retry in a few seconds.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#idempotency_key_in_use
                  request_id: req_3f1e9d7c5b2a4
              examples:
                Idempotency key in use:
                  summary: same key, concurrent duplicate
                  value:
                    error:
                      type: invalid_request_error
                      code: idempotency_key_in_use
                      message: >-
                        An earlier request with the same Idempotency-Key is
                        still processing. Retry in a few seconds.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#idempotency_key_in_use
                      request_id: req_3f1e9d7c5b2a4
                Idempotency key conflict:
                  summary: same key, different body
                  value:
                    error:
                      type: idempotency_error
                      code: idempotency_key_conflict
                      message: >-
                        An idempotency key was reused with a different request
                        body. To retry safely, use a new idempotency key.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#idempotency_key_conflict
                      request_id: req_3f1e9d7c5b2a4
          headers: {}
        '413':
          description: >-
            Request body exceeds the 1 MB cap. The body was never parsed, so the
            request had no effect.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: invalid_request_error
                  code: request_too_large
                  message: The request body exceeds the maximum allowed size.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#request_too_large
                  request_id: req_5f8b2c4a9e1d3
        '422':
          description: Semantically invalid input.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                Address not geocodable:
                  summary: Geocoder found nothing
                  value:
                    error:
                      type: invalid_request_error
                      code: address_not_geocodable
                      message: >-
                        Could not resolve 'asdfgh 999, Nowhere' to coordinates.
                        Fix the address or send coordinates directly.
                      param: address
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
                Unknown card:
                  summary: Card not in your organization
                  value:
                    error:
                      type: invalid_request_error
                      code: card_not_found
                      message: >-
                        enabled_card_ids[1] 'crd_unknown123' does not exist in
                        your organization.
                      param: enabled_card_ids
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
                Location without cards:
                  summary: Every card disabled at this location
                  value:
                    error:
                      type: invalid_request_error
                      code: location_needs_card
                      message: >-
                        A location must keep at least one card enabled. Delete
                        the location if it no longer uses any card.
                      param: enabled_card_ids
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
                Card without locations:
                  summary: Card disabled at its last location
                  value:
                    error:
                      type: invalid_request_error
                      code: card_needs_location
                      message: >-
                        Card 'crd_01HW9R4P2ZK7N8MTX1RVCARD01' must stay enabled
                        at at least one location. Pause the card to stop
                        offering it.
                      param: enabled_card_ids
                      request_id: req_01HW9Q3K2YJZ7M5RX1P8VTBNC4
        '429':
          description: Rate limited - respect Retry-After.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '500':
          description: Server error. Retry-safe on idempotent calls.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  type: api_error
                  code: internal_error
                  message: >-
                    An internal error occurred. The request may be retried with
                    the same Idempotency-Key.
                  doc_url: >-
                    https://vexioncards.mintlify.site/api-reference/errors#internal_error
                  request_id: req_5f8b2c4a9e1d3
              examples:
                Http 500 default:
                  summary: Http 500 default
                  value:
                    error:
                      type: api_error
                      code: internal_error
                      message: >-
                        An internal error occurred. The request may be retried
                        with the same Idempotency-Key.
                      doc_url: >-
                        https://vexioncards.mintlify.site/api-reference/errors#internal_error
                      request_id: req_5f8b2c4a9e1d3
          headers: {}
      deprecated: false
components:
  schemas:
    LocationPatchRequest:
      type: object
      description: >-
        Partial update - only supplied fields change.


        **Address ↔ coordinates interplay** (mirrors the dashboard: type an
        address → it geocodes → drag the pin to fine-tune):

        - `address` alone → the server geocodes it and updates `coordinates` +
        `country`. Ungeocodable → `422 address_not_geocodable`.

        - `coordinates` alone → moves the pin only; `address` stays as displayed
        (the "drag to fine-tune" gesture).

        - Both → stored exactly as sent, no geocoding (you already resolved
        them).


        Everything else on the location (POS mapping, team assignment,
        lifecycle, billing) has its own owner and is not writable here.
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 60
          description: New display name.
        address:
          type: string
          minLength: 1
          description: >-
            New street address. Sent without `coordinates`, the server geocodes
            it.
        coordinates:
          type: object
          required:
            - latitude
            - longitude
          description: >-
            New pin position (both values required together). Sent without
            `address`, only the pin moves.
          properties:
            latitude:
              type: number
              minimum: -90
              maximum: 90
              description: WGS84 latitude.
            longitude:
              type: number
              minimum: -180
              maximum: 180
              description: WGS84 longitude.
        geo_message:
          type:
            - string
            - 'null'
          maxLength: 500
          description: New geo push text (max 500 chars), or null to clear.
        geo_message_enabled:
          type: boolean
          description: Toggle the geo notification. Text is preserved when toggling off.
        enabled_card_ids:
          type: array
          items:
            type: string
            pattern: ^crd_[A-Za-z0-9]+$
          maxItems: 100
          description: >-
            SET semantics - the full list of cards active at this location after
            the update. A card id not in your organization → `422
            card_not_found`. Enabling pushes this location's geofence onto the
            card's passes; disabling removes it.
      example:
        geo_message: Fresh batch out of the oven - come say hi! 🥐
        geo_message_enabled: true
    Location:
      type: object
      description: >-
        A physical store - the unit of POS bridging, team assignment, geo
        notifications, and per-location card availability. Locations are
        **created and deleted in the dashboard only** (each is a billing unit);
        the API reads and edits them.
      required:
        - id
        - object
        - name
        - address
        - coordinates
        - geo_message_enabled
        - enabled_card_ids
        - cards
        - team_members
        - pos_connected
        - is_default
        - is_active
        - created_at
      properties:
        id:
          type: string
          pattern: ^loc_[A-Za-z0-9]+$
          description: Location identifier, `loc_` prefix.
          example: loc_01HW9R4P2ZK7N8MTX1RVLOCS01
        object:
          type: string
          const: location
          description: Always `"location"`.
        name:
          type: string
          minLength: 1
          maxLength: 60
          description: >-
            Display name shown in the dashboard, scanner, and transaction
            attribution.
          example: Demo Coffee - City Center
        address:
          type:
            - string
            - 'null'
          description: >-
            Human-readable street address, one line. Writable - see PATCH for
            the address ↔ coordinates interplay (sending a new address alone
            re-geocodes the pin server-side).
          example: Hlavná 12, 811 01 Bratislava, Slovakia
        coordinates:
          type:
            - object
            - 'null'
          description: >-
            The pin - drives the wallet-pass geofence and map display. Kept in
            sync with `address` by geocoding, or set directly to fine-tune (the
            API equivalent of dragging the map pin).
          required:
            - latitude
            - longitude
          properties:
            latitude:
              type: number
              minimum: -90
              maximum: 90
              description: WGS84 latitude.
              example: 48.1486
            longitude:
              type: number
              minimum: -180
              maximum: 180
              description: WGS84 longitude.
              example: 17.1077
          example:
            latitude: 48.1486
            longitude: 17.1077
        country:
          type:
            - string
            - 'null'
          description: >-
            ISO 3166-1 alpha-2, derived from the geocoded address. Null while no
            address is set.
          example: SK
        geo_message:
          type:
            - string
            - 'null'
          maxLength: 500
          description: >-
            The push text shown on the customer's lock screen when they come
            near this location (wallet-pass geofencing; the trigger radius is
            fixed by Apple/Google at roughly 100 m - not configurable). Null =
            no message configured. Only fires while `geo_message_enabled` is
            true.
          example: You're near Demo Coffee - your 10th coffee is on us today! ☕
        geo_message_enabled:
          type: boolean
          description: >-
            Master switch for the geo notification at this location. The message
            text is kept when you toggle off.
          example: true
        enabled_card_ids:
          type: array
          items:
            type: string
            pattern: ^crd_[A-Za-z0-9]+$
          description: >-
            Which card templates are active at this location - the writable
            source of truth behind `cards[]`. PATCH with SET semantics to toggle
            cards on/off. Enabling a card also pushes this location's geofence
            onto that card's wallet passes (Apple Wallet caps geofences at 10
            locations per pass).
          example:
            - crd_56gd3jpqoipsfjoa7cjabfqope
            - crd_2hh2fekk5zazfyyvian4uhepyq
        cards:
          type: array
          description: >-
            Read-only expansion of `enabled_card_ids` - **only the cards turned
            ON at this location**, each with its per-(card, location)
            `signup_url`. The URL is long-lived and never rotates: render your
            own QR from it for counter cards and window decals. Customers who
            sign up through it get this location stamped as
            `Customer.signup_location_id`. Distinct from the per-customer
            `claim_url` (wallet handoff).


            Deliberately NOT the full org catalog with on/off flags: embedding
            every card template in every location response would bloat list
            calls and go stale as templates are added. To build a toggle UI like
            the dashboard's, join two clean resources: `GET /v1/cards` (the full
            catalog) against this location's `enabled_card_ids` (the on/off
            state). To toggle, PATCH `enabled_card_ids` with the new SET.
          items:
            type: object
            required:
              - card_id
              - name
              - type
              - signup_url
            properties:
              card_id:
                type: string
                pattern: ^crd_[A-Za-z0-9]+$
                description: The enabled card template.
                example: crd_56gd3jpqoipsfjoa7cjabfqope
              name:
                type: string
                description: Card display name (default language).
                example: Coffee Stamp Card
              type:
                type: string
                enum:
                  - stamp
                  - reward
                  - discount
                  - coupon
                  - membership
                  - gift_card
                  - custom
                description: Card family.
                example: stamp
              signup_url:
                type: string
                format: uri
                description: Stable signup landing URL for this card at this location.
                example: >-
                  https://vexioncards.one/signup/?id=crd_56gd3jpqoipsfjoa7cjabfqope&loc=loc_01HW9R4P2ZK7N8MTX1RVLOCS01
        team_members:
          type: array
          description: >-
            Read-only summary of who is assigned here. The write path is `PATCH
            /v1/team_members/{id}` with `location_ids` - one relationship, one
            owner; this is the location-side read.
          items:
            type: object
            required:
              - team_member_id
              - role
            properties:
              team_member_id:
                type: string
                pattern: ^tm_[A-Za-z0-9]+$
                description: The assigned team member.
                example: tm_01HW9R6M4XQ2N8MTX1RVTM0001
              name:
                type:
                  - string
                  - 'null'
                description: Display name.
                example: Anna K.
              role:
                type: string
                enum:
                  - manager
                  - scanner
                description: >-
                  Their role at this location. Admins never appear here - they
                  have implicit access to all locations.
                example: scanner
        pos_connected:
          type: boolean
          description: >-
            True when this location has a live POS bridge - sales at the till
            flow in automatically as `source: "pos"` transactions. **Boolean by
            design:** the one integration decision this flag serves is "do I
            need to push transactions via the API, or does the till already
            report them?". The POS vendor, connection ids, and mapping details
            are internal (vendor privacy) and dashboard-owned - they are never
            surfaced or editable via API.
          example: true
        is_default:
          type: boolean
          description: >-
            The organization's default location (used when a write carries no
            explicit `location_id` and the org has exactly one candidate).
          example: true
        is_active:
          type: boolean
          description: >-
            Read-only lifecycle flag. Deactivating a location is a billing
            action - dashboard only.
          example: true
        created_at:
          type: integer
          description: Unix seconds.
          example: 1735689600
        updated_at:
          type: integer
          description: Unix seconds of last change.
          example: 1751500000
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - type
            - code
            - message
          properties:
            type:
              type: string
              enum:
                - invalid_request_error
                - authentication_error
                - permission_error
                - not_found_error
                - rate_limit_error
                - idempotency_error
                - api_error
            code:
              type: string
            message:
              type: string
            retry_after:
              type: integer
              description: >-
                Seconds to wait before retrying. Present on rate-limit (429) and
                idempotency-key-in-use (409) errors; mirrors the `Retry-After`
                HTTP header. Absent on error types that carry no retry guidance.
              example: 5
            param:
              type:
                - string
                - 'null'
            request_id:
              type: string
            doc_url:
              type: string
              format: uri
            field_errors:
              type: array
              description: >-
                Per-field errors on a 422. At most 100 entries: a request with
                more errors gets the first 100.
              items:
                type: object
                properties:
                  param:
                    type: string
                  code:
                    type: string
                  message:
                    type: string
                  expected_currency:
                    type: string
                    description: >-
                      On `currency_mismatch`: the business currency the request
                      must use. Absent on every other field error.
          description: >-
            The error payload: type (category), code (machine-readable), message
            (human-readable), retry_after (seconds, on
            rate-limit/idempotency-in-use errors only), param (offending field),
            request_id, doc_url, field_errors[].
      description: >-
        Standard error envelope. Branch on `error.code`; include
        `error.request_id` in support requests. See the Errors guide.
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        Your secret API key as a Bearer token: `Authorization: Bearer
        vxn_sk_...`. Keys are created and scoped in the dashboard - see
        [Authentication](/api-reference/introduction#authentication).

````

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