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

# Resolve a mobile open

> Resolve an installed or deferred mobile link with an app-scoped mobile publishable key.

<Warning>
  Mobile-open resolution is in **Public Beta**. Use an official Biqli mobile SDK unless you need to implement the wire contract directly.
</Warning>

`POST https://biq.li/api/v1/track/open` resolves an iOS or Android open for one registered Mobile App. A successful response can contain an exact match, a probabilistic iOS match, or no match.

## Authentication

Send the Mobile App's public, app-scoped key:

```http theme={null}
Authorization: Bearer biqli_mobile_pk_your_public_mobile_key
```

Do not use a `biqli_pk_...` browser key or a `biqli_...` secret workspace API key. The key must belong to the `appId` in the body.

## Required headers

| Header              | Constraint                                                                                             |
| :------------------ | :----------------------------------------------------------------------------------------------------- |
| `Authorization`     | `Bearer` plus an active `biqli_mobile_pk_...` key.                                                     |
| `Content-Type`      | `application/json`.                                                                                    |
| `Idempotency-Key`   | From 8 to 100 letters, digits, `.`, `_`, `~`, or `-`. Reuse only with the identical canonical request. |
| `X-Biq-SDK`         | Exactly `ios` or `android`; must match body `platform`.                                                |
| `X-Biq-SDK-Version` | Non-empty string up to 32 characters; must match body `sdkVersion`.                                    |
| `X-Biq-Request-Id`  | From 8 to 100 letters, digits, `.`, `_`, `~`, or `-`. Returned in the response header and body.        |

The JSON body must be an object no larger than 16 KB and no deeper than 32 levels. Unknown fields are rejected.

## Request body

| Field                  | Type                       | Required    | Constraint and meaning                                                                                                                                                 |
| :--------------------- | :------------------------- | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `eventId`              | string                     | Yes         | Stable event ID, 8–100 safe identifier characters.                                                                                                                     |
| `appId`                | string                     | Yes         | Mobile App ID, up to 40 characters, belonging to the mobile key.                                                                                                       |
| `appInstanceId`        | string                     | Yes         | Stable random ID for this installation, 16–128 safe identifier characters. Do not use a user or advertising ID.                                                        |
| `platform`             | string                     | Yes         | `ios` or `android`; that platform must be configured on the Mobile App.                                                                                                |
| `firstOpen`            | boolean                    | Yes         | Whether this is an eligible first-install resolution attempt.                                                                                                          |
| `probabilisticAllowed` | boolean                    | Yes         | Explicitly allow probabilistic evaluation. Valid only for an iOS request with an eligible verified `domain`.                                                           |
| `handoffToken`         | string or null             | No          | Opaque `bqmh_...` handoff, up to 128 characters. Do not log it.                                                                                                        |
| `deepLink`             | string or null             | No          | HTTPS Biqli short URL, up to 2,048 characters. It must use an attached verified domain and one-segment link path to match. Credentials and fragments are not eligible. |
| `domain`               | string or null             | Conditional | Hostname up to 255 characters. Required for probabilistic matching; it must be verified, HTTPS-active, and attached to the iOS app.                                    |
| `appVersion`           | string or null             | No          | Application version, up to 32 characters.                                                                                                                              |
| `osVersion`            | string or null             | No          | Operating-system version, up to 32 characters. Used as part of eligible probabilistic comparison.                                                                      |
| `sdkVersion`           | string                     | Yes         | SDK version, up to 32 characters; must equal `X-Biq-SDK-Version`.                                                                                                      |
| `occurredAt`           | ISO 8601 date-time or null | No          | Client-observed event time for diagnostics. Server receipt time controls matching.                                                                                     |
| `attestation`          | object or array or null    | No          | Reserved for future use. It is not currently enforced and does not enable App Attest or Play Integrity validation.                                                     |

The current official SDKs send no `attestation` value. Do not advertise device-attestation enforcement until Biqli documents an active contract.

