# App distribution feeds

> The Sparkle appcast feeds and download URLs desktop apps update from, and who gets what.

Desktop apps bought on Gujo update from the distribution feeds on the app host. Feeds use the Sparkle 2 format, and on every request the server builds the feed from only the releases that caller may get.

## Feed URL

```text
https://app.gujo.ai/distribution/v1/products/{id}/appcast.xml
```

```bash
curl https://app.gujo.ai/distribution/v1/products/42/appcast.xml \
  -H "Authorization: Bearer $GUJO_DEVICE_KEY"
```

- `{id}` is the numeric id of the app product.
- An app product's feed always needs `Authorization: Bearer <device key>`, and that key needs the `device.install` ability. A key bound to one product must be bound to this one.
- The response is `application/rss+xml; charset=UTF-8`, and consumers need Sparkle 2.9 or later.
- Responses that saw a device key carry `Cache-Control: no-store, private` and `Vary: Authorization`. Sessions and cookies are not used.
- The feed URL is the installed app's `SUFeedURL`, which cannot change without shipping the app again. That is why this path stays fixed.

## Who gets what

Each state of an app product decides who may fetch it and which releases it carries.

| Product state | Who may fetch | Releases carried |
|---|---|---|
| Draft (`draft`) | Device key + an entitlement to that product (for test installs) | dev and approved releases |
| Published (`published`) | Device key + an entitlement to the app, or a Gujo Pass subscription that opens it | Approved stable, beta and alpha (not dev) |
| Retired (`retired`) | Same as published + an install record the server received before the retirement time | The last stable approved before retirement, only |

- Gujo Pass opens app products only. Assets and lectures are not opened by Pass.
- stable, beta and alpha releases appear in feeds only after a person approves them. A release waiting for approval appears in no one's feed.
- For a retired app, the install record counts by the time the server first received it. The install time the device reports is ignored.

## Everything else is 404

An unknown product, a product that is not an app, an unknown channel and a request the rules above reject all get 404. Answering 401 or 403 would reveal that the product or release exists. If a feed returns 404, check the key, its `device.install` ability, then the entitlement or Pass.

## Channels

Channels are `stable`, `beta`, `alpha` and `dev`.

- The URL without a query is the combined feed. It carries releases of every published channel, newest first, and adds a `<sparkle:channel>` tag only to items that are not stable. The app picks the channels to follow from its installed settings (`allowedChannels`).
- The `?channel=<c>` URL is kept for older builds that put the channel in the feed URL. It carries only that channel's items, without tags.
- The item limit applies per channel.

## Downloads

```text
https://app.gujo.ai/distribution/v1/releases/{id}/download
```

- Call it with the same device key as the feed. The rules are the same as the feed's.
- When allowed, it answers 302 to a short-lived signed URL, with `Cache-Control: no-store, private` and no `Set-Cookie`.
- A missing, withdrawn or still-pending release, or one the product's current state does not carry, gets 404.
- If the server has no distribution storage configured, it answers 503 `distribution_not_configured`.

Don't send the device key again to the signed URL.

## Registering releases

Only Gujo's publishing tools register releases. Keys from this site cannot.
