all posts

How to set up team access: orgs, roles and API keys without locking yourself out

Ajay Kumar··9 min read

The support ticket reads the same way every time. Someone's CI pipeline has been happily creating sandboxes for a month. Then a teammate tries to list them from the dashboard and sees nothing. Nothing is broken. The key was minted while the person creating it had a different organisation selected, so it has been writing into a workspace nobody looks at.

I'm Ajay; I built PandaStack. This is the practical guide to setting up access for a team: what the roles actually permit, how key scoping works and where it surprises people, how to rotate without an outage, and the two mistakes that account for most of the pain.

The model in one paragraph

There are organisations. Each org has members, and each member holds one of three roles. Every resource — sandboxes, apps, databases, functions — belongs to exactly one org's workspace. A human authenticates with a dashboard session and acts in whichever org is currently selected. A machine authenticates with an API key, and that key is permanently bound to the workspace of the org that was selected when it was created. That last sentence is the whole article, really.

The three roles, precisely

  • owner — the person who created the org. Full control, including billing and destroying the org. There is exactly one, and the role cannot be handed out by invitation.
  • admin — everything except removing other admins or the owner. Can invite, can remove members, can manage every resource.
  • member — full access to the org's resources: create and destroy sandboxes, deploy apps, manage databases. Cannot invite or remove anyone.

Two consequences are worth stating out loud because people assume otherwise. `member` is not a read-only role — a member can delete a production database. And an `admin` cannot demote or remove another `admin`; the hierarchy check is deliberately strict, so a compromised admin account cannot lock the rest of the team out. If you need to remove an admin, the owner does it.

If you want genuine read-only access for an auditor or a dashboard, this role model does not give it to you today. The honest answer is to give that person their own org and share nothing, or to put a read-only proxy in front of the API. Do not hand out `member` and describe it as limited access, because it is not.

Inviting people

Invites are by email and carry the role they will grant. They can grant `admin` or `member` only — there is no path to a second owner.

# invite a teammate as an admin
curl -X POST "https://api.pandastack.ai/v1/orgs/$ORG_ID/members" \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"dana@example.com","role":"admin"}'

# see who is in the org and what they hold
curl -s "https://api.pandastack.ai/v1/orgs/$ORG_ID/members" \
  -H "Authorization: Bearer $PANDASTACK_API_KEY" | jq '.items[]'

The invitee accepts on first login, which redeems a single-use token and creates the membership row. Invites expire. If someone tells you the link does not work, check the expiry before you check anything else — it is almost always that.

API keys, and the scoping trap

Keys are created from a dashboard session, not from another API key. That is deliberate: a leaked key cannot mint more keys, which meaningfully limits what an attacker can do with one. The flow is mint, copy, store.

# the current org is what the new key will be bound to — check it first
curl -s https://api.pandastack.ai/v1/me \
  -H "Authorization: Bearer $DASHBOARD_JWT" | jq '.current_org'

# switch, if that is not the org you meant
curl -X POST https://api.pandastack.ai/v1/me/current-org \
  -H "Authorization: Bearer $DASHBOARD_JWT" \
  -H "Content-Type: application/json" \
  -d '{"org_id":"'"$ORG_ID"'"}'

# mint. the full secret is returned exactly once
curl -X POST https://api.pandastack.ai/v1/me/tokens \
  -H "Authorization: Bearer $DASHBOARD_JWT" \
  -H "Content-Type: application/json" \
  -d '{"label":"github-actions-ci"}'

The response contains the key once. We store a hash of it and a short prefix, and nothing else. There is no endpoint that returns an existing secret, no support process that recovers one, and no database column that holds one. If you lose it, you revoke it and mint another.

# list what exists: labels and prefixes, never the secret
curl -s https://api.pandastack.ai/v1/me/tokens \
  -H "Authorization: Bearer $DASHBOARD_JWT" | jq '.items[]'

# revoke by prefix
curl -X DELETE "https://api.pandastack.ai/v1/me/tokens/pds_a1b2c3d4" \
  -H "Authorization: Bearer $DASHBOARD_JWT"
