Concepts
Products
Myna is a platform of products, and a project declares which ones it runs.
Myna is infrastructure for the systems around your application, and it ships more than one product. A project declares which ones it runs.
| Product | What it holds |
|---|---|
content |
Schemas, entries, assets, change sets, previews, releases |
feedback |
Boards, reports, evidence, conversations, resolutions |
analytics |
Page views, custom events, sessions, funnels, and the joins to content and reports |
A project chooses what it runs when it is created, and can change its mind afterwards. The only rule is that it must run at least one — a project running nothing has no screens and no routes, so the last product cannot be turned off. Archive or delete the project instead.
Omit the choice and a project gets content, which is what every API client written before Myna had three products expects.
Reading what a project runs
GET /v1/projects/:project reports products, so a client can check before it calls rather than after:
{
"id": "prj_01k2...",
"slug": "acme-site",
"products": ["content", "analytics"]
}
Calling a product that is off
A route belonging to a product a project does not run answers PRODUCT_NOT_ENABLED, which is a 404:
{
"type": "https://myna.sh/errors/product-not-enabled",
"title": "Product not enabled",
"status": 404,
"code": "PRODUCT_NOT_ENABLED",
"detail": "This project does not have the feedback product enabled.",
"product": "feedback",
"enabledProducts": ["content"]
}
A 404 rather than a 403 is deliberate. For that project the resource genuinely is not there, and no scope would have helped — answering "forbidden" would send you looking for a permission that does not exist. The response names the product and lists what is enabled, so the fix is visible without a second call.
Choosing at creation
POST /v1/organizations/{organization}/projects takes the list:
POST /v1/organizations/{organization}/projects
{ "name": "Marketing site", "products": ["content", "analytics"] }
myna projects create --name "Marketing site" --products content,analytics
Each one is enabled through the ordinary path, so project_products records who asked for it. Omitting products means content; sending an empty array is refused rather than read as "none".
Turning a product on
Enabling a product needs project:admin and is done from project settings in the dashboard, or through the management API:
PUT /v1/projects/{project}/products
{ "product": "feedback", "enabled": true }
It is a separate route rather than a field on the project-update call because it is not a setting: it decides which routes exist, which tools an agent can see, and which allowances are counted. Myna records who enabled it and when.
Disabling a product hides it. It never deletes what the product holds, so turning something off by mistake is not a way to lose data. The last enabled product is refused, for the reason above.
Scopes and products
Scopes are grouped by the product they reach, and one rule matters more than the rest:
project:admin is frozen. It grants Content and project capabilities, and it never widens to cover a product added later — so an existing key cannot silently gain access to something it was never scoped for.
For a key that should keep up with new products, ask for admin:all by name. It is the only scope that grows, which is why it has to be chosen deliberately.
Roles work the other way round: an organization owner or admin can reach every product their organization runs, because a role is a live statement about a person rather than a delegation frozen in a credential. See API key scopes.
Two capabilities are held by no key and no role at all: feedback:submit and analytics:track. They belong to a publishable ingest key, which ships in a browser bundle. Both can only append, because a credential that can also read is not a containment boundary. Which of them a given key can reach is decided by the products the project has enabled.
