# Custom apps' records

A custom app installed on the store (app-registry card) may own record
types the storefront shows: a store locator's locations, a lookbook's
looks. They are metaobjects the app owns, typed `<app key>.<record type>`,
and the storefront reads them exactly as it reads any metaobject, with the
publishable key. **Public types only, active records only**: an app's
private type (its mapping table, its sync log) is 404 here.

The whole app story for an agent building one is in `docs/apps/` (the
manifest, the app's own API, the CLI). This page is the storefront's half.

## GET /api/store/metaobjects/:app.:type — an app's records

- **Purpose**: the active records of one of an app's public record types,
  newest-updated first, raw field values.
- **Auth**: anon (`x-client-id`) or the publishable key.
- **Request**: query `{limit?, offset?}` (`limit` 1 to 100, default 20).
- **Response**: `{metaobjects: [Metaobject…], count, offset, limit}`; the
  metaobject shape is [metaobjects.md](metaobjects.md)'s.
- **Errors**: `404 not_found` (no such app record type, or a private one).
- **SDK**: `apps.listAppRecords(client, appKey, recordType, query?)` composes
  the type; `apps.getAppRecord(client, appKey, recordType, handle)` reads one
  with references resolved.
- **Components**: the app's own storefront component, under `apps/<key>/`
  in the storefront project.
- **Settings**: the app's records are edited in the admin under Apps, the
  app, its records screen.

```bash
# A record type nobody deployed is a 404, the same answer a private one gives.
STATUS=$(curl -s -o /dev/null -w '%{http_code}' \
  "$BASE/api/store/metaobjects/doc-no-app-$RUN.location" -H "x-client-id: $CLIENT_ID")
test "$STATUS" = 404
```

```ts
import { listAppRecords } from "@cartbase/storefront/api/apps"

const { metaobjects: locations } = await listAppRecords(client, "store-locator", "location", { limit: 100 })
for (const location of locations) {
  const city = location.fields.find((f) => f.key === "city")?.value
}
```
