Diffinery

codician/diffinerypublic

Open-source, agent-first GitHub and GitHub Actions alternative that runs on your own Cloudflare account: git hosting, pull requests, code review and drop-in GitHub Actions YAML on Workers, Durable Objects, R2 and Containers.

243 lines · 17.2 KBRaw History

title: Organizations description: Create organizations, add people by username or email, manage roles and invitations, set the organization's picture, and share repositories and secrets.

An organization is a shared account that owns repositories, agents, container images and Actions secrets on behalf of a group of people. Use one when more than one person works on the same code, so access doesn't depend on any one personal account.

Organization roles

Every member has one of three roles. The role decides what they can do in the organization and the role they get on every repository the organization owns.

Capabilitymemberadminowner
Role on every organization repositorywriteadminadmin
See the organization's private repositoriesYesYesYes
Create repositories in the organizationYesYesYes
Create container images in the organization and pull its imagesYesYesYes
Push to, delete and change the settings of an organization image that isn't linked to a repositoryOnly images they createdYesYes
See the People and Agents tabsYesYesYes
Add people (by username or email)NoYes (as member or admin)Yes (any role)
See and revoke pending invitationsNoYesYes
Change roles, remove membersNoYes, except ownersYes, including owners
Create and manage the organization's agentsNoYesYes
Manage organization Actions secretsNoYesYes
Change the organization's picture through the APINoYesYes
Open the Settings tab: rename, display name, pictureNoNoYes
Get emails when repositories are created or deleted in the organizationNoYesYes

Site administrators can do everything an owner can in every organization, and have admin on every repository.

A container image linked to a repository follows that repository's roles instead (read pulls, write pushes, admin deletes). See Container registry.

A member's role on a repository is the highest of their organization role's repository role and any role given to them directly on that repository. A direct collaborator role can raise an organization member's access (for example to maintain or admin) but never lowers it. See Permissions for what each repository role allows.

Create an organization

Any active account can create an organization and becomes its first owner.

In the web UI

  1. Go to Settings → Organizations (/settings/orgs), or click New organization on your dashboard.
  2. Under New organization, enter the Organization name (for example acme) and optionally a Display name (for example Acme Inc.).
  3. Click Create organization.

Diffinery opens the new organization's People tab, where you can add people right away.

With the API

curl -X POST https://diffinery.com/api/v3/user/orgs \
  -H "Authorization: Bearer dfy_..." \
  -H "Content-Type: application/json" \
  -d '{"login": "acme", "name": "Acme Inc."}'

The response (201 Created) describes the organization, with "type": "Organization" and its html_url.

Creating an organization through the API needs a personal access token with the admin scope that isn't restricted to repositories. A read or write token gets 403 this token needs the admin scope, and a restricted token gets 403 this token is limited to repositories and can't change account settings. In the browser no token is needed.

Name rules

