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

# Redact Customer

> Queue GDPR customer redaction for a Loop Anywhere (Commerce Data) shop.
Shopify shops receive `403` and should keep using Shopify's `customers/redact`
webhook.

Send at least one of `customer.id`, `customer.email`, or `customer.external_id`.
Send `customer.id` or `customer.external_id`, not both. Identifiers combine as
a union: when a customer id and an email are both sent, Loop redacts orders
that match the id plus orders that match the email. Optionally send
`orders_to_redact` or `orders_to_redact_external` (1–100 ids each, not both)
to narrow each of those matches to the listed orders.

`customer.id` and `orders_to_redact` are Loop Commerce Data global ids.
`customer.external_id` and `orders_to_redact_external` are merchant ids sent
at ingest.

Redaction anonymizes PII on matching Loop orders, returns, and Commerce
Data customers and orders. It is not a hard delete. After the jobs run,
`GET /customers/{id}` and `GET /orders/{id}` return the anonymized values.
`DELETE /customers` is not a substitute.

Auth: Public API key or OAuth with scope `customers:write`.
A `202` `{"accepted": true}` means the job is queued asynchronously.

> [!WARNING]
>
> Shopify shops receive `403`. Continue using Shopify's `customers/redact`
> webhook, which Loop already handles.

> [!NOTE]
>
> [Delete Customer](https://docs.loopreturns.com/api-reference/latest/customers/delete-customer.md) only removes
> the Commerce Data catalog record. It is not a substitute for this GDPR
> redaction. Redaction anonymizes PII; it is not a hard delete.

The endpoint returns `202` `{"accepted": true}` and runs asynchronously.
After the jobs finish, the matched Commerce Data customer and orders are
anonymized, not deleted.
[Get Customer](https://docs.loopreturns.com/api-reference/latest/customers/get-customer.md) and
[Get Order](https://docs.loopreturns.com/api-reference/latest/orders/get-order.md) then return the
anonymized values.

A `customer.id` or `customer.external_id` for a deleted customer is accepted
(`202`) and that customer's data is anonymized. A `customer.id` or
`customer.external_id` that does not resolve returns `404`. An
`orders_to_redact` or `orders_to_redact_external` value that is unknown
or not owned by the supplied customer or email returns `202`
`{"accepted": true}` and is not redacted. An owned deleted order sent by
its original external id in `orders_to_redact_external` is redacted.

These identifier rules decide which records are redacted (`customer.id` or
`external_id`, the email union, and `orders_to_redact` or
`orders_to_redact_external`, 1–100 ids each). An empty `orders_to_redact` or
`orders_to_redact_external` array returns `422`. `orders_to_redact` and
`orders_to_redact_external` only redact orders the identified customer owns:
a core order whose customer or email matches, or a Commerce Data order
belonging to that customer, including when identified by email. Any other customer's order id in the
list is ignored entirely, including its order-edit session data. If nothing
in the list is owned, no orders are redacted, and the customer record is
still anonymized.

* Customer: `first_name` and `last_name` become `gdpr`; `email` becomes
  `{id}_gdpr@loopreturns.com`, where `{id}` is an internal identifier; `phone`
  becomes `null`.
* Order: `browser_ip` and `client_details_user_agent` become `null`.
* Shipping and billing address: `name`, `first_name`, `last_name`, `address1`,
  `city`, and `postal_code` become `gdpr`; `company`, `address2`, and `phone`
  become `null`; `region`, `province_code`, `country`, and `country_code` are
  kept.


## OpenAPI

````yaml post /privacy/customer/redact
openapi: 3.1.0
info:
  title: Customers API
  description: >-
    API for managing customers


    ### Resource identifier format


    Resource identifiers (the `id` field and related global-identifier fields)
    are returned as JSON

    integers by default. To receive them as strings instead, send the

    `X-Loop-Global-Identifier-Type: string` request header.
  version: v1
servers:
  - url: https://api.loopreturns.com/api/2026-10
    description: 2026-10 (Stable)
  - url: https://api.loopreturns.com/api/2026-07
    description: 2026-07
  - url: https://api.loopreturns.com/api/unstable
    description: Unstable (Preview)
security: []
tags:
  - name: Customers
paths:
  /privacy/customer/redact:
    post:
      tags:
        - Customers
      summary: Redact Customer
      description: >-
        Queue GDPR customer redaction for a Loop Anywhere (Commerce Data) shop.

        Shopify shops receive `403` and should keep using Shopify's
        `customers/redact`

        webhook.


        Send at least one of `customer.id`, `customer.email`, or
        `customer.external_id`.

        Send `customer.id` or `customer.external_id`, not both. Identifiers
        combine as

        a union: when a customer id and an email are both sent, Loop redacts
        orders

        that match the id plus orders that match the email. Optionally send

        `orders_to_redact` or `orders_to_redact_external` (1–100 ids each, not
        both)

        to narrow each of those matches to the listed orders.


        `customer.id` and `orders_to_redact` are Loop Commerce Data global ids.

        `customer.external_id` and `orders_to_redact_external` are merchant ids
        sent

        at ingest.


        Redaction anonymizes PII on matching Loop orders, returns, and Commerce

        Data customers and orders. It is not a hard delete. After the jobs run,

        `GET /customers/{id}` and `GET /orders/{id}` return the anonymized
        values.

        `DELETE /customers` is not a substitute.


        Auth: Public API key or OAuth with scope `customers:write`.

        A `202` `{"accepted": true}` means the job is queued asynchronously.
      operationId: redactCustomer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RedactCustomerRequest'
      responses:
        '202':
          description: Redaction accepted and queued.
          content:
            application/json:
              schema:
                type: object
                required:
                  - accepted
                properties:
                  accepted:
                    type: boolean
                    examples:
                      - true
        '401':
          description: >-
            Missing, invalid, or expired credentials, or the credentials lack
            `customers:write`. The body differs by which of those it is.
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/AuthError'
                  - $ref: '#/components/schemas/UnauthorizedResponse'
              examples:
                invalidCredentials:
                  summary: Missing, invalid, or expired API key or OAuth token
                  value:
                    error:
                      code: '401'
                      http_code: GEN-UNAUTHORIZED
                      message: Unauthorized.
                missingScope:
                  summary: Valid credentials, missing `customers:write`
                  value:
                    errors: Unauthorized.
        '403':
          description: Shop is not a Loop Anywhere (Commerce Data) shop.
        '404':
          description: >-
            The `customer.id` or `customer.external_id` does not resolve. A
            deleted customer's `id` or `external_id` is accepted (`202`) and
            that customer's data is anonymized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NotFoundResponse'
              examples:
                customerDoesNotResolve:
                  summary: customer.id or customer.external_id does not resolve
                  value:
                    message: Resource not found.
        '422':
          description: >-
            No customer identifier, both `customer.id` and
            `customer.external_id`, both order arrays, or an empty
            `orders_to_redact` or `orders_to_redact_external` array.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationErrorResponse'
      security:
        - oauth2:
            - customers:write
        - write: []
components:
  schemas:
    RedactCustomerRequest:
      type: object
      required:
        - customer
      properties:
        customer:
          type: object
          description: >-
            At least one of `id`, `email`, or `external_id` is required. Send
            `id` or `external_id`, not both. `id` or `external_id` plus `email`
            redacts the union of matching orders.
          properties:
            id:
              type: integer
              format: int64
              description: Loop Commerce Data customer global id.
            email:
              type: string
              format: email
              description: Customer email as stored on matching Loop orders.
            external_id:
              type: string
              maxLength: 100
              description: Merchant customer id sent at ingest.
        orders_to_redact:
          type: array
          description: >-
            Loop global order ids, 1–100 inclusive. Narrows each customer match
            to these orders. Do not send with `orders_to_redact_external`.
          minItems: 1
          maxItems: 100
          items:
            type: integer
            format: int64
        orders_to_redact_external:
          type: array
          description: >-
            Merchant order ids, 1–100 inclusive. Narrows each customer match to
            these orders. Do not send with `orders_to_redact`. An owned deleted
            order sent by its original external id is redacted. An unknown or
            unowned value is not redacted.
          minItems: 1
          maxItems: 100
          items:
            type: string
            maxLength: 100
    AuthError:
      type: object
      description: >-
        Returned when the API key or OAuth token is missing, invalid, or
        expired.
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              examples:
                - '401'
            http_code:
              type: string
            message:
              type: string
              examples:
                - Unauthorized.
    UnauthorizedResponse:
      type: object
      required:
        - errors
      properties:
        errors:
          type: string
          examples:
            - Unauthorized.
    NotFoundResponse:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          examples:
            - Resource not found.
    ValidationErrorResponse:
      type: object
      description: >-
        Returned when the request body fails validation. Uses Laravel's standard
        validation error shape.
      properties:
        message:
          type: string
          examples:
            - The external_id field is required.
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          examples:
            - external_id:
                - The external_id field is required.
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://oauth.loopreturns.com/authorize
          tokenUrl: https://oauth.loopreturns.com/oauth/token
          scopes:
            customers:write: Redact Customer (`POST /privacy/customer/redact`)
    write:
      name: X-Authorization
      type: apiKey
      in: header
      description: 'API Scope: "Customers (write)"'

````

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