## Example request

```bash theme={null}
curl --request POST \
  --url https://biq.li/api/v1/track/open \
  --header "Authorization: Bearer $BIQLI_MOBILE_PUBLISHABLE_KEY" \
  --header 'Content-Type: application/json' \
  --header 'Idempotency-Key: open_0e7cb12b4ecb4bc4a192de5578ec0139' \
  --header 'X-Biq-SDK: android' \
  --header 'X-Biq-SDK-Version: 1.0.0' \
  --header 'X-Biq-Request-Id: req_8d2cc6242a224f7aaf2c0c172f849f87' \
  --data '{
    "eventId": "open_0e7cb12b4ecb4bc4a192de5578ec0139",
    "appId": "biq_mapp_01ARZ3NDEKTSV4RRFFQ69G5FAV",
    "appInstanceId": "install_98261e74ceca42bc830282c8f6cfd02f",
    "platform": "android",
    "firstOpen": true,
    "probabilisticAllowed": false,
    "handoffToken": "bqmh_example_opaque_value_not_a_real_token",
    "deepLink": null,
    "domain": null,
    "appVersion": "2.4.0",
    "osVersion": "15",
    "sdkVersion": "1.0.0",
    "occurredAt": "2026-09-27T12:30:00Z"
  }'
```

## Matched response

```json theme={null}
{
  "open": {
    "id": "biq_open_01ARZ3NDEKTSV4RRFFQ69G5FAW",
    "firstOpen": true,
    "matchedBy": "exact_install_referrer",
    "confidence": "exact"
  },
  "click": {
    "id": "AbCdEf123456"
  },
  "link": {
    "id": "biq_lnk_01ARZ3NDEKTSV4RRFFQ69G5FAX",
    "shortUrl": "https://go.example.com/invite",
    "route": "/invite/accepted"
  },
  "attribution": {
    "referralCode": "PARTNER_42",
    "metadata": {
      "campaign": "spring_launch"
    },
    "dynamic": {
      "ref": "BOB"
    }
  },
  "attributionReceipt": "eyJhbGciOiJFZERTQSIsImtpZCI6ImV4YW1wbGUiLCJ0eXAiOiJKV1QifQ.example.signature",
  "requestId": "req_8d2cc6242a224f7aaf2c0c172f849f87"
}
```

## No-match response

No match is a successful `200` response:

```json theme={null}
{
  "open": {
    "id": "biq_open_01ARZ3NDEKTSV4RRFFQ69G5FAY",
    "firstOpen": false,
    "matchedBy": "none",
    "confidence": "none"
  },
  "click": null,
  "link": null,
  "attribution": null,
  "attributionReceipt": null,
  "requestId": "req_5f36c66b57eb42e4a5f644235e59e6bd"
}
```

## Matching behavior

| `matchedBy`              | Confidence      | Source                                                  |
| :----------------------- | :-------------- | :------------------------------------------------------ |
| `exact_app_link`         | `exact`         | Eligible installed-app HTTPS link.                      |
| `exact_install_referrer` | `exact`         | Valid Android handoff supplied through Google Play.     |
| `exact_handoff`          | `exact`         | Valid iOS Deep View handoff supplied by explicit paste. |
| `probabilistic`          | `probabilistic` | One unambiguous eligible iOS first-open candidate.      |
| `none`                   | `none`          | No eligible match.                                      |

Exact handoff or App Link matching runs before probabilistic evaluation. If `handoffToken` and `deepLink` identify different links, the API returns `422 handoff_mismatch`.

For a matched first open, the first accepted install attribution is immutable. A later `firstOpen: true` request for the same app instance returns the original accepted result.

## Idempotency

The endpoint de-duplicates within the Mobile App by either `eventId` or the hashed `Idempotency-Key`. Repeating either identifier with the identical validated request returns the stored result and creates no second open event. Reusing either identifier with different request data returns `409 idempotency_conflict`.

