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

# App storage

> Keep your app's own JSON state in a namespaced key-value store, with keys, values, TTLs and per installation caps.

## Overview

App storage is a namespaced key-value store owned by one installation of your
app. Use it for state that belongs to your app rather than to a store record: a
sync cursor, a cached access token, the last product id you processed, or a
small settings object.

It is not a replacement for [metafields](https://docs.salesive.com/apps-api/metafields.md). A metafield is
typed data attached to a product, order, customer or the store, and the merchant
can see it. App storage is private to your installation: the merchant cannot
read it, and another app cannot see it even when it uses the same key.

## Authentication

Send your `app_` access token:

```http theme={null}
Authorization: Bearer app_<your-app-access-token>
```

No OAuth scope is required, because this is your app's own data and not a store
resource. The installation, and therefore the store and the namespace, comes from
the token. A merchant session is refused with `403`.

## Endpoints

All paths are under `https://api.salesive.com/api/v1`.

| Method | Path | What it does |
| - | - | - |
| `GET` | `/app/storage?prefix=&limit=` | List live keys, ordered by key |
| `GET` | `/app/storage/:key` | Read one value |
| `PUT` | `/app/storage/:key` | Create or replace one value |
| `DELETE` | `/app/storage/:key` | Delete one key |
| `DELETE` | `/app/storage` | Delete every key for this installation |

A key may contain `/`, so a hierarchical key such as `settings/theme` works. Send
the slash raw or percent encoded; both are accepted.

## Keys

A key is 1 to 200 characters from this alphabet:

```text theme={null}
A to Z, a to z, 0 to 9, and . _ ~ : @ + - /
```

* Slashes are allowed for hierarchy.
* A key cannot start or end with `/`, cannot contain `//`, and cannot contain a
  `.` or `..` segment.
* Whitespace, control characters, `?`, `#` and `%` are not allowed.
* A key is unique inside one installation. Two installations of your app, or two
  apps on one store, can use the same key without seeing each other's value.

## Values

A value is any JSON value: an object, array, string, number, boolean or `null`.
The cap is measured on the UTF-8 byte length of `JSON.stringify(value)`:

| Rule | Value |
| - | - |
| One value | At most **64 KB** (65,536 bytes) |
| All values in one installation | At most **1 MB** (1,048,576 bytes) |
| Keys in one installation | At most **1,000** |

A circular value, a function or a symbol is refused with `400`, because it has no
JSON form. A value over 64 KB is `413` with code `value_too_large`.

The key and byte caps are checked under a short lived per-installation lock, so
two parallel writes cannot both pass the same check. A write that would cross
either cap is `413` with code `storage_limit_exceeded` and the current, projected
and requested numbers. Replacing a value charges only the difference, so a write
that shrinks a value always fits when the key already exists.

## TTL

`PUT` accepts an optional `ttlSeconds`:

| Rule | Value |
| - | - |
| Minimum | 60 seconds |
| Maximum | 2,592,000 seconds (30 days) |
| Omitted or `null` | The key never expires |

`ttlSeconds` must be a whole number in that range. An expired key is not returned
by a read or a list, and MongoDB removes the row within about a minute of its
expiry. Writing the key again with no `ttlSeconds` removes the expiry.

## Put a value

```bash theme={null}
curl -X PUT "https://api.salesive.com/api/v1/app/storage/settings%2Ftheme" \
  -H "Authorization: Bearer $APP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "value": { "primary": "#0d65d9" }, "ttlSeconds": 86400 }'
```

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Storage key created",
  "data": {
    "key": "settings/theme",
    "value": { "primary": "#0d65d9" },
    "expiresAt": "2026-11-08T14:00:00.000Z",
    "createdAt": "2026-11-07T14:00:00.000Z",
    "updatedAt": "2026-11-07T14:00:00.000Z",
    "created": true,
    "usage": { "keys": 1, "bytes": 21 }
  }
}
```

`message` is `Storage key created` or `Storage key updated`, and `created` says
which. The status is `200` in both cases.

## Read and list

`GET /app/storage/:key` returns the same `key`, `value`, `expiresAt`, `createdAt`
and `updatedAt` fields. A missing or expired key is `404`.

`GET /app/storage?prefix=settings%2F&limit=50` returns live keys whose name starts
with the prefix, ordered by key:

```json theme={null}
{
  "status": 200,
  "success": true,
  "message": "Storage entries retrieved",
  "data": {
    "items": [
      {
        "key": "settings/theme",
        "value": { "primary": "#0d65d9" },
        "expiresAt": null,
        "createdAt": "2026-11-07T14:00:00.000Z",
        "updatedAt": "2026-11-07T14:00:00.000Z"
      }
    ],
    "limit": 50
  }
}
```

`limit` defaults to 50 and is capped at 100. A prefix may be up to 200 characters
from the key alphabet and may end in `/` or in a partial segment.

## Delete

`DELETE /app/storage/:key` returns `{ "key": "settings/theme", "deleted": true }`.
A missing key is `404`.

`DELETE /app/storage` clears the whole namespace and returns
`{ "deleted": 12 }` with the number of rows removed. It cannot be undone.

## Uninstall

Uninstalling your app deletes every storage row for that installation, together
with its lock. A reinstall starts with an empty namespace. The shop redaction
sweep also removes rows left behind by installations that were already
uninstalled.

## Errors

| Status | Code | Meaning |
| - | - | - |
| `400` | `invalid_key` | The key is empty, too long, or uses a character the alphabet does not allow |
| `400` | `invalid_value` | The value has no JSON form |
| `400` | `invalid_ttl` | `ttlSeconds` is not a whole number from 60 to 2,592,000 |
| `400` | `invalid_limit` | `limit` is not a whole number from 1 to 100 |
| `403` | | The caller is not an installed app, or the token is not bound to an installation |
| `404` | | The key does not exist or has expired |
| `409` | `storage_busy` | Another write held the installation lock for more than 2 seconds. Retry |
| `413` | `value_too_large` | One value is over 64 KB |
| `413` | `storage_limit_exceeded` | The write would cross the 1,000 key or 1 MB cap |

## Next steps

> [card] [Metafields](https://docs.salesive.com/apps-api/metafields.md)
>
> Typed data attached to a store record, visible to the merchant.

> [card] [Bulk operations](https://docs.salesive.com/apps-api/bulk-operations.md)
>
> Export and import large record sets in the background.

> [card] [Rate limits](https://docs.salesive.com/apps-api/rate-limits.md)
>
> The per installation bucket that every app call shares.

> [card] [Data protection](https://docs.salesive.com/apps/data-protection.md)
>
> What happens to app data on uninstall and on a shop or customer erasure request.


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