AI Knowledge & Logic

Organization Store

Store durable JSON state shared by your organization's Python actions, event handlers, condition providers, and recurring jobs.

The organization store is a durable JSON key-value store available to every Python execution in your organization. Use it when multiple conversations or scheduled jobs need to share small amounts of state.

Common uses include:

  • Tracking orders that a recurring job must revisit
  • Coordinating an event handler with a custom action
  • Saving a cursor so the next recurring-job run can resume a paginated import
  • Making an operation idempotent across conversations

Entries are shared across every business in the organization. Every entry has a namespace, key, JSON value, revision, and timestamps:

{
  "namespace": "pending-cancellations",
  "key": "order-123",
  "value": {
    "status": "pending",
    "cancelAt": "2026-09-02T18:30:00Z"
  },
  "revision": 1,
  "createdAt": "2026-09-02T12:00:00Z",
  "updatedAt": "2026-09-02T12:00:00Z"
}

Do not store passwords, API keys, or access tokens here. Use Organization Secrets for credentials.


Functions

These functions are available without imports in custom actions, condition providers, prompt sections, event handlers, sidebar widgets, and recurring jobs.

FunctionPurpose
organization_store_create(namespace, key, value)Create a new entry; fails if the key already exists
organization_store_get(namespace, key)Return one entry, or None when it does not exist
organization_store_upsert(namespace, key, value, if_revision=None)Create or replace an entry
organization_store_compare_and_set(namespace, key, value, if_revision)Replace an entry only when its revision still matches
organization_store_delete(namespace, key, if_revision=None)Delete an entry and return whether it existed
organization_store_list(namespace, *, limit=100, cursor=None, order_by="createdAt", …)List a bounded page
organization_store_search(namespace, filter, *, limit=100, cursor=None, …)Search using exact JSON containment and return a bounded page

All mutating functions return the complete current entry except organization_store_delete, which returns a boolean.


Create, get, and upsert

def execute_action(context):
    order_id = context["args"]["orderId"]

    entry = organization_store_create(
        "pending-cancellations",
        order_id,
        {
            "conversationId": context["conversation"]["id"],
            "status": "pending",
            "cancelAt": context["args"]["cancelAt"],
        },
    )

    return {"storedRevision": entry["revision"]}

Use organization_store_create when overwriting an existing value would be a bug. Use organization_store_upsert when the latest value should always win:

organization_store_upsert(
    "sync-cursors",
    "shopify-orders",
    {"cursor": next_cursor},
)

Read an entry with:

entry = organization_store_get("sync-cursors", "shopify-orders")
if entry is None:
    cursor = None
else:
    cursor = entry["value"]["cursor"]

Atomic compare-and-set

A normal read followed by a write is unsafe when a customer action and recurring job could run at the same time. Both executions might read "pending" and then both act.

Compare-and-set writes only when the entry still has the revision you read:

entry = organization_store_get("pending-cancellations", order_id)
if entry is None or entry["value"]["status"] != "pending":
    return {"claimed": False}

claimed_value = {
    **entry["value"],
    "status": "processing",
}

try:
    claimed = organization_store_compare_and_set(
        "pending-cancellations",
        order_id,
        claimed_value,
        if_revision=entry["revision"],
    )
except OrganizationStoreConflictError:
    # Another action or job changed the entry first.
    return {"claimed": False}

return {"claimed": True, "revision": claimed["revision"]}

Exactly one competing transition can succeed. The returned entry has the incremented revision.

OrganizationStoreConflictError.current_revision contains the latest revision when the entry still exists. A missing entry can also produce a conflict when a conditional update expected it to exist.

Compare-and-set coordinates your Python code; it does not make an external API call transactional. Before charging, refunding, or cancelling, re-read the external system and use its idempotency support where available.

Conditional deletion uses the same protection:

organization_store_delete(
    "pending-cancellations",
    order_id,
    if_revision=entry["revision"],
)

Listing and pagination

Every list is bounded. The default page contains 100 entries and the maximum is 500.

page = organization_store_list(
    "pending-cancellations",
    limit=500,
    order_by="createdAt",
    direction="asc",
)

for entry in page["entries"]:
    process(entry)

if page["nextCursor"] is not None:
    next_page = organization_store_list(
        "pending-cancellations",
        limit=500,
        cursor=page["nextCursor"],
        order_by="createdAt",
        direction="asc",
    )

Pass nextCursor back unchanged and keep the same order_by and direction. Supported sort fields are createdAt and updatedAt; both use the key as a deterministic tie-breaker.

Optional key_prefix limits results to keys beginning with an exact prefix:

page = organization_store_list(
    "webhook-deliveries",
    key_prefix="shopify:",
    limit=100,
)

For resumable background work, prefer ascending createdAt order so old records cannot be hidden by a continuous stream of new records. A page limit restricts one response; use the cursor when you need later pages.


Searching JSON

Search uses exact JSON containment. All fields in the filter must be present with matching values:

page = organization_store_search(
    "pending-cancellations",
    {
        "status": "pending",
        "order": {"market": "US"},
    },
    limit=100,
    order_by="createdAt",
    direction="asc",
)

This matches:

{
  "status": "pending",
  "cancelAt": "2026-09-02T18:30:00Z",
  "order": {
    "market": "US",
    "provider": "shopify"
  }
}

It does not provide arbitrary substring, numerical-range, or timestamp-range queries. Fetch a bounded page and apply those comparisons in Python. If a namespace can grow beyond one page, paginate until you have checked the required records.

Sorting by createdAt does not imply sorting by a timestamp inside the JSON value. Do not stop at the first future cancelAt unless your own key design guarantees that ordering.


Limits and safeguards

ResourceLimit
Namespace100 characters; lowercase letters, numbers, ., _, and -
Key200 characters; letters, numbers, ., _, :, @, +, and -
JSON value15,000 UTF-8 bytes and no more than 20 nested levels
Search filter4,000 UTF-8 bytes
Entries per namespace2,500
Entries per organization10,000
Entries returned per request500 maximum
REST API trafficIncluded in the organization-wide REST limit of 500 requests per minute

Completed or abandoned state should be deleted or moved to an external reporting system. The organization store is intended for operational state, not unbounded event history, analytics, documents, or large API responses.

All access is scoped from the authenticated organization API key. Python cannot provide an organization ID to read or write another organization's entries.

On this page