The organization name is its URL (https://diffinery.com/acme) and the owner part of every repository URL. It shares one namespace with usernames and agent names.

  • Starts with a letter or digit, then letters, digits, - and _, up to 100 characters. Dots aren't allowed.
  • Not already used by a person, organization or agent: "name already taken".
  • Not reserved (for example api, settings, new, login, admin, docs): "invalid or reserved organization name".

The organization page

Open https://diffinery.com/acme (you need to be signed in). The header shows the organization's picture, display name, name and your role ("you are an owner", "you are an admin" or "you are a member"). The tabs are:

TabWho sees itWhat it shows
RepositoriesEveryone signed inRepositories you can see. Members get a New repository button.
PeopleEveryone signed inMembers and their roles. Owners and admins also get the Add people form and the Pending invitations list.
AgentsEveryone signed inThe organization's agents, their maximum role and status. Owners and admins get a Manage agents button.
SettingsOwnersThe organization's picture, name and display name. Others get 403 organization owners only.
PackagesEveryone signed inContainer images the organization owns (/acme/-/packages). See Container registry.

People who aren't members only see repositories that are public, or that they were added to directly.

Add and remove members

Owners and admins manage members on the People tab. The top of the tab shows how many members and pending invitations there are, for example "4 members · 1 pending invitation".

Add people

The Add people form takes a username or an email address, like GitHub's "Add people" or "Invite member" dialog:

  1. Open https://diffinery.com/acme?tab=people.
  2. In Username or email, enter a username (octocat, or @octocat) or an email address (friend@example.com).
  3. Choose a Role: Member (default) or Admin. Owners can also choose Owner.
  4. Click Add.

What happens depends on who you entered:

You enteredResult
The username of a personThey become a member right away, with no invitation to accept. You see "Added octocat to acme as member. We let them know." They get an email ("You're now a member of acme", naming who added them) and an in-app notification ("<your username> added you to acme as a member").
The email address of someone who already has an accountThe same as entering their username: they are added right away.
An email address with no accountDiffinery creates an invitation and emails it. The page shows Invitation sent to friend@example.com with the link and a copy button: "They don't have an account yet, so we emailed them an invitation. You can also send them this link yourself. It works once and expires in 14 days: opening it creates their account and passkey and adds them here."

A person whose account is waiting for an invite (pending) becomes active when you add them by username or email. See Invitations for how invitations work, including inviting from the CLI and API.

MessageCause
"enter a username or an email address"The field was empty.
"enter a valid email address"The text has an @ but isn't a complete address such as friend@example.com.
"no one has the username octocat; to invite someone new, enter their email address"No account has that username. Enter their email address to invite them.
"review-bot is not a person; agents belong to an organization through Settings → Agents"Agents and organizations can't be members. Create an organization agent instead (see Organization agents).
"octocat's account is disabled"A site administrator disabled the account.
"octocat is already a member of acme"Change their role in the members table instead.
"friend@example.com already has a pending invitation; revoke it first to send a new one"Revoke the invitation under Pending invitations, then add the address again.
"only owners can add owners"Admins can add members and admins only.
"organization admins only"Only owners, admins and site administrators can add people.

Pending invitations

Owners and admins see a Pending invitations list on the People tab with each invitation's email address, who invited them and when, the expiry date and the role. Accepted and expired invitations aren't listed.

To withdraw an invitation, click Revoke and confirm "Revoke the invitation for friend@example.com? The link stops working." You see "Invitation revoked." Opening the revoked link shows "this invite is invalid, already used, or expired". The person isn't told.

Change a member's role

In the member's row, pick a new role (Owner, Admin or Member) in the Role menu and click Save. The member gets an email saying their role changed and who changed it.

Remove a member

Click Remove in the member's row and confirm "Remove octocat from acme?". The member loses the organization's repository access on their next request, including through their tokens, and gets an email saying they were removed. Their personal repositories and accounts are not affected.

Rules:

RuleMessage
Only owners can promote someone to owner, change an owner's role or remove an owner."only owners can manage owners"
An organization always keeps at least one owner: you can't remove or demote the last one."an organization needs at least one owner; make someone else an owner first"
Only owners and admins can manage members."organization admins only"
The username must belong to a person."user not found"

Leave an organization

There is no separate Leave button. Ask an owner or admin to remove you. Owners and admins can remove themselves from the People tab, as long as another owner remains.

Organization repositories

Any member can create a repository owned by the organization.

  • Web UI: click New repository on the organization's Repositories tab, or choose the organization as Owner on the Create a new repository page (/new).
  • CLI: diffinery repo create website --org acme (add --public for a public repository; the default is private).
  • API: POST /api/v3/orgs/acme/repos with {"name": "website", "visibility": "private"}. The CLI and API need a token with the write or admin scope that isn't restricted to repositories; a read token gets 403 this token needs the write scope.

Access to organization repositories comes from the organization role (members get write, owners and admins get admin), plus any collaborators added under the repository's Settings → Collaborators, which can include people outside the organization and agents. When a repository is created, the organization's other owners and admins get an email, and when one is deleted they are told who deleted it.

Organization secrets

Organization Actions secrets are available to workflows in every repository the organization owns, as ${{ secrets.NAME }}. If a repository has a secret with the same name, the repository's value wins.

Owners and admins (and site administrators) manage them with the CLI or the API; agents can't (403 agents cannot manage secrets). There is no page for them in the web UI yet. Listing, setting and deleting all need a personal access token with the admin scope that isn't restricted to repositories: other tokens get 403 this token needs the admin scope or 403 this token is limited to repositories and can't change account settings.

# Create or update (the value is read from a hidden prompt, stdin or --body)
diffinery secret set NPM_TOKEN --org acme

# List names and when they were updated (values are never returned)
diffinery secret list --org acme

# Delete
diffinery secret delete NPM_TOKEN --org acme
Method and pathWhat it does
GET /api/v3/orgs/{org}/actions/secretsList secret names and updated_at
PUT /api/v3/orgs/{org}/actions/secrets/{name}Create or update; body {"value": "..."}; returns 204
DELETE /api/v3/orgs/{org}/actions/secrets/{name}Delete; returns 204

Names are stored in upper case and must match ^[A-Z_][A-Z0-9_]*$ after upper-casing, otherwise the API returns 422 invalid secret name. Values are encrypted at rest. See Secrets and variables for how workflows use them.

Organization agents

An agent can be owned by an organization instead of a person, so it keeps working when people come and go. Owners and admins create one under Settings → Agents by choosing the organization as Owner, or with diffinery agent create review-bot --org acme. An organization agent can only reach that organization's repositories, and its security emails (new tokens, expiring tokens) go to the organization's owners and admins. See Agents.

Organization picture

The organization's picture shows on its page, next to its repositories and in member lists. Without one, Diffinery shows the first letter or digit of the organization's name on a colored background.

Owners change it on the Settings tab, in the Profile picture box:

  1. Under Upload a new picture, choose a JPEG, PNG or WebP file of up to 5 MB. It is cropped to a centred square; a preview shows the result.
  2. Click Save picture. You see "Organization picture updated."
  3. To go back to initials, click Remove picture and confirm "Remove the picture of acme?". You see "Organization picture removed."

Owners and admins (and site administrators) can also change it through the API, with a token that has the write or admin scope and isn't restricted to repositories. The body is the raw image file:

curl -X PUT https://diffinery.com/api/v3/orgs/acme/avatar \
  -H "Authorization: Bearer dfy_..." \
  --data-binary @logo.png

curl -X DELETE https://diffinery.com/api/v3/orgs/acme/avatar \
  -H "Authorization: Bearer dfy_..."

Both return 200 with the picture's URL, for example {"avatar_url": "https://diffinery.com/api/avatars/acme?v=1760350000"} after an upload and {"avatar_url": "https://diffinery.com/api/avatars/acme"} after removing it. Errors include 403 only organization owners and admins can change its picture for members, and 404 not found when you aren't in the organization. See Profile and preferences for the limits, error messages, the public picture URL and caching.

Rename an organization

Owners open the Settings tab, change Organization name or Display name and click Save. You see "Organization updated." Renaming changes every repository URL in the organization, so existing clones need a new remote URL:

git remote set-url origin https://diffinery.com/acme-inc/website.git

URLs with the old name stop working right away; Diffinery doesn't redirect them.

Organizations API

Method and pathDescription
GET /api/v3/user/orgsOrganizations you belong to, each with your role
POST /api/v3/user/orgsCreate an organization: {"login": "acme", "name": "Acme Inc."}. Needs an admin token.
GET /api/v3/orgs/{org}/membersMembers and their roles. Members and site administrators only; others get 404.
POST /api/v3/orgs/{org}/invitationsInvite by email: {"email": "...", "role": "member"}. Needs an admin token. See Invitations.
GET /api/v3/orgs/{org}/reposRepositories you can see in the organization
POST /api/v3/orgs/{org}/reposCreate a repository in the organization. Needs a write or admin token.
GET, PUT, DELETE /api/v3/orgs/{org}/actions/secrets[/{name}]Organization secrets. Needs an admin token.
PUT /api/v3/orgs/{org}/avatarUpload the organization's picture (raw image body). Owners and admins.
DELETE /api/v3/orgs/{org}/avatarRemove the organization's picture. Owners and admins.

Organization objects in API responses follow GitHub's shape, with "type": "Organization", html_url and avatar_url.

Every endpoint in this table that changes something is an account-level action: besides the scope noted, the token must not be restricted to repositories, or the request gets 403 this token is limited to repositories and can't change account settings (the picture endpoints answer 403 changing a profile picture needs a write or admin token that is not limited to repositories). See Personal access tokens.

Adding existing people directly, changing a member's role, removing a member and revoking invitations are done in the web UI. The full list of endpoints is in the API reference.

How organizations differ from GitHub

  • Diffinery has three organization roles: owner, admin and member. Admins manage members, invitations, agents and secrets but can't manage owners or open the organization's Settings tab.
  • Members get write access to every organization repository. GitHub's default base permission is configurable; Diffinery's is fixed.
  • There are no teams. Give extra access per repository under Settings → Collaborators.
  • Add people works like GitHub's: enter a username or an email address. Unlike GitHub, someone who already has an account joins immediately, with no invitation to accept; only email addresses without an account get an invitation.
  • Organization pages need you to be signed in.
  • Organization secrets apply to every repository in the organization; there is no per-repository selection.