# The app runtime API

The app's own door: `/api/apps/*`, opened by the app's key.

```
Authorization: Bearer cak_…
```

The key is printed once by `cartbase app deploy` (the first deploy) or by a
rotation, and lives in the environment where the app's server code runs,
`CARTBASE_APP_<KEY>_KEY`. It reaches this app's settings, secrets and record
types on this store, and nothing else. A member's token, a CLI token or a
session is refused here.

Every answer is JSON. Errors follow the platform's envelope:
`{ error, code, details? }`.

| Status | Code | When |
|---|---|---|
| 401 | `unauthenticated` | No key, or a revoked one |
| 403 | `app_disabled` | The merchant switched the app off |
| 403 | `app_scope` | The record type belongs to another app |
| 404 | `not_found` | A record type the manifest does not declare, or a record that is not this type's |
| 400 | `invalid_data` | A record that does not match its fields; `details.errors: [{key, code, message}]` |

## GET /api/apps/me

Who am I, on which store, with which settings.

```jsonc
{
  "app": { "id": "app_…", "key": "store-locator", "name": "Store locator", "version": "1.0.0", "status": "live" },
  "store": { "client_id": "…" },
  "settings": { "maps_key": "AIza…", "default_zoom": 7 },   // secrets DECRYPTED, here only
  "records": [
    { "type": "location", "store_type": "store-locator.location", "name": "Location", "visibility": "public", "fields": [ … ] }
  ]
}
```

`store_type` is what the storefront asks the store API for.

## GET /api/apps/records/:type

The app's records of one type. Query `q`, `status` (`draft` | `active`),
`limit` (default 50, up to 200), `offset`.

```jsonc
{ "records": [ { "id": "mobj_…", "handle": "ay-gross-mega", "display_name": "AY GROSS MEGA", "fields": { "city": "Пловдив", … }, "status": "active", … } ], "count": 40, "offset": 0, "limit": 50 }
```

## POST /api/apps/records/:type

Create a record. Body `{ handle?, display_name?, fields?, status? }`. The
handle is derived from the display name when absent. `required` fields are
enforced when `status` is `active`. Answers `201 { record }`.

## GET /api/apps/records/:type/:id

One record with its fields resolved: `{ record, resolved_fields }`.

## POST /api/apps/records/:type/:id

Partial update, same body as create. The merged field set is validated, so
a patch cannot dodge a required field on activation. Answers `{ record }`.

## DELETE /api/apps/records/:type/:id

Answers `{ id, object: "record", deleted: true }`.

## The storefront's reads

Public record types answer to the publishable key at the store API, as any
metaobject does:

```
GET /api/store/metaobjects/<key>.<type>
GET /api/store/metaobjects/<key>.<type>/<handle>
```

Active records only; a private type is 404. The package wraps both:
`listAppRecords` and `getAppRecord` in `@cartbase/storefront/api/apps`.
