You can manage the Lookup Tables from the InOne panel or programmatically through the public API. The API gives you full control over the lifecycle of a Lookup Table: you can create the table, define its schema, ingest rows, retrieve them, clear values, or delete rows.
This page walks through every endpoint with a request and response example. All examples use one running scenario (a store’s Lookup Table) so you can follow each call end-to-end and see how the data flows from creation to activation.
All endpoints follow the same authentication model and base URL: https://unification.useinsider.com/api/lookup/v1
Supported column types
When you define columns for a Lookup Table, you choose a type for each one. The type controls how the value is stored, what segmentation operators are available, and how the value renders in dynamic content. Pick the type carefully because once a column has a type, you can only change it to one of the allowed types.
Type | Description | Example value |
|---|---|---|
String | Text values up to 1,024 characters | “London” |
Number | Integer or decimal numbers | 29.99 or 42 |
Boolean | True or False values | true |
Datetime | ISO datetime string or unix timestamp | "2026-04-15T10:00:00Z" |
URL | Valid web link, format-validated | "https://example.com/store/1" |
Strings | Array of text values | ["premium", "loyal"] |
Numbers | Array of numeric values | [101, 202, 303] |
The primary key column must be of type string or number. The other types (boolean, datetime, url, strings, numbers) are not allowed for the primary key, because the primary key needs a deterministic value that can be matched against user attributes or event parameters.
Scenario: Store Locator
Throughout the rest of this page, every example uses the same scenario. Imagine you run a retail brand with hundreds of stores. You want to power campaigns with each store’s opening hours, current local promo, and store type, without copying that information onto every customer profile.
You set up a Lookup Table called “Stores”. The primary key is store_id and is bound to the user attribute preferred_store_id. Once bound, segmentation and dynamic content can reference any column in the stores table for the customer’s preferred store.
Schema used in examples
Column | Type | Role |
|---|---|---|
store_id | string | Primary key. Unique store identifier |
city | string | City the store is located in |
open_time | string | Daily opening time in HH:MM format |
close_time | string | Daily closing time in HH:MM format |
local_promo | string | Currently active local promo, if any |
store_type | string | flagship, standard, or outlet |
Binding
store_id is bound to the user attribute preferred_store_id. With this binding in place, you can segment users by store attributes (for example, all users whose preferred store is a flagship), and you can use store columns directly in dynamic content (for example, “Your {{store.city}} store opens at {{store.open_time}}”).
Endpoints
Create a Lookup Table
When to use: You are setting up a new Lookup Table from scratch. Use this endpoint to define the schema, primary key, and optional bindings in a single call, so the table is ready for data ingestion immediately after creation.
What this call does: Validates your schema, creates the table in Draft state, registers the columns and primary key, and configures the join binding if provided. After a successful response, you can begin uploading rows. The table does not appear in segmentation or dynamic content until at least one binding is added and rows are ingested.
Endpoint: POST /api/lookup/v1/table/create
Sample Request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/table/create' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"display_name": "Stores",
"join_key": "store_id",
"columns": [
{
"name": "store_id",
"type": "string",
"display_name": "Store ID"
},
{
"name": "city",
"type": "string",
"display_name": "City"
},
{
"name": "open_time",
"type": "string",
"display_name": "Open Time"
},
{
"name": "close_time",
"type": "string",
"display_name": "Close Time"
},
{
"name": "local_promo",
"type": "string",
"display_name": "Local Promo"
},
{
"name": "store_type",
"type": "string",
"display_name": "Store Type"
}
],
"bindings": [
{
"attribute": "preferred_store_id"
}
]
}'Sample Response:
{
"error": null
}Notes:
table_name must be lowercase, start with a letter, and contain only lowercase letters, numbers, and underscores. Maximum 40 characters.
display_name must be 3 to 40 characters and contain only letters, numbers, and spaces. It must be unique across your tables (case-insensitive).
Column and table names must be unique within your panel.
Reserved names (iid, partner, crea, upta, actv, writ, source, event_name, inserted_at, and any name starting with an underscore) cannot be used.
join_key (Primary key) must reference one of the columns. The column type must be string or number.
You need at least 2 columns: 1 primary key plus 1 data column. Maximum 100 columns per table.
Binding is optional at table creation. If you skip it, the table is created without any binding, and you can add it later with the update endpoint. Each Lookup Table supports a maximum of 10 attribute bindings and 10 event parameter bindings.
Upsert single row
When to use: You want to insert or update one specific row identified by its primary key. For example, the Istanbul flagship store updates its closing time and pushes a new local promo.
What this call does: Looks up the row by the primary key value provided in join_key. If a matching row exists, the specified columns are updated. If no matching row exists, a new row is created. Columns not included in the request are left unchanged.
Endpoint: POST /api/lookup/v1/row/upsert
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/upsert' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"join_key": {
"store_id": "ist-flagship-001"
},
"columns": {
"city": "Istanbul",
"open_time": "10:00",
"close_time": "23:00",
"local_promo": "Spring Drop, 20% off accessories",
"store_type": "flagship"
},
"request_id": "upsert-stores-001",
"hook_endpoint": "https://example.com/webhook/lookup"
}'Sample response:
{
"request_id": "upsert-stores-001",
"accepted_rows": 1,
"rejected_rows": 0,
"rejection_details": [],
"warnings": []
}Notes:
Primary (join) key name must match the table’s primary key (store_id in this scenario).
Unknown column names are silently skipped. They do not cause the row to be rejected.
Empty string and empty array values are silently skipped and reported in the warnings field.
If all columns end up empty or unknown after filtering, the row is rejected.
1 upsert per minute per primary key value.
request_id is optional but recommended. It enables progress tracking through the Get Progress endpoint.
Updating a single cell
To update only one cell, send only that column in the columns object.
For example, suppose the Istanbul flagship store launches a new promo and you want to update only the local_promo column. You send a single-cell upsert:
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/upsert' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"join_key": {
"store_id": "ist-flagship-001"
},
"columns": {
"local_promo": "Summer Sale, 30% off"
},
"request_id": "upsert-promo-only-001"
}'Sample response:
{
"request_id": "upsert-promo-only-001",
"accepted_rows": 1,
"rejected_rows": 0,
"rejection_details": [],
"warnings": []
}After this call, the row for ist-flagship-001 keeps its existing city, open_time, close_time, and store_type values. Only local_promo changes.
Notes:
If you send only the primary key with no other columns (or an empty columns object), the row is rejected with “columns must not be empty”.
If you send a column with an empty string or empty array, it is silently skipped, not stored as empty. To explicitly clear a cell, use Delete Single Row instead.
You can send at most 1 upsert per minute per primary key, so a single-cell update counts as one write.
If the row does not exist yet, the upsert creates it with only that one cell populated. Other columns will be empty for that row until you upload them later.
To update multiple cells in one row, include them all in the same columns object. That counts as one write and is more efficient than several single-cell upserts to the same primary key.
Upsert bulk rows
When to use: You want to insert or update many rows in a single request. For example, pushing daily store data updates for hundreds of locations.
What this call does: Validates each row independently. Valid rows are accepted and processed. Invalid rows are returned in rejection_details with their position in the request array. The rest of the request still succeeds. If every row is invalid, the entire request is rejected with a 400 response.
Endpoint: POST /api/lookup/v1/row/upsert-bulk
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/upsert-bulk' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"rows": [
{
"join_key": {
"store_id": "ist-flagship-001"
},
"columns": {
"city": "Istanbul",
"open_time": "10:00",
"close_time": "23:00",
"local_promo": "Spring Drop, 20% off accessories",
"store_type": "flagship"
}
},
{
"join_key": {
"store_id": "ldn-002"
},
"columns": {
"city": "London",
"open_time": "10:00",
"close_time": "22:00",
"store_type": "standard"
}
},
{
"join_key": {
"store_id": "ams-outlet-007"
},
"columns": {
"city": "Amsterdam",
"open_time": "11:00",
"close_time": "21:00",
"local_promo": "Outlet, extra 10% on shoes",
"store_type": "outlet"
},
"timestamp": 1745870400
}
],
"request_id": "bulk-stores-2026-04-27",
"hook_endpoint": "https://example.com/webhook/lookup"
}'200 OK — all rows accepted
{
"request_id": "bulk-stores-2026-04-27",
"accepted_rows": 3,
"rejected_rows": 0,
"rejection_details": [],
"warnings": []
}200 OK — partial accept
{
"request_id": "bulk-stores-2026-04-27",
"accepted_rows": 2,
"rejected_rows": 1,
"rejection_details": [
{
"index": 1,
"reason": "string value must be at most 1024 characters"
}
],
"warnings": []
}Notes:
Maximum 100 rows per request. If you have more, send them in batches.
Partial accept model: valid rows are accepted; invalid rows are returned in rejection_details in your request body.
Duplicate primary key values within the same request are rejected (the second occurrence is dropped).
If all rows are invalid, the response is HTTP 400 with no request_id.
Delete single row
When to use: You want to remove one specific row from a Lookup Table. For example, a store closes permanently and you want segmentation and dynamic content to stop targeting it.
What this call does: Queues the row for deletion by primary key. Once processed, the entire row is removed including its primary key value.
Endpoint: POST /api/lookup/v1/row/delete
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/delete' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"join_key": {
"store_id": "ams-outlet-007"
},
"request_id": "del-store-001",
"hook_endpoint": "https://example.com/webhook/lookup"
}'Sample response:
{
"request_id": "del-store-001"
}Notes:
Use Get Progress with the request_id to track completion.
1 write per minute per primary key value is shared with upsert.
To clear only specific column values while keeping the row, use Delete Column Values.
Delete bulk rows
When to use: You want to remove many rows in a single request. For example, a franchise group exits the network and you need to remove hundreds of store rows.
What this call does: Validates each primary key independently. Invalid keys are returned in rejection_details with their position. Valid keys are queued for deletion. The rest of the request still succeeds if some keys are invalid. If every key is invalid, the entire request is rejected.
Endpoint: POST /api/lookup/v1/row/delete-bulk
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/delete-bulk' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"join_keys": [
{
"store_id": "ams-outlet-007"
},
{
"store_id": "ams-outlet-008"
},
{
"store_id": "ams-outlet-009"
}
],
"request_id": "del-bulk-2026-04-27",
"hook_endpoint": "https://example.com/webhook/lookup"
}'Sample response:
{
"request_id": "del-bulk-2026-04-27",
"accepted_rows": 3,
"rejected_rows": 0,
"rejection_details": []
}Notes:
Maximum 100 primary keys per request.
Invalid keys are listed in rejection_details in the response.
Duplicate primary key values within the same request are rejected.
Delete column values
When to use: You want to clear specific column values for a set of rows while keeping the rows themselves. For example, a seasonal promotion ends, and you clear the local_promo column across many stores without deleting any of them.
What this call does: Clears the listed column values from each specified row. The rest of the row is untouched. After processing, cleared columns do not appear in Get Rows responses, segmentation treats them as empty, and dynamic content templates render them as empty strings.
Endpoint: POST /api/lookup/v1/row/delete-values
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/delete-values' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"rows": [
{
"join_key": {
"store_id": "ist-flagship-001"
},
"columns": [
"local_promo"
]
},
{
"join_key": {
"store_id": "ank-002"
},
"columns": [
"local_promo",
"concierge_phone"
]
}
],
"request_id": "delval-promo-end-001",
"hook_endpoint": "https://example.com/webhook/lookup"
}'Sample response:
{
"request_id": "delval-promo-end-001",
"accepted_rows": 2,
"rejected_rows": 0,
"rejection_details": []
}Notes:
The row itself is preserved. Only the listed column values are cleared.
The primary key column cannot appear in the columns array. You cannot clear a primary key value, only the row as a whole.
Each column you list must exist in the table. Listing an unknown column rejects that row.
Add columns
When to use: You want to enrich an existing Lookup Table with new columns without rebuilding it or losing existing row data.
What this call does: Validates the new column definitions against the existing schema and registers them. After a successful response, new column values can be ingested via Upsert Single Row or Upsert Bulk Rows. Existing rows are unaffected — the new columns are empty for all existing rows until values are uploaded.
Endpoint: POST /api/lookup/v1/column/add
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/column/add' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"columns": [
{
"name": "concierge_phone",
"type": "string",
"display_name": "Concierge Phone"
},
{
"name": "tags",
"type": "strings"
}
]
}'Sample response:
{
"error": null
}Notes:
Existing column names and types cannot be renamed through this endpoint. You can only add new columns.
Duplicate column names return a 409 Conflict error.
Column names follow the same naming rule as the table: lowercase, start with a letter, max 40 characters.
List Lookup Tables
When to use: You want to see every Lookup Table configured for your panel, including row counts, bindings, and column schemas. Useful for integration teams verifying setup or administrators auditing active tables.
What this call does: Returns metadata for every Lookup Table in your panel. Row data is not returned — use Get Rows for that. Tables are ordered by creation date, newest first.
Endpoint: POST /api/lookup/v1/table/list
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/table/list' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{}'{
"tables": [
{
"table_name": "stores",
"display_name": "Stores",
"join_key": "store_id",
"row_count": 248,
"bindings": [
{
"attribute": "preferred_store_id"
}
],
"columns": [
{
"name": "store_id",
"type": "String",
"display_name": "Store ID"
},
{
"name": "city",
"type": "String",
"display_name": "City"
},
{
"name": "open_time",
"type": "String",
"display_name": "Open Time"
},
{
"name": "close_time",
"type": "String",
"display_name": "Close Time"
},
{
"name": "local_promo",
"type": "String",
"display_name": "Local Promo"
},
{
"name": "store_type",
"type": "String",
"display_name": "Store Type"
}
],
"created_at": "2026-04-12 10:30:00",
"updated_at": "2026-04-27 14:00:00"
}
],
"total_column_count": 6
}Get rows
When to use: You want to read back the values of one or more rows by primary key. For example, after a bulk upload you want to verify that specific stores have the expected data.
What this call does: Fetches each row by its primary key and returns the current column values. Missing keys are returned in not_found rather than as errors.
Endpoint: POST /api/lookup/v1/row/get
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/row/get' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"join_keys": [
{
"store_id": "ist-flagship-001"
},
{
"store_id": "ank-002"
},
{
"store_id": "missing-store-999"
}
]
}'Sample response:
{
"rows": [
{
"join_key": {
"store_id": "ist-flagship-001"
},
"columns": {
"city": "Istanbul",
"open_time": "10:00",
"close_time": "23:00",
"local_promo": "Spring Drop, 20% off accessories",
"store_type": "flagship"
}
},
{
"join_key": {
"store_id": "ank-002"
},
"columns": {
"city": "Ankara",
"open_time": "10:00",
"close_time": "22:00",
"store_type": "standard"
}
}
],
"found": 2,
"not_found": [
{
"store_id": "missing-store-999"
}
]
}Export Table
When to use: You want a full snapshot of a Lookup Table. For example, your data team needs the stores table for a quarterly audit, or you want to back up the data to your own warehouse.
What this call does: Queues an export of the full table to S3 in the format you specify. When the export is ready, a webhook fires to hook_endpoint with a signed download URL. The download URL is valid for 24 hours.
Endpoint: POST /api/lookup/v1/table/export
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/table/export' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"table_name": "stores",
"format": "csv",
"hook_endpoint": "https://example.com/webhook/export-done"
}'Sample response:
{
"error": null
}Notes:
Supported formats: csv, parquet, and json.
Rate limit: 1 export per Lookup Table per day.
hook_endpoint must be a valid HTTPS URL.
Get progress
When to use: You want to check the processing status of an asynchronous operation, such as bulk upsert, bulk delete, delete column values, or export. Use this endpoint after any operation where you provided a request_id.
What this call does: Returns the current processed, succeeded, and failed counts for the given request_id. There is no rate limit on this endpoint; it can be polled freely.
Endpoint: POST /api/lookup/v1/progress
Sample request:
curl --location --request POST 'https://unification.useinsider.com/api/lookup/v1/progress' \
--header 'X-PARTNER-NAME: mybrand' \
--header 'X-REQUEST-TOKEN: 1a2b3c4e5d6f' \
--header 'Content-Type: application/json' \
--data-raw '{
"request_id": "bulk-stores-2026-04-27"
}'Sample response:
{
"total": 100,
"success": 97,
"failed": 3
}Progress keys have a 24-hour TTL. After expiry, the response returns total: 0, success: 0, failed: 0
Error messages
400: Cell length exceeded
{
"data": {
"successful": {},
"fail": {
"count": 1,
"errors": {
"rows[0].columns.local_promo": [
"string value must be at most 1024 characters"
]
}
}
}
}400: Wrong primary key type
{
"data": {
"successful": {},
"fail": {
"count": 1,
"errors": {
"join_key.store_id": [
"join_key value must be a string for string-type join key"
]
}
}
}
}404: Table not found
{
"data": {
"successful": {},
"fail": {
"count": 1,
"errors": {
"table_name": [
"table 'stores' not found"
]
}
}
}
}429: Per-key rate limit
{
"data": {
"successful": {},
"fail": {
"count": 1,
"errors": {
"rate_limit": [
"per-key rate limit exceeded for join_key 'ist-flagship-001'"
]
}
}
}
}