Persist the event ID, request ID, idempotency key, and complete body together before sending. A retry must not regenerate only part of that set.

## Rate limits

Default one-minute limits are 3,000 requests per Mobile App, 120 per source address, 30 per app instance, and 20 per handoff token. Additional abuse protection applies to repeated invalid-token attempts. A `429` response includes `Retry-After`; wait at least that many seconds and retry with the same idempotent request.

## Errors

Errors produced after the mobile resolver accepts the endpoint contain
`error.code`, `error.message`, and `requestId`; the same safe request ID is
returned in `X-Biq-Request-Id`. The method guard and unexpected-failure handler
use the standard API envelope with `request_id` instead. Read either request-ID
spelling when implementing a direct client.

| Status | Code                           | Meaning                                                                                                  |
| :----- | :----------------------------- | :------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request`              | Body is not JSON, not an object, exceeds 16 KB, or exceeds supported JSON depth.                         |
| `400`  | `invalid_headers`              | An idempotency, SDK, SDK-version, or request-ID header is missing or invalid.                            |
| `401`  | `invalid_mobile_key`           | Mobile key is missing, invalid, revoked, or unavailable.                                                 |
| `403`  | `mobile_linking_unavailable`   | Mobile resolution is not currently available.                                                            |
| `403`  | `mobile_sdk_unavailable`       | The requested platform SDK is not currently available.                                                   |
| `404`  | `resource_not_found`           | The app ID does not belong to the mobile key or an exact resource is unavailable.                        |
| `409`  | `idempotency_conflict`         | An event or idempotency identity was reused with different data.                                         |
| `422`  | `validation_error`             | A field is missing, malformed, out of bounds, or unknown.                                                |
| `422`  | `platform_not_configured`      | The Mobile App does not contain this platform.                                                           |
| `422`  | `header_mismatch`              | Platform or SDK version differs between headers and body.                                                |
| `422`  | `invalid_probabilistic_domain` | Probabilistic matching lacks an eligible attached iOS domain.                                            |
| `422`  | `platform_mismatch`            | The existing app instance is already registered to another platform.                                     |
| `422`  | `handoff_mismatch`             | Handoff and deep link identify different links.                                                          |
| `429`  | `rate_limited`                 | A mobile-open or abuse-protection bucket was exceeded. Honor `Retry-After`.                              |
| `405`  | `method_not_allowed`           | Use `POST`; other methods return `Allow: POST` rather than the application shell.                        |
| `500`  | `internal_server_error`        | An unexpected failure prevented resolution. Retry the identical idempotent request with bounded backoff. |

Retry transport failures, `429`, and `5xx` with bounded backoff and the identical idempotent request. Correct other errors before retrying.

## Receipts and caching

Matched live responses can include a short-lived signed receipt. Cached SDK results intentionally omit it. Verify a live receipt on a trusted backend by following [Attribution receipts](/docs/api-reference/mobile/attribution-receipts).

Responses include `Cache-Control: no-store` and must not be stored by shared HTTP caches.


## OpenAPI

````yaml POST /v1/track/open
openapi: 3.1.0
info:
  title: Biqli API
  version: 1.0.0
  description: Workspace-scoped REST API and app-scoped mobile attribution API for Biqli.
servers:
  - url: https://biq.li/api
security:
  - bearerAuth: []
paths:
  /v1/track/open:
    post:
      tags:
        - Mobile attribution
      summary: Resolve a mobile open
      description: >-
        Resolves a verified installed-app link, Android Install Referrer
        handoff, explicit iOS handoff, or eligible consented iOS probabilistic
        match. This Public Beta endpoint uses a Mobile App publishable key
        rather than a workspace API key.
      operationId: mobileOpen.resolve
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: >-
            Stable identity for this exact request. Reuse only with the
            identical canonical body.
          schema:
            type: string
            minLength: 8
            maxLength: 100
            pattern: ^[A-Za-z0-9._~-]+$
        - name: X-Biq-SDK
          in: header
          required: true
          description: Native platform. Must equal the body platform.
          schema:
            type: string
            enum:
              - ios
              - android
        - name: X-Biq-SDK-Version
          in: header
          required: true
          description: SDK version. Must equal the body sdkVersion.
          schema:
            type: string
            minLength: 1
            maxLength: 32
        - name: X-Biq-Request-Id
          in: header
          required: true
          description: Caller-generated request identifier returned in the response.
          schema:
            type: string
            minLength: 8
            maxLength: 100
            pattern: ^[A-Za-z0-9._~-]+$
      requestBody:
        required: true
        description: >-
          JSON object no larger than 16 KB and no deeper than 32 levels. Unknown
          fields are rejected.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MobileOpenRequest'
      responses:
        '200':
          description: >-
            Resolution completed. A no-match result is also a successful
            response.
          headers:
            X-Biq-Request-Id:
              $ref: '#/components/headers/RequestId'
            Cache-Control:
              description: Always no-store.
              schema:
                type: string
                const: no-store
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileOpenResponse'
        '400':
          description: invalid_request or invalid_headers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '401':
          description: invalid_mobile_key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '403':
          description: mobile_linking_unavailable or mobile_sdk_unavailable.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '404':
          description: resource_not_found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '405':
          description: >-
            method_not_allowed. The method guard uses the standard API error
            envelope.
          headers:
            Allow:
              description: Supported method.
              schema:
                type: string
                const: POST
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: idempotency_conflict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '422':
          description: >-
            validation_error, platform_not_configured, header_mismatch,
            invalid_probabilistic_domain, platform_mismatch, or
            handoff_mismatch.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '429':
          description: >-
            rate_limited. Retry the identical idempotent request after the
            supplied interval.
          headers:
            Retry-After:
              description: Minimum seconds before retrying.
              schema:
                type: integer
                minimum: 1
            X-Biq-Request-Id:
              $ref: '#/components/headers/RequestId'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MobileApiError'
        '500':
          description: >-
            internal_server_error. The unexpected-failure handler uses the
            standard API error envelope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
      security:
        - mobilePublishableKey: []
components:
  schemas:
    MobileOpenRequest:
      type: object
      additionalProperties: false
      required:
        - eventId
        - appId
        - appInstanceId
        - platform
        - firstOpen
        - probabilisticAllowed
        - sdkVersion
      properties:
        eventId:
          type: string
          minLength: 8
          maxLength: 100
          pattern: ^[A-Za-z0-9._~-]+$
        appId:
          type: string
          maxLength: 40
          description: Mobile App ID belonging to the bearer key.
        appInstanceId:
          type: string
          minLength: 16
          maxLength: 128
          pattern: ^[A-Za-z0-9._~-]+$
          description: Random stable identifier for this app installation.
        platform:
          type: string
          enum:
            - ios
            - android
        firstOpen:
          type: boolean
        probabilisticAllowed:
          type: boolean
          description: >-
            Valid only for an eligible iOS first-open request with a verified
            attached domain.
        handoffToken:
          type:
            - string
            - 'null'
          maxLength: 128
          description: Opaque handoff token. Treat as sensitive and do not log.
        deepLink:
          type:
            - string
            - 'null'
          format: uri
          maxLength: 2048
          description: Eligible installed-app HTTPS short URL.
        domain:
          type:
            - string
            - 'null'
          maxLength: 255
          description: Required verified attached hostname for probabilistic matching.
        appVersion:
          type:
            - string
            - 'null'
          maxLength: 32
        osVersion:
          type:
            - string
            - 'null'
          maxLength: 32
        sdkVersion:
          type: string
          minLength: 1
          maxLength: 32
        occurredAt:
          type:
            - string
            - 'null'
          format: date-time
        attestation:
          description: Reserved and not currently enforced.
          anyOf:
            - type: object
              additionalProperties: true
            - type: array
              items: {}
            - type: 'null'
    MobileOpenResponse:
      type: object
      additionalProperties: false
      required:
        - open
        - click
        - link
        - attribution
        - attributionReceipt
        - requestId
      properties:
        open:
          $ref: '#/components/schemas/MobileOpen'
        click:
          oneOf:
            - $ref: '#/components/schemas/MobileClick'
            - type: 'null'
        link:
          oneOf:
            - $ref: '#/components/schemas/MobileResolvedLink'
            - type: 'null'
        attribution:
          oneOf:
            - $ref: '#/components/schemas/MobileAttribution'
            - type: 'null'
        attributionReceipt:
          type:
            - string
            - 'null'
          description: >-
            Short-lived Ed25519 JWT on eligible live matched responses. Cached
            SDK results omit it.
        requestId:
          type: string
          minLength: 1
          maxLength: 100
    MobileApiError:
      type: object
      additionalProperties: true
      required:
        - error
        - requestId
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
            message:
              type: string
        requestId:
          type: string
    ApiError:
      type: object
      required:
        - error
        - request_id
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - invalid_token
                - insufficient_scope
                - upgrade_required
                - quota_exceeded
                - resource_not_found
                - tracking_disabled
                - attribution_required
                - currency_unavailable
                - idempotency_conflict
                - idempotency_key_required
                - invalid_preflight
                - origin_required
                - hostname_not_configured
                - origin_not_allowed
                - client_click_tracking_disabled
                - invalid_click_id
                - stripe_account_not_connected
                - stripe_environment_required
                - stripe_attribution_conflict
                - stripe_mode_mismatch
                - domain_taken
                - alias_taken
                - external_id_taken
                - folder_name_taken
                - tag_name_taken
                - pixel_name_taken
                - biolink_name_taken
                - method_not_allowed
                - url_blocked
                - dns_verification_failed
                - validation_error
                - rate_limit_exceeded
                - internal_server_error
            message:
              type: string
            details:
              type: object
              additionalProperties: true
        request_id:
          type: string
          description: Request identifier to include when contacting support.
    MobileOpen:
      type: object
      additionalProperties: false
      required:
        - id
        - firstOpen
        - matchedBy
        - confidence
      properties:
        id:
          type: string
          pattern: ^biq_open_
        firstOpen:
          type: boolean
        matchedBy:
          type: string
          enum:
            - exact_app_link
            - exact_install_referrer
            - exact_handoff
            - probabilistic
            - none
        confidence:
          type: string
          enum:
            - exact
            - probabilistic
            - none
    MobileClick:
      type: object
      additionalProperties: false
      required:
        - id
      properties:
        id:
          type: string
    MobileResolvedLink:
      type: object
      additionalProperties: false
      required:
        - id
        - shortUrl
        - route
      properties:
        id:
          type: string
          pattern: ^biq_lnk_
        shortUrl:
          type: string
          format: uri
        route:
          type: string
          minLength: 1
          maxLength: 500
          pattern: ^/
    MobileAttribution:
      type: object
      additionalProperties: false
      required:
        - referralCode
        - metadata
        - dynamic
      properties:
        referralCode:
          type:
            - string
            - 'null'
          maxLength: 128
        metadata:
          type: object
          additionalProperties: true
        dynamic:
          type: object
          additionalProperties:
            type: string
            maxLength: 256
          maxProperties: 20
  headers:
    RequestId:
      description: Request identifier for tracing and support.
      schema:
        type: string
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Workspace API key
      description: A workspace API key beginning with biqli_.
    mobilePublishableKey:
      type: http
      scheme: bearer
      bearerFormat: Biqli Mobile App publishable key
      description: >-
        A public app-scoped key beginning with biqli_mobile_pk_. It resolves
        mobile opens for one registered Mobile App and grants no
        workspace-management access.

````