The trap: a key is bound to the org that was current at the moment you minted it, and switching orgs later does not follow it. Switch first, verify with /v1/me, then mint. Naming keys after the org they belong to — `acme-prod-ci` rather than `ci` — makes the mistake visible in the key list instead of three weeks later in a support ticket.

One key per consumer, always

The single highest-value habit here costs nothing: never share a key between two things. One for CI, one for the staging deployer, one for each engineer's laptop, one per production service. The reason is not least privilege — every key currently has the same permissions — it is blast radius during rotation.

If four systems share a key and one of them leaks it, rotating means coordinating four deploys under time pressure. If they have four keys, you revoke one, and the other three never notice. The prefix in the key list is what makes this work: your logs show which prefix acted, so you can answer "who did this" without a shared-secret investigation.

Rotating without an outage

There is no built-in grace period, so overlap is something you construct. It works because there is no limit on how many keys can be live at once.

  1. Mint the replacement with a label that says what it replaces and when: `github-actions-ci-2026-08`.
  2. Deploy the new secret everywhere the old one is used. Do not remove the old one yet.
  3. Wait a full cycle of whatever uses it — one nightly job, one deploy, one cron run. Long enough that anything still holding the old key has had a chance to run.
  4. Revoke the old prefix.
  5. Do one deliberate operation with the new key to confirm it works before you close the ticket.

Skipping step three is how a rotation becomes an incident. The thing that breaks is never the service you were thinking about; it is the weekly report job that only runs on Sunday.

If a key leaks

Revoke first, investigate second. Revocation is immediate and free, and a key you revoked unnecessarily costs you one deploy, while a key you left live during an investigation costs you whatever the attacker does next.

Then look at what ran. Sandbox creates carry the workspace they were made in, so the useful question is not "was the key used" but "were there resources created that nobody on the team recognises". Check running sandboxes and apps in that org, and check the billing usage — an abused key almost always shows up as a compute spike before it shows up anywhere else.

We have had this happen for real, from a key committed to a public repository. What caught it was compute usage on an org that had been quiet for a month, not any clever detection. Set a billing alert on every org, including the ones you think are idle. Especially the ones you think are idle.

A setup that holds up

For a small team, this is what I would do and what we do ourselves. Two orgs, production and staging, so that no single credential can touch both. The owner is a person who will still be at the company in a year, not a shared account. Every engineer is an `admin` in staging and a `member` in production, so day-to-day work needs no elevation and adding someone to production is a deliberate act. Machine keys are one per consumer, labelled with org and purpose, rotated on a calendar rather than after an incident. Billing alerts on both orgs.

None of that is clever. It is just the arrangement where each individual mistake stays small, which in practice is the only property of an access model that matters.

Frequently asked questions

Why can't I create an API key with another API key?

Key creation requires a dashboard session rather than a key, so a leaked key cannot mint further credentials for itself. That bounds what an attacker can do: they can use the key you leaked until you revoke it, but they cannot establish a second credential that survives the revocation. It does mean automated key provisioning needs a human in the loop, which is a tradeoff we made deliberately.

What happens to an API key if I switch organisations?

Nothing — and that is the surprise. The key stays bound to the workspace of the org that was current when it was minted. Switching orgs in the dashboard changes what your session acts on, not what an existing key acts on. Check the current org before minting, and put the org name in the key's label so the binding is visible in the list rather than discoverable only by experiment.

Is there a read-only role?

No. The `member` role has full access to the org's resources including deletion; it is limited only in that it cannot invite or remove people. If you need genuine read-only access — for an auditor, an external dashboard, a status page — the practical answers today are a separate org with nothing shared into it, or your own read-only proxy in front of the API. Handing out `member` and calling it limited access would be misleading.

How do I rotate a key with no downtime?

Mint the replacement before revoking the old one; multiple keys can be live simultaneously. Deploy the new secret everywhere, then wait for one complete cycle of every consumer — including the weekly job you forgot about — before revoking the old prefix. The overlap window is the entire mechanism, and shortening it is what turns a routine rotation into a pager.

Can an admin remove another admin?

No. An admin can invite people and remove members, but the hierarchy check refuses when the target is another admin or the owner. Only the owner can remove an admin. This keeps a single compromised or departing admin account from being able to lock the rest of the team out of the organisation.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.