Guides
Organizations & projects
Every request runs inside one organization and one project. Learn what they are, how a request chooses them, who can reach which project, and how to structure workspaces for your product.
The ownership model
Mahvash organizes everything you create into a two-level hierarchy:
- An organization is the tenant boundary. It has members with roles, one wallet that pays for all its work, usage limits and budgets. Organizations are completely invisible to each other.
- A project is a workspace inside an organization. Responses, conversations, files, assets, vector stores, prompts, data sources, webhooks and API keys all belong to exactly one project.
A typical company layout looks like this:
Organization "Acme" tenant, members, wallet, limits
├─ Members Sara (ADMIN), Ali (MEMBER), Nadia (VIEWER)
├─ Teams Support, Marketing
├─ Project "default" open to every member requests with no selector land here
├─ Project "support" open to every member support copilot: keys, files, prompts
└─ Project "campaigns" restricted: Marketing team only granted teams can reach itEvery account is created with a personal organization and a default project, so a single developer can ignore this page entirely: requests without any selector land in that default project. The structure becomes important when you add teammates, serve several customers, or want to separate environments.
Note:Ownership is fixed. A project never moves to another organization, and a resource never moves to another project. To reorganize, create the new resource in its new home.
What a project gives you
Projects are the main tool for isolation inside an organization. Everything in one project shares:
- Data — a file, store or response from one project is not visible from another. A foreign ID answers
404, exactly like an ID that never existed. - Credentials — an API key reaches exactly one project.
- Configuration — default instructions, prompts, retrieval data sources, managed tool settings and default retrieval placement.
- Limits and budgets — request rate, concurrent runs and a monthly spend ceiling can be set per project.
Common ways to split work into projects:
- one project per product feature (a support copilot and an internal search tool have different data and prompts);
- one project per environment (
stagingandproductionnever share keys or documents); - one project per customer when you build a multi-tenant SaaS — see Multi-customer SaaS integration.
How a request chooses its organization and project
Mahvash resolves the organization first and the project second, before it looks anything up or starts any work. Two optional headers steer the choice:
| Selector | With a user JWT | With a project API key |
|---|---|---|
X-Mahvash-Organization | Chooses one of your active memberships, by UUID or slug | Already pinned; a different value is refused |
X-Mahvash-Project | Chooses a project you can reach inside that organization, by UUID or slug | Already pinned; a different value is refused |
| No selector | Your personal organization and its default project | The key's pinned organization and project |
Resolution happens in a fixed order:
| Order | Organization comes from | Project comes from |
|---|---|---|
| 1 | The organization pinned to the API key | The project pinned to the API key |
| 2 | X-Mahvash-Organization, among your active memberships | X-Mahvash-Project, among projects you can reach |
| 3 | Your personal organization | A project field in the body, where the endpoint accepts one (must agree with the header) |
| 4 | — | The organization's default project |
Some important consequences:
X-Mahvash-Organizationonly chooses among organizations you are an active member of. It can never reach an organization you do not belong to.X-Mahvash-Projectnarrows a request; it can never widen what the caller may reach.- An unknown organization or project selector answers
404rather than silently falling back. A client that thinks it is working for one company must never quietly work in another. - A project named in the request body (where an endpoint accepts
project) must agree with the header. - Continuing a conversation with
previous_response_idorchat_idstays in the project that owns that conversation.
# A person working in the "support" project of the "acme" organization
curl https://api.mahvashai.ir/v1/responses \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "X-Mahvash-Project: support" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "input": "Summarize today\u0027s open tickets."}'
# A project API key already knows its organization and project
curl https://api.mahvashai.ir/v1/responses \
-H "Authorization: Bearer $MAHVASH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "auto", "input": "Summarize today\u0027s open tickets."}'Roles and project access
Every member has one organization role. Roles are ordered: VIEWER < MEMBER < ADMIN < OWNER.
| Role | Typical capabilities |
|---|---|
VIEWER | Read projects, conversations, files, stores and prompts it can reach |
MEMBER | Everything a viewer can, plus create responses, files, vector stores, batches and prompts |
ADMIN | Everything a member can, plus manage projects, API keys, grants, webhooks, members, teams, audit and usage reports |
OWNER | Full control of the organization; the last owner cannot be removed or demoted |
Nobody can grant a role above their own, and an organization always keeps at least one owner.
Organization-wide and restricted projects
By default a project is open to every active member of the organization, at their organization role. Where your organization uses restricted projects, a restricted project is reachable only by owners, administrators, and the teams or members named in an access grant:
# Create a team, then give it access to one restricted project
curl -X POST https://api.mahvashai.ir/api/v1/tenancy/teams \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "Content-Type: application/json" \
-d '{"name": "Marketing"}'
curl -X POST https://api.mahvashai.ir/api/v1/tenancy/projects/<project-uuid>/grants \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "Content-Type: application/json" \
-d '{"team": "<team-uuid>", "role": "MEMBER"}'Two rules are easy to get wrong:
- A grant widens where you can go, never what you can do there. The effective role is the lower of the organization role and the grant role, so a
VIEWERholding aMEMBERgrant can still only read. - A project you cannot reach looks like it does not exist. It is reported as
404, not403, so lists and IDs never reveal what other teams have.
Limits and budgets
An organization and each of its projects (and each API key) can carry a request rate (requests_per_minute), a cap on concurrently running work (max_active_runs), and — for organizations and projects — a monthly budget with an alert threshold:
curl -X PATCH https://api.mahvashai.ir/api/v1/tenancy/organizations/settings \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "Content-Type: application/json" \
-d '{"requests_per_minute": 120, "max_active_runs": 4, "monthly_budget_usd": "500.00", "budget_alert_percent": 80}'- The tightest limit wins. A project cannot raise its organization's limit and a key cannot raise its project's. Setting a small limit on a key you hand to a partner is the simplest way to contain it.
nullmeans "inherit", not "unlimited".- Over the request rate you receive
429withRetry-After; over the concurrency cap the new run is refused at admission. monthly_budget_usdis a hard ceiling measured per calendar month in UTC: work that would cross it is refused.budget_alert_percentonly reports, it never blocks.
Managing a workspace through the API
Workspace management uses the /api/v1/tenancy endpoints with a user JWT — never a project key. Send X-Mahvash-Organization to choose which organization you are managing. A typical provisioning script:
- 1List the organizations you belong to with
GET /api/v1/tenancy/organizations, and confirm what a request resolves to withGET /api/v1/tenancy/organization. - 2Create a project with
POST /api/v1/tenancy/projects. - 3Optionally configure it: prompts, retrieval data sources, managed tool settings.
- 4Issue a project API key and store it in your secret manager.
- 5Hand the key to the service that will make AI calls.
curl https://api.mahvashai.ir/api/v1/tenancy/organizations \
-H "Authorization: Bearer $MAHVASH_USER_JWT"
# Which organization, project and role did this request resolve to?
curl https://api.mahvashai.ir/api/v1/tenancy/organization \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme"curl -X POST https://api.mahvashai.ir/api/v1/tenancy/projects \
-H "Authorization: Bearer $MAHVASH_USER_JWT" \
-H "X-Mahvash-Organization: acme" \
-H "Content-Type: application/json" \
-d '{"name": "Support assistant", "slug": "support", "description": "Ticket drafting"}'Creating projects, issuing keys, managing grants and webhooks all require the ADMIN role. The default project cannot be archived or deleted, because it is where requests without a selector land. Deleting any other project archives it and keeps its history for accounting.
Related references: Organizations, Projects, API credentials, Members, Teams and Access grants.