# The manifest

> manifest.json — the OpenAPI document of your actions and the block that tells Apigami how to run them, the sites the application reaches, its slug and its address.

Every application starts from one file, `manifest.json`. It names the actions, the sites the
application reaches and the name it goes by.

## The project and its manifest

A project is a directory. One file is required, `manifest.json` at its root: an OpenAPI 3.1 document
that describes your actions, plus an `x-apigami` block that tells Apigami how to run them.

```json title="manifest.json"
{
  "openapi": "3.1.0",
  "info": {
    "title": "Grocery",
    "description": "Search a grocery site and fill a basket, using the shopper's own session.",
    "version": "1.0.0"
  },
  "x-apigami": {
    "manifest_version": 2,
    "slug": "acme-basket",
    "entrypoint": "main.ts",
    "domains": [{ "domain": "example.com", "via": "extension" }],
    "env": [
      { "name": "CURRENCY", "description": "ISO 4217 code shown in results", "secret": false }
    ]
  },
  "paths": {
    "/search": {
      "post": {
        "operationId": "searchProducts",
        "summary": "Search products",
        "description": "Finds products by name and returns their price and reference.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["query"],
                "properties": {
                  "query": { "type": "string", "description": "What to look for" },
                  "limit": { "type": "integer", "minimum": 1, "maximum": 50, "default": 10 }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The products found",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["products"],
                  "properties": {
                    "products": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": ["reference", "name", "price"],
                        "properties": {
                          "reference": { "type": "string" },
                          "name": { "type": "string" },
                          "price": { "type": "number", "description": "Unit price, in the currency of CURRENCY" }
                        }
                      }
                    }
                  }
                },
                "example": { "products": [{ "reference": "7412", "name": "Comté 18 mois", "price": 4.35 }] }
              }
            }
          }
        }
      }
    },
    "/basket": {
      "post": {
        "operationId": "readBasket",
        "summary": "Read the basket",
        "description": "Returns what is in the shopper's basket right now.",
        "responses": {
          "200": {
            "description": "The basket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["lines", "total"],
                  "properties": {
                    "lines": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "reference": { "type": "string" },
                          "quantity": { "type": "integer" }
                        }
                      }
                    },
                    "total": { "type": "number" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
```

Two version numbers live here and they are not the same thing. `x-apigami.manifest_version` is the
version of **this format**, and it is `2`; `info.version` is the version of **your application**, a
label you choose and that nothing depends on. What identifies a version to Apigami is its
`version_id`.

`summary`, `description` and `responses` are optional in OpenAPI and required here. They are what
another developer — and another developer's assistant — reads to decide whether to reuse your work,
and a brick nobody can read is a brick nobody reuses. Say in `description` what the action is worth
to whoever calls it, not how it is implemented.

**What an action takes and answers has to have a shape.** A success response is any `2xx` code, `201`
included, or `2XX`; the lowest numbered 2xx code it declares, else its `2XX`, needs a `content` naming its media type, and where that media type is `application/json`, a `schema` of what comes back; the
same holds for the `requestBody` of an action that takes one. A description alone is refused:
`catalog_read` hands your manifest to another group's assistant as the whole contract, and a sentence
where a shape was promised means one call made blind to find out what the fields are called. Not
everything is JSON — an action that answers a PDF, a CSV or raw bytes declares
`application/pdf`, `text/csv` or `application/octet-stream` with no schema at all, and that is
enough. An `example` is never required and is worth writing wherever the shape alone does not say
what a good answer looks like.

`x-apigami.public_paths` is no longer needed: the `--staging` address serves every path to anyone, and a
manifest that still lists it is accepted and ignored.

