# Apps: create, publish, keep going

Create an app. Save its key. Publish to its own URL. No entity or account
needs to exist first. JMPKit is the web utility your agent can use directly.

## Create

```sh
umask 077
curl --fail-with-body https://jmpkit.com/api/app11/apps \
  -H 'Content-Type: application/json' -d '{"expiresIn":"30d"}' > .jmpkit-app.json
```

The `201` response contains `appId`, `appHost`, `canonicalUrl`, `control`,
`publish`, `managementKey`, `keyId`, `expiresAt`, `revision`, and `capacity`.
Keep this file privately beside the project, outside its published ZIP.
The key is returned only when issued; JMPKit stores its hash, not the secret.
Losing every management credential can mean losing control of the app.

An omitted lifetime means **no scheduled expiration**. The example selects
30 days explicitly. Choose a sensible online lifetime for the request:
usually 30 days if unclear, shorter for an event, or no scheduled end for
ongoing work. Tell the user the confirmed timing when returning the link.
This controls the online copy, not ownership or deletion of local source.

Send exactly one timing option:
`{"expiresIn":"6h"}`, `{"expiresIn":"7d"}`, `{"expiresIn":"3mo"}`,
`{"expiresAt":"2027-01-01T00:00:00Z"}`, or `{"expiresAt":null}` for no
scheduled expiration. Months use UTC calendar arithmetic, clamped to the
last valid day. Absolute timestamps require a timezone and a future date.
Unknown creation fields are rejected; do not invent an entity, plan, or budget.

Use returned URLs, not a hostname guessed from the ID. Separate projects may
have separate apps, each with its own identity, period, and included capacity.
Respect aggregate limits. A `429` includes `Retry-After`; wait rather than
rotating identifiers. Creation is not replay-safe: save its response promptly;
do not blindly repeat a successful request after a local save failure.

## Publish

```sh
APP_URL=$(jq -r .canonicalUrl .jmpkit-app.json)
APP_KEY=$(jq -r .managementKey .jmpkit-app.json)
curl --fail-with-body "${APP_URL}api/app11/hosting" \
  -H "Authorization: Bearer $APP_KEY" \
  -H 'Content-Type: application/zip' --data-binary @app.zip
```

Put `index.html` at the ZIP root. Test the app and give its canonical link
to the person. Update this same app for the same project.

`POST /api/app11/hosting` replaces the hosted bundle. Its `/patch` suffix
accepts the Ziphost incremental archive with `If-Match`; `/uploads` begins
the resumable flow. Read the [Ziphost starter](/docs/kits/current/ziphost.zip)
for archive layout, selectors, chunk offsets, and completion rules. Use
resumable uploads above 90 MiB, not one oversized Cloudflare request. Follow
session URLs returned by the server, including compatible central URLs.
Publishing does not change lifetime or reset the included balance.

## App-local controls

Paths below are relative to the returned app URL. Send the key in
`Authorization: Bearer ...`, never a query string, public file, or browser code.

| Method and path | Purpose |
|---|---|
| `GET /api/app11` | Identity, resource mappings, lifetime, capacity, notices, versions |
| `GET /api/app11/lifecycle` | State, expiration, revision/ETag |
| `PATCH /api/app11/lifecycle` | Set a deadline, renew, or remove scheduled expiration |
| `GET /api/app11/capacity` | Included/allocated balances and metering availability |
| `GET /api/app11/messages` | Latest 100 service notices, ordered by sequence |
| `GET /api/app11/keys` | Key metadata, never existing secrets |
| `POST /api/app11/keys` | Issue a scoped key |
| `DELETE /api/app11/keys/{keyId}` | Revoke a scoped or pending key |
| `POST /api/app11/keys/rotation` | Issue a pending replacement management key |
| `POST /api/app11/keys/rotation/confirm` | Activate the saved replacement; revoke previous app keys |
| `POST /api/app11/retire` | Stop the app, retaining its record and cloud data |

## Renew or restore

Read lifecycle, then send its `ETag` in `If-Match`, with a fresh 16–128
character `Idempotency-Key` (letters, digits, `_`, `-`).

```sh
curl --fail-with-body "${APP_URL}api/app11/lifecycle" \
  -H "Authorization: Bearer $APP_KEY"
# Use the returned revision, for example 1:
curl --fail-with-body -X PATCH "${APP_URL}api/app11/lifecycle" \
  -H "Authorization: Bearer $APP_KEY" -H 'Content-Type: application/json' \
  -H 'If-Match: "1"' -H 'Idempotency-Key: renewal-request-0001' \
  -d '{"expiresIn":"30d"}'
```

