# Share a package and manage access

Access in Kody belongs to an **organization**. Resources (packages, apps, jobs,
secrets, integrations, memory, email) live in one org, and a **grant** gives one
person or team a preset on one resource. To let someone use a package without
sharing a login, grant them **Use** on it.

> [!TIP] Grants and forks work together. Grant **Read** when someone should see
> your private source and fork their own copy; grant **Use** when they should
> run **your** live package. See
> [Share source with one person](#share-source-with-one-person).

## Share source with one person

To let one person (inside or outside your organization) read a private package
and fork it into their own Kody, without running yours or opening your app:

1. Open the package's **Collaborators** tab and give them **Read**: pick the
   member, or in a personal org invite their email or username with **Read**.
   Agents call `packageCollaboratorSet` with `"preset": "read"` (a username
   outside the org becomes an outside collaborator), `inviteCreate` with
   `kind: "grant"` and `"preset": "read"`, or `inviteLinkCreate` with
   `"preset": "read"`. The CLI takes `--preset read`.
2. They open the package page and click **Fork**, or their agent calls
   `packageFork` with `{ "package": "@owner/package-name" }`.
3. The fork is a new package in their own org. It records where it came from, so
   they see when your source moves ahead, and it runs and bills in their org.

Read is `package:read` only. They can view and `git clone` the source, but
cannot run your package (no `package:execute`) or open your app (no
`app:execute`). Forking never gives them more access to your package. Everyone
with a preset that includes read (Read, Use, Contribute, Manage) and you as the
owner can fork the same way. Someone without access still gets a 404 and cannot
fork. Making a package public is a different choice: anyone on the internet can
read and fork the default branch.

## Presets

| Preset         | On a package                                                        |
| -------------- | ------------------------------------------------------------------- |
| **Read**       | Read, clone, and fork the source (`package:read`)                   |
| **Use**        | Read the source and run it (`package:read`, `package:execute`)      |
| **Contribute** | Use, plus write (`package:write`)                                   |
| **Manage**     | Contribute, plus publish, delete, and manage access for the package |

Advanced grants take an explicit permission list instead of a preset. No preset
includes `billing:*` or revealing a secret. On the web, **Advanced: choose
permissions** under the preset picker sends the same list. Read applies to
packages only; other resources start at Use.

## Grant access

Call `accessGrant` from a request bound to the org that owns the package. You
need manage access on the package.

```json
{
	"resource_type": "package",
	"resource_id": "<package id>",
	"subject_type": "user",
	"username": "jesse",
	"preset": "use"
}
```

### Everyone in this organization

To give every member of a team org the same access to one package, grant it to
the org itself with `subject_type: "org"` instead of granting each person:

```json
{
	"resource_type": "package",
	"resource_id": "<package id>",
	"subject_type": "org",
	"preset": "use"
}
```

The grant is checked live against org membership every time access is checked,
so it covers current members and anyone who joins later, and someone who leaves
the org loses it at once. Kody writes one grant, never one row per member. It
covers only the package it is granted on; everything else stays private, and
outside collaborators and other orgs get nothing. `subject_id` may be omitted or
must be this org's id: an everyone grant cannot name another org, cannot target
`resource_type: "org"` or an execution context, and is refused in a personal org
(which has no other members).

An `everyone` team with Use on the package, plus an
[org invite link](/docs/organizations) that adds new people to that team, keeps
working the same way.

`accessList` shows who holds what across the org. `accessRevoke` removes one
grant by `grant_id`. To change someone's preset, call `accessGrant` again for
the same subject; it updates the grant.

## Package collaborators

For one package, three calls cover teams, members, and everyone in the owning
org. Each needs Manage on the package, and `package_id` accepts the
saved-package UUID or the package name leaf (`@owner/leaf` or `leaf`).

- `packageCollaboratorList` (`{ "package_id": "…" }`) lists everyone who holds a
  grant on the package: everyone in the org (`kind: "org"`), teams, members, and
  outside collaborators, each with `grant_id`, `kind`, username or team slug,
  and preset.
- `packageCollaboratorSet` adds a team, an org member, or everyone in the org,
  or changes the preset of one already listed. Pass exactly one of `team_slug`,
  `team_id`, `username`, `user_id`, or `everyone: true`, plus a preset or an
  advanced `permissions` list. A person outside the org becomes an outside
  collaborator. Owners already have full access:

  ```json
  {
  	"package_id": "release-notes",
  	"team_slug": "writers",
  	"preset": "contribute"
  }
  ```

- `packageCollaboratorRemove` (`{ "package_id": "…", "grant_id": "…" }`) removes
  one collaborator, including an outside collaborator.

These run through `accessGrant` and `accessRevoke`, so they are checked and
audited the same way.

## On the web

People with Manage on a package see a **Collaborators** tab on the package page
(`/@owner/<package>/collaborators`). It lists who has access and their preset,
adds everyone in this organization, a team, or an org member, changes a preset,
and removes access, using the package collaborator calls. **Advanced: choose
permissions** under each preset picker sends an explicit permission list
instead. Outside collaborators are listed and can be removed; their access is
changed through `accessGrant` or `packageCollaboratorSet`. Org owners always
have full access and are not listed.

In a personal org there are no members or teams to add, so the tab invites
people instead:

- **Invite a collaborator** takes an email or username and a preset: **Read** to
  read and fork the source without running it, **Use** to run it and read the
  source, **Contribute** to also update it, or **Manage** to also publish,
  delete, and choose who else has access. It calls `inviteCreate` with
  `kind: "grant"`.
- The invite message is shown once, with a copy button. Send it to the person so
  their agent can call `inviteAccept`. Open **Details** to see the message.
- **Pending invites** lists invites that have not been accepted. **Revoke**
  calls `inviteRevoke`.
- Once the person accepts, they appear under **Who has access** as an outside
  collaborator. **Remove** calls `packageCollaboratorRemove`.

The tab also links to creating a team org for group sharing through members and
teams. Team orgs use member and team controls on this tab.

## Access for one person

In a team org, an Owner opens **Members**, **Collaborators**, or a person-linked
row on **Grants**, then **Access**. That page is
`/@<slug>/-/grants/user/<userId>` (`Access for {name}`). It lists what that
person can use in this organization and **Give access** grants or revokes a
preset on specific packages, apps, and credentials in one submit. Team and
everyone grants appear as read-only. Owners already hold everything, so there is
nothing to grant them. A person with no membership or grant 404s.

The package **Collaborators** tab stays the place to share one package. Use
**Access** when the work is one person across packages, apps, and credentials.
Agents still call `accessGrant`, `accessRevoke`, and `packageCollaboratorSet`.

## Open API

The same calls are on the [Open API](/docs/open-api) (`/openapi.json`):

| Call                                                        | Capability                  |
| ----------------------------------------------------------- | --------------------------- |
| `GET /v1/packages/{package_id}/collaborators`               | `packageCollaboratorList`   |
| `POST /v1/packages/{package_id}/collaborators`              | `packageCollaboratorSet`    |
| `DELETE /v1/packages/{package_id}/collaborators/{grant_id}` | `packageCollaboratorRemove` |
| `POST /v1/access/grants`                                    | `accessGrant`               |
| `POST /v1/access/grants/{grant_id}/revoke`                  | `accessRevoke`              |
| `POST /v1/access/invites`                                   | `inviteCreate`              |
| `POST /v1/access/invites/{invite_id}/revoke`                | `inviteRevoke`              |

## Invite someone who has no access yet

`inviteCreate` with `kind: "grant"` sends the same grant to an email address or
username. The token is shown once, with a prompt for the invitee's agent:

```json
{
	"kind": "grant",
	"email": "jesse@example.com",
	"resource_type": "package",
	"resource_id": "<package id>",
	"preset": "use"
}
```

The invitee calls `inviteAccept` with the token, or chooses **Accept** on the
invite card on their Waiting page (an email invite needs a verified email
there). Nothing attaches before they accept. Invites expire after 7 days;
`inviteRevoke` cancels a pending one.

Use `kind: "membership"` to add someone to the whole **team** org instead (with
a role and optional teams). Members hold the org-level basics plus what they are
granted. Owners hold everything. Membership invites are refused on a personal
(signup) org; create a team org to invite people.

Personal orgs can still grant Use/Contribute/Manage on packages via
`accessGrant` / `inviteCreate` `kind: "grant"` and revoke with `accessRevoke`.
The web Grants page and per-person **Access** page are aimed at team orgs. In a
personal org, invite and remove collaborators from the package's
[Collaborators tab](#on-the-web), or use those capabilities.

## Unmappable credentials

A credential is **unmappable** when nobody who runs or visits the package can
swap in their own, so it always acts as the owning org. A package has one while
its published version declares a need with `supplier: "publisher"` (while the
`package-credential-suppliers` flag is on) or `ownerCredentialForVisitors: true`
(see [Credentials](/docs/credentials#choose-who-supplies-a-package-credential)).
Anyone who can change or publish such a package could make its code use those
credentials however they like, so Kody surfaces and guards it:

- **Share-time warning.** Granting a preset that can change or publish the
  package (Contribute or Manage, or a custom list with `package:write` or
  `package:publish`) still succeeds, and returns a warning that names each
  credential (by name and need key, never values or ids) and says the grantee
  could make the package's code use them. It shows in the Collaborators tab on
  the web, after `kody package grant`, and in the `warnings` field of
  `packageCollaboratorSet`, `accessGrant`, `inviteCreate`, and
  `inviteLinkCreate`. The Collaborators tab also shows it on each existing grant
  it affects.
- **Owner-only changes.** While the package has unmappable credentials, only org
  Owners (the owner of a personal org) can push, publish, or otherwise change
  its code or manifest. Existing collaborators are locked too, with no
  grandfathering, and the lock applies as soon as a credential becomes
  unmappable. Read and Use are unaffected: collaborators keep running it.
  Denials have code `unmappable_credentials`, name the credentials, and say how
  an Owner can allow the collaborator.
- **Allow one collaborator anyway.** An org Owner can let one person or team
  change it despite the risk, per grant: the **Allow changes despite unmappable
  credentials** checkbox on their row in the Collaborators tab,
  `kody package grant <package> --user <name> --preset contribute --allow-changes-despite-unmappable-credentials`
  (`--disallow-changes-despite-unmappable-credentials` turns it off), or
  `allow_changes_despite_unmappable_credentials: true` on
  `packageCollaboratorSet` or `accessGrant`. Omitting it keeps the grant's
  current choice. It is not available on everyone-in-org or profile grants.
- **Owner notice.** Owners see a notice explaining the lock on the package page,
  the Collaborators tab (`unmappable_credentials` on `packageCollaboratorList`),
  `kody dev check` / `packageCheck` (`notices`), and publish output
  (`unmappable_credentials_notice`). No email is sent.
- **Profiles.** The check uses the acting principal through the profile chain. A
  profile acting for an Owner changes the package only when every layer of its
  chain holds the permission; a profile acting for a collaborator follows that
  collaborator's grant, including its allow option.

## What a collaborator can do

Someone who holds a grant but is not a member is an **outside collaborator**.
They must connect with **your** org selected at consent. Then they can:

- Read and run the packages they hold Use on
- Run ad hoc code that imports those packages
- Search the shared catalog

They cannot see anything else in your org, reveal secret values, or change a
package unless the grant says so.

### Who pays when a Use collaborator runs your package

The org is the billing boundary, and a connection is billed to the org it is
bound to. Jesse connects with the **owning** org selected, so everything his
runs use is charged there:

| Meter                                                                | Charged to                     |
| -------------------------------------------------------------------- | ------------------------------ |
| Usage events, credits, org and per-user budgets, outbound fetch      | **Owning org** (the bound org) |
| Daily and weekly **execute** limits (unique execute workers per day) | **Owning org's** execute pool  |

Execute limits are one org-wide pool that grows with the org's paid seats. A
grant-only collaborator draws from the owning org's pool but is not a seat, so
granting someone Use does not change your seat count or grow your pool. Nothing
from these runs is charged to the collaborator's personal plan.

Example: Jesse has Use on `@acme/bot`. Connected as `@acme`, a run debits acme's
credits and budgets and counts against acme's execute pool. Jesse's own
personal-org limits are untouched by those runs.

Package **apps** are the exception. When Jesse opens an internal app of `@acme`
in the browser (he holds a grant on it but is not a member), his own personal
org pays for the metered work his visits cause, and acme keeps paying the app's
Worker compute. Internet-visible apps stay paid by acme for every visitor. See
[Package apps: who pays for visits](/docs/package-apps#who-pays-for-visits). See
[Organizations](/docs/organizations#execute-limits-one-org-wide-pool) for how
the pool is sized.

### Secrets and integrations on a granted run

Every run acts as a person, and a credential resolves only when that person
holds Use on it. When Jesse runs **your** package in your org:

- Secret **values** and integration tokens come from **your** org
- Jesse never gets `secret:read` from a Use grant
- Each secret or integration the package reads needs Jesse's own `secret:use` or
  `integration:use` on it. A Use grant on the package does not include them;
  grant them on the credential: `accessGrant` with `resource_type: "credential"`
  and the `credential_id` from `secretList` (or `integrationList`,
  `mcpServerList`, `secretProviderList`). The grant follows the credential if it
  is renamed, and ends if it is deleted
- The package rules then narrow that use: `package:execute` on the package, and
  each secret or integration must allow that package (host allowlist,
  `allowed_packages`, or self-authored rules for user secrets)
- The same applies to `{{secret:…}}` in Jesse's own ad hoc code

See [Secrets](/docs/secrets) for placeholders and allowlists.

### Triggers in a shared package

A package's jobs, subscriptions, and webhooks run only for people who turn them
on for themselves with `enablementEnable`, and each run acts as that person with
their live access. If Jesse turns on the nightly job in `@acme/bot`, it runs as
Jesse, with Jesse's grants, and pauses if he loses access. Usage still counts
against `@acme`. See
[Organizations](/docs/organizations#automation-runs-as-the-person-who-turned-it-on).

## Share with a link

The package's [Collaborators tab](#on-the-web) also has **Share with a link**,
in personal and team orgs alike, for when you do not know the person's email or
username. Pick a preset, how many people may use the link (1 by default; blank
for anyone who has it), and how many days it lasts (7 by default, up to 365).
The link is copied once; Kody keeps only its hash.

Whoever opens the link sees what it gives: the package, the preset, and who
shared it. They can create an account first if they do not have one. Opening the
link changes nothing; they choose **Accept** or **Decline**. A signed-in person
who opened it also finds it on their Waiting page and in the Invites panel on
their Organizations page (`/account/organizations`) until they accept, decline,
or the link stops working.

Accepting grants the preset on that one package. Someone who is not a member of
the owning org becomes an **outside collaborator**; the link never adds anyone
to the org. Accepting never lowers access: a person who already has Manage keeps
it when they accept a Use link, and an Owner already has everything.

Each person can use a package link once. If you later remove them, the same link
does not let them back in; send a new one. The tab lists active links with their
uses and expiry, and **Revoke** stops one immediately. Links stop working when
they expire, run out of uses, the package is deleted, or the person who made the
link loses access. Creating, revoking, and accepting a link are in the org audit
log (`invite_link.created`, `invite_link.revoked`, `invite_link.joined`).

## Cross-org imports

A package imports only packages from its own org. A package in org A cannot
import a package from org B, even with a grant. To reuse another org's public
package,
[fork](/docs/package-lifecycle#fork-a-close-public-package-before-creating) it
into your own org and import the copy.

## Which call to use

- Who has access to one package: `packageCollaboratorList`
- Add a team or org member to one package, or change their preset:
  `packageCollaboratorSet`; remove one with `packageCollaboratorRemove`
- One person or team, already known: `accessGrant`
- Someone without access or an account yet: `inviteCreate` with `kind: "grant"`,
  or a link from **Share with a link** on the Collaborators tab when you do not
  know their email or username
- The whole org: `inviteCreate` with `kind: "membership"` or `orgMemberUpdate`
- Take access away: `accessRevoke` (a grant) or `orgMemberRemove` (a member)

Search these names first and open capability detail for the exact call shape.