Dependencies are **not** in the manifest. Being allowed to call another application is an
authorisation stored by Apigami, granted and revoked without deploying anything — see
[Dependencies: reuse a brick](/docs/app-dependencies/#dependencies-reuse-a-brick).

### Domains: via extension or direct

`x-apigami.domains` lists the sites your application talks to, each with how it reaches them:

- `"via": "extension"`: through the person's browser, with their session, by `request.fetch` or a
  `tabs.*` operation. The person approves it when they connect, or in a consent window at the first
  call. An operation your code sends on a domain not declared this way is refused before any window
  opens: `403 … is not a domain this application declares as reached through the browser`. A side
  panel shows only on such a domain.
- `"via": "direct"`: your server calls the site itself, with `fetch` and its own credentials, an API
  key in `x-apigami.env` for instance, never the person's session. No browser, no consent window.

Only `extension` is enforced when your code runs: Apigami does not filter what your server fetches,
so a `direct` declaration is not required for the code to work. Declare every site your server calls
anyway: the connect page lists it, « Without your browser », so the person sees where their data goes
before connecting, `catalog_read` shows it to whoever considers reusing your application, and an
application that uses yours shows it in turn. When every domain, yours and those of the applications
you use, is `direct`, a person connects without attaching a browser.

**Declare the site's own domain, `example.com`, not `www.example.com`.** A domain is a bare name,
lowercased: a scheme, a path, a port, a wildcard such as `*.example.com` or a trailing dot is refused
with `Use a bare domain, such as example.com`. The person's approval of `example.com` covers it and
every subdomain, `www.example.com` and `api.example.com` included; an approval of `www.example.com`
covers `www.example.com` and its own subdomains, but neither `example.com` nor `api.example.com`. Pass the
declared string as `params.domain` in every call, and the host you want in `url`:

```json
{ "op": "request.fetch", "params": { "domain": "example.com", "url": "https://www.example.com/news" } }
```

`params.domain` must equal a declared domain exactly: with `example.com` declared, `"domain":
"www.example.com"` answers `403 www.example.com is not a domain this application declares as reached
through the browser`. The `url` must be `https://` on that domain or one of its subdomains: outside it
the call answers `502 url is outside the granted domain example.com: …`, and a redirect that leaves it
`502 redirected outside the granted domain example.com: …`.

### Permissions: emails

`x-apigami.permissions` lists what your application may do beyond its sites: `"emails"` and `"ai"`.
With `"emails"`, your code may email the people connected to it with the `emails.send` operation, by their
`user_ref`, without ever learning their address.

```json
"x-apigami": { "permissions": ["emails"] }
```

It belongs to the version that declares it: production's for everyone, the latest version's when a
member of your group previews an email or sends one to themself. Adding or removing it takes a deployment, and an application without hosted
code has no manifest, so it sends no email. An application that uses yours as a brick inherits it, and
the email always leaves in the name of the application the person connected to. Once it is declared,
the connect page asks each person who connects to tick the box of your emails, and offers a second,
optional box for your news. How to write them: [Emails to your users](/docs/emails/).

### Permissions: ai

`"ai"`: your code may ask a model to read text, PDFs and images with the `ai.generate` operation, which
Apigami runs in the EU, without storing what it reads or training on it.

```json
"x-apigami": { "permissions": ["ai"] }
```

The version whose code calls must declare it: the one a run executes, production's for your `apg_` key and
`APIGAMI_KEY`. The connect page lists it among what your application may do, without a box to tick, and so
does it for an application that uses yours as a brick. How to call it: [AI in your application](/docs/ai/).

### Database

`"database": "sqlite"`, or `true`, gives your application a SQLite database, which your code reads as `env.DB`;
`"database": "postgres"` gives it a PostgreSQL database, reached through `env.DATABASE_URL`, for a real need only: a
schema or a library already written for PostgreSQL, long queries, many concurrent writes. Either is created at the
first try or deployment of a version that declares it, and the application keeps that kind. Without one, the
application has none: a site that only serves pages needs nothing more. Which to take, and how to use it:
[Database and schedules](/docs/app-database/#its-database). Deleting the application deletes a PostgreSQL database
at once; a SQLite database is kept 30 days, during which contact@apigami.com can give it back, then deleted.

### Retired fields

`"manifest_version": 1` is refused (`manifest_v1_retired`): Apigami no longer runs it, and an older version
written for it can be neither tried nor deployed. `x-apigami.predeploy` is refused too (`predeploy_removed`):
[Migrations](/docs/app-database/#migrations-migrations) replace it.

### The slug: its rules, and what a rename does (no redirect)

`x-apigami.slug` is the public name of your code and its address, `https://{slug}.apigami.tech`.
It is unique on all of Apigami, first come, first served: choose a distinctive one, your product's
name with what the application does, such as `acme-basket`, never a common word another team will
want too. It is 3 to 32 lowercase letters, digits and hyphens, with no `--` and no hyphen at either
end. A few whole names are refused, platform words and large brands, and so is a slug with `apigami`,
`staging`, `login`, `signin`, `password`, `official`, `com`, `net` or `org` between its hyphens.
A version whose slug is reserved or taken is invalid as soon as you write it, with `reserved_slug` or
`slug_taken`.

The slug is reserved at your first deploy, never by creating the application, and it is never freed
afterwards: a slug you renamed away from stays your group's, its host answering `404` until a version
declares it again, and a deleted application's slug stays with your group, which alone can take it back. Code and dependencies name an application by its
`app_id`, never by its slug.

A new slug becomes the address when the version carrying it is published; until then, a deployment of
it answers at `--staging` under both names. The publication's answer then carries `rename`, with what
does not follow the application to its new host: browser storage, cookies and sessions, an installed
app, the redirect URIs registered with other services, and the `{slug}.*` prefix of your event types.
The old host and its `--staging` answer `404` from then on, without a redirect: the name stays your
group's, and only a version declaring it again brings it back. When the hosting provider fails
mid-move, `rename.note` says the move is not finished: the old host and its `--staging` may still serve
the previous version or already answer `404`, and publishing the same version again finishes it.