---
title: "Look-up"
description: "Look images up, singly with POST /lookup or in batches through a look-up slot, and read the verdicts back."
published: 2026-09-23T10:54:51.893037+00:00
updated: 2026-09-23T10:54:51.893037+00:00
tags: ["api", "parallax", "reference", "rest"]
url: https://xiobjects.com/docs/xio/parallax/rest-api/reference/look-up
source: XI Objects
---

<!-- xion:doctype xion+markdown -->
<!-- xion:metadata
{
  "version": "1.0",
  "content_type": "application/xion\u002Bmarkdown",
  "source_type": "xi-content/doc",
  "generator": "xio-content-publisher/1.0.0",
  "generated": "2026-09-23T10:54:37.3989136\u002B00:00",
  "encoding": "utf-8",
  "render_intent": "markdown",
  "title": "Look-up",
  "slug": "xio/parallax/rest-api/reference/look-up",
  "copyright": "\u00A9 2026 XI Objects Inc"
}
-->

# Look-up

Look-up comes in two forms: one image per request, or a slot that holds many query images and looks them all up on commit. Both answer the same verdict per image.

## POST /lookup

Looks one image up. Accepts `multipart/form-data` carrying exactly one image file part and nothing else.

**Parts:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| any file part | File part | Yes | Exactly one image. The part name carries no meaning. |

No query-string parameter and no other form field is accepted. There is no top-K, threshold or options object.

```bash
curl -sS -X POST "$BASE/lookup" \
  -H "Authorization: Bearer $TOKEN" \
  -F "image=@query.png;type=image/png"
```

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `matched` | `bool` | `true` when any account holds a live registration for this image. |
| `matchedOriginalImageHashes` | `array` | The original image hashes this query matched. Each is a key for `GET /records/{originalImageHash}`. Empty for no match. |
| `candidates` | `array` | Only your own matching registrations. Empty for a match you do not own, and empty for no match. |
| `candidates[].registrationId` | `uuid` | Your registration for this image. |

```json
{
  "matched": true,
  "matchedOriginalImageHashes": ["9f2c7a41e6b8d0532c1f4a9b7e0d8c6a53f1b2e4d7c0a9f8b6e3d1c4a7f0b2e5"],
  "candidates": [
    { "registrationId": "6f0d4d6e-9a0b-4c2e-9b1a-2d3c4e5f6a7b" }
  ]
}
```

No manifests are returned here, for your own match or anyone else's. Take a hash from `matchedOriginalImageHashes` to `GET /records/{originalImageHash}`.

No score, rank or similarity value exists anywhere in this response.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Malformed request` | A query-string parameter, a wrong part count, an extra part, or a malformed body |
| 413 | `Request too large` | Past the per-request cap |
| 413 | `Image too large` | Past the per-image cap |
| 422 | `Image could not be checked` | No verdict was produced. Nothing charged |
| 429 | `Quota exceeded` | Carries both remaining counts |
| 503 | `Image could not be checked yet` | The engine could not answer for this image yet. Nothing registered, answered or charged. `Retry-After` names the seconds to wait before sending the same request again |
| 503 | `Engine not configured` | No engine is wired |

See [Look up a single image](/docs/xio/parallax/rest-api/look-up-single-image).

---

## POST /lookup/slots

Opens a look-up slot. Look-up slots have their own cap, counted separately from registration slots.

```bash
curl -sS -X POST "$BASE/lookup/slots" -H "Authorization: Bearer $TOKEN"
```

**Response: 201 Created**, with a `Location` header pointing at the slot.

| Field | Type | Description |
|-------|------|-------------|
| `lookupSlotId` | `string` | The slot's opaque id. |
| `expiresAt` | `datetime` | When the slot dies, unless activity moves it. |

```json
{
  "lookupSlotId": "sl_4d7h2j9k1m3n5p7q9r2s4t",
  "expiresAt": "2026-09-21T14:32:05.118Z"
}
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 409 | `Open-slot cap reached` | Carries `cap` |

---

## DELETE /lookup/slots/{lookupSlotId}

Abandons the look-up slot. Every held query's pixels are deleted and every reservation comes back. Nothing was looked up, so nothing is charged.

**Response: 204 No Content**, no body.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, already gone, or not yours |
| 409 | `Slot is no longer open` | The slot is committed |