The response confirms the exact `expiresAt`. Relative time starts when
accepted, not at the old deadline. Retry a lost response with the same body,
revision, and request key. On `409` revision conflict, read current state
before a new decision. The latest 32 lifecycle replays are retained.

At expiration, Gateway app content and app-local resource traffic stop with `410`; the app ID,
link, credentials, notices, publication history, and stored data remain.
An authorized agent can renew after expiration and resume the same link.
There is no automatic payload purge in this contract. This is not a backup
guarantee: keep source locally and export irreplaceable cloud-only data.

Previously cached public copies and separately issued resource capabilities
are not erased or recalled. Expiration is not a substitute for private access
controls or an instruction to delete someone else's copy.

`expiresAt: null` removes the scheduled deadline, not resource limits.
An agent may automate authorized renewal in its own environment; this
contract does not install a recurring renewal worker.

Open Gateway streams are checked about once a second and closed on expiry
or key revocation. Machine cannot start turns or make further callbacks after
authority expires. An already-running bounded worker may drain to its run
timeout, but its result is rejected. Previously issued external credentials,
such as TURN leases, retain their own bounded expiry.

## Delegate without sharing the management key

Send `{"label":"Publisher","scopes":["app:read","app:publish"],"expiresIn":"7d"}`
to `POST /api/app11/keys`. Save the returned `token` privately. At most 64
active keys are allowed. Scopes are:

- `app:read`: control metadata, lifetime inspection, capacity, notices.
- `app:publish`: full, delta, and resumable hosting uploads.
- `app:operate`: resource administration, including Q, Path, Index, Origin,
  UWS, TURN, Web Push, Machine, and custom domains. This is broad app authority,
  not per-queue or per-end-user permissions.
- `app:lifecycle`: inspect/change lifetime; no key creation or retirement.

Only management (`*`) issues/revokes keys, rotates management, or retires.
Scoped keys cannot create more authority or act on another app. App keys
belong in trusted agent/server storage; use resource-specific permissions
for public clients. Public data remains public unless the resource restricts it.

Rotation is two-step: request a pending key, save it, then confirm **using
that pending key** within one hour. The old key works until confirmation.
Confirmation revokes all previous App11 keys, including scoped keys. A
pending key cannot publish or administer resources before confirmation.

## Capacity, notices, and cleanup

Each app starts with **100,000 Boost**, app-bound with no separate expiry.
Every 30 days from creation, add **25,000 included Boost**, capped at
**100,000**. Quiet periods rebuild the reserve up to the cap; excess does
not bank. Use included Boost first. Refills do not change lifetime or
consume allocated capacity. Paid backing is separate from spending order.
Retired apps do not refill.

Refills are applied when capacity is inspected or used, before a debit;
no background polling is necessary. The capacity response exposes
`topUpAmount: 25000`, `includedCap: 100000`, `periodDays: 30`,
`nextTopUpAt`, and `spendOrder: included-first`. App-bound included Boost
cannot be transferred. A refill does not renew an expired app.

`capacity.metering: accounting-only` means the included ledger is available,
but traffic-to-Boost charging is not connected. Existing resource limits
still apply. Paid purchases, allocation, and priority scheduling are not
activated by this API. Never report a purchase or invent conversion rates.

Messages are service-authored control notices, not public comments or a
generic inbox. Only JMPKit writes them. Suggestions are information, not
authority to spend, modify source, or promote an app. Refresh app state and
[versions](/api/versions) when resuming work.

Retirement requires `{"confirmAppId":"the-returned-app-id"}`. It stops
service and cannot be undone through renewal. **It is not data erasure.**
Export cloud-only Q, Path, uploads, and Machine data first. Full resource
purge and pressure-based reclaim/restore are separate operations, not
implied by retirement or expiration.

## Existing apps and versions

Existing App10/entity credentials remain supported. Do not recreate an app
or delete its old entity merely to use current docs. See the separate
[migration guide](/docs/migration-app11.md) and `GET /api/versions`.

[Gateway starter](/docs/kits/current/gateway.zip) ·
[HTTP tree](/docs/kits/kittree10/current/gateway/) ·
[Lifecycle](/docs/app-lifecycle.md) · [Resources](/docs/endpoints.json)