---

## POST /lookup/slots/{lookupSlotId}/queries

Uploads query images. No engine call happens here. Refreshes the slot's idle window.

**Parts:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| any file part | File part | Yes, at least one | One query image per part. The part name carries no meaning. |

A look-up request carries the image alone. No manifest part, no other form field and no query-string parameter is accepted. Any of them refuses the whole request, and a refused request holds nothing.

```bash
curl -sS -X POST "$BASE/lookup/slots/$LOOKUP_SLOT_ID/queries" \
  -H "Authorization: Bearer $TOKEN" \
  -F "a=@first.png;type=image/png" \
  -F "b=@second.png;type=image/png"
```

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `outcomes` | `array` | One [upload outcome](#upload-outcome) per image part, in request order. `registrationId` is never populated here. |

```json
{
  "outcomes": [
    { "partIndex": 1, "fileName": "first.png", "accepted": true, "imageHash": "3b8f1a...9e4c", "rejectionReason": null, "registrationRemaining": null, "lookupRemaining": null, "registrationId": null },
    { "partIndex": 2, "fileName": "second.png", "accepted": false, "imageHash": null, "rejectionReason": "This lookup slot already holds the maximum number of query images.", "registrationRemaining": null, "lookupRemaining": null, "registrationId": null }
  ]
}
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Malformed request` | A query-string parameter, a non-image part, more images than the per-request cap, or a malformed body |
| 404 | `Slot not found` | Unknown, expired, or not yours |
| 409 | `Slot is no longer open` | The slot is committed |
| 413 | `Request too large` | Past the per-request cap |
| 429 | `Quota exceeded` | No image in the request fit. Carries both remaining counts |
| 503 | `Engine not configured` | No engine is wired |

---

## POST /lookup/slots/{lookupSlotId}/queries/missing

Answers which of your declared hashes the look-up slot does not already hold. Refreshes the slot's idle window.

**Request body:**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `hashes` | `string[]` | No | Image hashes, 64 lowercase hex characters each, no duplicates. |

```bash
curl -sS -X POST "$BASE/lookup/slots/$LOOKUP_SLOT_ID/queries/missing" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"hashes":["3b8f1a...9e4c","ffff...ffff"]}'
```

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `missing` | `string[]` | The declared hashes the slot does not hold. |

```json
{ "missing": ["ffff...ffff"] }
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 400 | `Too many declared hashes` | Past the declared-list cap |
| 400 | `Invalid declared hash` | A value is not a unique 64-character lowercase hex hash |
| 404 | `Slot not found` | Unknown, expired, or not yours |
| 409 | `Slot is no longer open` | The slot is committed |

---

## GET /lookup/slots/{lookupSlotId}/manifest

Reports the held queries. Does not refresh the idle window.

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `entries` | `array` | One [slot entry](#slot-entry) per held query. |

A query carries no manifest of its own, so `manifestsHash` and `notRegisteredReason` are always `null` here.

```json
{
  "entries": [
    {
      "imageHash": "3b8f1a...9e4c",
      "manifestsHash": null,
      "byteLength": 184320,
      "contentType": "image/png",
      "createdAt": "2026-09-21T14:12:41.882Z",
      "state": "held",
      "notRegisteredReason": null
    }
  ]
}
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, expired, or not yours |

---

## DELETE /lookup/slots/{lookupSlotId}/entries/{imageHash}

Removes one held query, deletes its pixels and gives its reservation back. Idempotent. Refreshes the slot's idle window.

**Response: 204 No Content**, no body.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, expired, or not yours |
| 404 | `Entry not found` | The hash is not a valid image hash |
| 409 | `Slot is no longer open` | The slot is committed |

---

## POST /lookup/slots/{lookupSlotId}/commit

Looks every held query up. This is the only call that triggers the engine's look-up, and it is terminal. It answers the results directly.

```bash
curl -sS -X POST "$BASE/lookup/slots/$LOOKUP_SLOT_ID/commit" -H "Authorization: Bearer $TOKEN"
```

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `lookupSlotId` | `string` | The slot's id. |
| `queries[].imageHash` | `string` | The query's hash. |
| `queries[].state` | `string` | `answered`, `held`, `failed` or `retry`. |
| `queries[].result` | `object?` | The verdict, when the query reached one. `matched` plus `candidates`, the same shape `POST /lookup` answers. |
| `queries[].failureReason` | `string?` | Why the query could not be answered. |

```json
{
  "lookupSlotId": "sl_4d7h2j9k1m3n5p7q9r2s4t",
  "queries": [
    {
      "imageHash": "3b8f1a...9e4c",
      "state": "answered",
      "result": {
        "matched": true,
        "candidates": [
          { "registrationId": "6f0d4d6e-9a0b-4c2e-9b1a-2d3c4e5f6a7b", "manifests": [] }
        ]
      },
      "failureReason": null
    },
    {
      "imageHash": "a17c05...2f91",
      "state": "answered",
      "result": { "matched": false, "candidates": [] },
      "failureReason": null
    }
  ]
}
```

A query the engine would not serve lands `failed`, never as no match, and is charged nothing. A query it could not answer yet lands `retry` instead: send that image again, in a new slot or through `POST /lookup`.

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, expired, or not yours |
| 409 | `Slot is no longer open` | Already committed or abandoned |
| 429 | `Quota exceeded` | Carries both remaining counts |
| 503 | `Engine not configured` | No engine is wired |

---

## GET /lookup/slots/{lookupSlotId}/progress

Reports the slot's state and each query's own, without the verdict bodies. Polling only. Does not refresh the idle window.

**Response: 200 OK**, the [progress](#progress) shape. `counts.registered` and `counts.errata` are always `0` on a look-up slot.

```json
{
  "slotId": "sl_4d7h2j9k1m3n5p7q9r2s4t",
  "status": "committed",
  "counts": { "total": 2, "held": 0, "registered": 0, "answered": 2, "failed": 0, "errata": 0 },
  "entries": [
    { "imageHash": "3b8f1a...9e4c", "state": "answered", "registrationId": null, "failureReason": null },
    { "imageHash": "a17c05...2f91", "state": "answered", "registrationId": null, "failureReason": null }
  ]
}
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, expired, purged, or not yours |

---

## GET /lookup/slots/{lookupSlotId}/results

Reads the verdicts back. Same shape the commit answered. Readable until the slot's `expiresAt`, then the slot is purged and this route answers `404`.

```bash
curl -sS "$BASE/lookup/slots/$LOOKUP_SLOT_ID/results" -H "Authorization: Bearer $TOKEN"
```

**Response: 200 OK**

| Field | Type | Description |
|-------|------|-------------|
| `lookupSlotId` | `string` | The slot's id. |
| `queries` | `array` | One entry per query the slot holds, with its state, verdict and failure reason. |

```json
{
  "lookupSlotId": "sl_4d7h2j9k1m3n5p7q9r2s4t",
  "queries": [
    {
      "imageHash": "3b8f1a...9e4c",
      "state": "answered",
      "result": {
        "matched": true,
        "matchedOriginalImageHashes": ["9f2c7a...b2e5"],
        "candidates": []
      },
      "failureReason": null
    }
  ]
}
```

**Statuses:**

| Status | Title | Meaning |
|--------|-------|---------|
| 404 | `Slot not found` | Unknown, expired, purged, or not yours |

See [Look up in batches](/docs/xio/parallax/rest-api/look-up-in-batches).

---
<!-- xion:trust
{
  "v": 1,
  "canon_v": 1,
  "ctx": "xiobjects.com/content",
  "hash_blake3_hex": "ee76bf20fb0d4201bd87187850e4c0c16805d1f2850bcd5685ff4e40224040bd",
  "hash_sha256_hex": null,
  "sig_alg": "ed25519",
  "sig_b64": "s47hK4ZAqCOT3MWeWKHmlHFO76ok619StUkwdFSmM3t8sZbN6pnw2LhusjuBSiUm7Gy4EQP5ZTydjATM47LvCw",
  "pubkey_b64": "qvdEfSxeAWQCSmFhYZ6YX5kI935su0PASlrB7Yi2nJ8",
  "x509_chain_pem": [
    "-----BEGIN CERTIFICATE-----\r\nMIIB9TCCAaegAwIBAgIRAMAcad\u002BzF5t\u002B/s4nONSc6aAwBQYDK2VwMC4xLDAqBgNV\r\nBAMMI1hJIE9iamVjdHMgSW5jIENvbnRyb2wgSW50ZXJtZWRpYXRlMB4XDTI2MDky\r\nMzA0MTAzMFoXDTI2MTAyMzA0MTAzMFowSzEeMBwGA1UEAwwVeGlvLWNvbnRlbnQt\r\ncHVibGlzaGVyMRcwFQYDVQQKDA5YSSBPYmplY3RzIEluYzEQMA4GA1UECwwHQ29u\r\ndGVudDAqMAUGAytlcAMhAKr3RH0sXgFkAkphYWGemF\u002BZCPd\u002BbLtDwEpawe2Itpyf\r\no4G8MIG5MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoG\r\nCCsGAQUFBwMkMGUGA1UdIwReMFyAFDspt5hZsP6rNX4Cq7owpMYa05OyoS6kLDAq\r\nMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2YgUHJvdmVuYW5jZSBSb290IENBghRSYDf4\r\nsUJ\u002B9h\u002Bod0\u002BZRK/X/JSUBTAdBgNVHQ4EFgQUg7Gut2vWupuiLVcKgZt1GwdYmggw\r\nBQYDK2VwA0EA1cI0DTLhDQyTflrGrMlnMT/3Iw2c1OXVYphjr0uXnCmX1Dt5sNYT\r\niTgydyG3BPQqqiZ253V1ltTxT68ZA2gNAg==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIByDCCAXqgAwIBAgIUUmA3\u002BLFCfvYfqHdPmUSv1/yUlAUwBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMxNzEyWhcNMzAxMTAxMDMxNzEyWjAuMSwwKgYDVQQDDCNYSSBPYmplY3RzIElu\r\nYyBDb250cm9sIEludGVybWVkaWF0ZTAqMAUGAytlcAMhAFSS/pggSRmTcAMko7uc\r\nATH8OHgxVymd5mBFlPXbJkgio4GtMIGqMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD\r\nVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBQ7KbeYWbD\u002BqzV\u002BAqu6MKTGGtOTsjBlBgNV\r\nHSMEXjBcgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQKEupCwwKjEoMCYGA1UEAwwfSW5z\r\ndGl0dXRlIG9mIFByb3ZlbmFuY2UgUm9vdCBDQYIUJqoJlpiSFg\u002B7W5IJLMrLttgR\r\nQp4wBQYDK2VwA0EA5FOht7YOsVRPp/FOKMQ\u002B3Mo9JxrvGR3ylKWAWNm6OUV7N3DB\r\nI9cD62wU5I0d0EKDBy0CX9DnoqUyxv5yguraAA==\r\n-----END CERTIFICATE-----\r\n",
    "-----BEGIN CERTIFICATE-----\r\nMIIBaTCCARugAwIBAgIUJqoJlpiSFg\u002B7W5IJLMrLttgRQp4wBQYDK2VwMCoxKDAm\r\nBgNVBAMMH0luc3RpdHV0ZSBvZiBQcm92ZW5hbmNlIFJvb3QgQ0EwHhcNMjUxMTAy\r\nMDMwNTEyWhcNMzUxMDMxMDMwNTEyWjAqMSgwJgYDVQQDDB9JbnN0aXR1dGUgb2Yg\r\nUHJvdmVuYW5jZSBSb290IENBMCowBQYDK2VwAyEAEWNZl\u002Br3IC7\u002BgBh90Yo1kWk1\r\npZCVzVuFdFT7qBBU8W2jUzBRMB0GA1UdDgQWBBQAZRTDswSVORu\u002BkUOKX6WvrOvm\r\nQDAfBgNVHSMEGDAWgBQAZRTDswSVORu\u002BkUOKX6WvrOvmQDAPBgNVHRMBAf8EBTAD\r\nAQH/MAUGAytlcANBAO6QeydOFNrN75qNyftggYudsxMyl4w9qWkSdZ6hlhrRcbSr\r\niG9Si0kbrIJOwYB/LTBU0RM4Rl\u002Bo9PM3Qp0mPwo=\r\n-----END CERTIFICATE-----\r\n"
  ],
  "key_id": "i1xUjBgnfprOkR49BjDnH_u3g5aYtfteENcAGMjJGlA",
  "created_at": "2026-09-23T10:54:37Z"
}
-->