Skip to content

feat(dashboard-api): project upsert, member sync and user purge - #3442

Merged
ben-fornefeld merged 13 commits into
mainfrom
dashboard-api-management-interface-en-1740
Jul 29, 2026
Merged

ben-fornefeld merged 13 commits into
mainfrom
dashboard-api-management-interface-en-1740

Conversation

@ben-fornefeld

@ben-fornefeld ben-fornefeld commented Jul 29, 2026 •

Copy link
Copy Markdown
Member

Leaves 501 for every /v1/management operation except project deletion. Completes the cluster-side half of the contract belt's workspace-api calls (EN-1740, EN-1741, EN-1743).

What lands

Route Behaviour
PUT /projects/{id} Creates from a caller-supplied UUID on the default tier, or reconciles. 201 vs 200
PUT·DELETE /projects/{id}/members/{userId} One shared implementation
POST /projects/{id}/members/batch Same implementation, both directions in one transaction
DELETE /users/{id} Memberships and access tokens; public.users survives

Contract changes

email is now required. The caller synchronizes every property in the upsert body and sends all of them on every push, so a reconcile is a complete statement of the project rather than a patch. Runtime-breaking for a caller that omits it.

project_type is gone. This side has no column for it and no opinion about the caller's plan vocabulary — the tier is assigned once at creation from a local default, and the limits that matter arrive absolute through upsertProjectLimits. Nothing sets additionalProperties: false, so a caller still sending the field has it ignored rather than rejected; the break is at their next codegen, not at runtime.

The handler branches on whether the project exists rather than on an insert failing, which is what makes "a create assigns the tier, a reconcile never touches it" visible in the code.

Why membership writes moved out of the handlers

Auth caches a copy of the team per member under <userID>-<teamID>, and InvalidateTeamCache discovers those keys by reading users_teams — so a member already removed is one it structurally cannot see. Writing and evicting from separate call sites leaves a revoked member authenticating until the entry expires.

internal/management makes that unrepresentable: the write and its evictions are one call. Three routes converge on it, so the per-member and batch paths cannot answer differently for the same desired state.

Two consequences worth flagging:

  • Eviction covers every user the request named, not the rows that moved. A crash before the eviction leaves a stale entry only a retry clears, and that retry finds the work already done — keying off what the statement touched would make the recovery path a no-op.
  • Membership changes evict pair keys only. Sweeping the team-wide entries too would evict every API key on the project for a change none of them observe.

Review caught the inverse of the first point in PurgeUser, fixed in 17128ae: it read the user's teams, then ran an unconditional delete, then evicted for the earlier read. Under READ COMMITTED those are separate statement snapshots. It is now DELETE ... RETURNING team_id, so the evicted set is the deleted set by construction.

Project deletion stays 501

envs, snapshots and volumes reference teams with ON DELETE NO ACTION and template deletion only stamps deleted_at, so any project that ever built one pins its team row. Releasing it needs the api service's orchestrator connections, which this process does not have.

Reason and the three ways out are recorded in the handler, with a test pinning the 501. Belt drops its caller separately.

Notes

  • ADMIN_AUTH_PROVIDER_CONFIG is still absent from iac/provider-gcp/dashboard-api.tf, so these routes answer 401 in production until EN-2007.
  • Cross-service ordering gaps found during review are tracked in EN-2012 rather than fixed here — they need workspace-api live to observe, and several want a decision rather than a fix.
  • internal/management handles project, handlers handle team — the contract's word meeting the cluster's, translated at the call site.

Implements the remaining /v1/management operations except project
deletion, which stays 501 with the reason recorded in the handler.

Membership writes live in a new internal/management package together
with their cache evictions: auth caches a copy of the team per member,
and the sweep that would find those keys reads users_teams, so a
removal has to name its evictions itself.

Adds an optional email to the project upsert contract. It is required
to create a project and optional to reconcile one, which a single
operation cannot express in the schema.
@linear-code

linear-code Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

EN-1740

@cursor

cursor Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes membership and auth cache behavior and tightens the management API contract (required email); incorrect eviction or slug conflict handling could affect access until cache TTL, but logic is transactional and tested.

Overview
Implements the cluster-side /v1/management contract for workspace residency: project upsert (create/reconcile teams by caller UUID, default tier on create only, required email, project_type removed), member sync (single, delete, and batch with contradiction checks) via internal/management with post-commit auth cache evictions, and user purge (memberships and access tokens; users row kept). Auth invalidates team/member cache entries with detached, bounded contexts so client disconnect does not skip eviction. Project delete stays 501 with documented rationale. OpenAPI and architecture docs updated; broad handler and integration tests added.

Reviewed by Cursor Bugbot for commit bf5b488. Bugbot is set up for automated code reviews on this repo. Configure here.

@codecov

codecov Bot commented Jul 29, 2026 •

Copy link
Copy Markdown

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
3647 1 3646 7
View the top 1 failed test(s) by shortest run time
github.com/e2b-dev/infra/tests/integration/internal/tests/api/metrics::TestSandboxMetrics
Stack Traces | 31.6s run time
=== RUN   TestSandboxMetrics
=== PAUSE TestSandboxMetrics
=== CONT  TestSandboxMetrics
    sandbox_metrics_test.go:26: 
        	Error Trace:	.../api/metrics/sandbox_metrics_test.go:26
        	Error:      	Condition never satisfied
        	Test:       	TestSandboxMetrics
        	Messages:   	sandbox metrics not available in time
    sandbox_metrics_test.go:28: 
        	Error Trace:	.../api/metrics/sandbox_metrics_test.go:28
        	            				.../hostedtoolcache/go/1.26.5.../src/runtime/asm_amd64.s:1771
        	Error:      	Received unexpected error:
        	            	Get "http://localhost:.../sandboxes/ilmxvtku798axjsejcrey/metrics": context canceled
        	Test:       	TestSandboxMetrics
--- FAIL: TestSandboxMetrics (31.61s)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Comment thread packages/dashboard-api/internal/management/service.go Outdated
…r read

Under READ COMMITTED the pre-read and the unconditional delete take
different statement snapshots, so a membership committed between them
was removed without its cache entry being evicted. DELETE ... RETURNING
makes the eviction set the deleted set by construction, matching what
the membership sync already does.
ben-fornefeld and others added 3 commits July 29, 2026 18:03
…existence

The caller synchronizes every property in the upsert body and sends all
of them on every push, so email is required rather than optional and a
reconcile is a complete statement instead of a patch.

project_type leaves the contract. This side has no column for it and no
opinion about the caller's plan vocabulary: the tier is assigned once at
creation from a local default, and the limits that matter arrive
absolute through upsertProjectLimits. Nothing sets additionalProperties,
so a caller still sending the field has it ignored rather than rejected.

The handler now branches on whether the project exists rather than on an
insert failing, which is what makes 'create assigns the tier, reconcile
never touches it' visible in the code.
…rvice

The upsert grew past what belonged in a handler: three functions, a
type, three sentinels and a tier constant, none of them touching gin.
Keeping it in the web layer meant that layer importing dberrors and
authqueries, and knowing a Postgres constraint name to tell a taken slug
from a real failure.

The service now reports outcomes as sentinel errors and owns the cache
eviction its own write invalidates, the same rule the membership
operations already followed. The handler parses, calls, maps sentinels
to status codes and responds.

Splits the package by feature — service.go for the type, members.go and
project.go for the operations — with the tests following the same split.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb2b0667b6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dashboard-api/internal/management/members.go
RedisCache.Delete documents that its caller must pass a context detached
from request cancellation when the delete must not be skipped, and
InvalidateAPIKeyCache already does. The two team invalidators did not,
so an invalidation running after its write had committed was skipped
whenever the client had gone away.

That matters most for the management purge route, whose eviction set
comes from the delete's own RETURNING: a retry finds the rows already
gone and evicts nothing, leaving a purged user authenticating until the
entry expires. The caller times out by design before retrying, so the
cancelled context is the expected path rather than a rare one.

Also fixes the same latent gap in the dashboard's own member routes, the
limits push, and the api service's team-kill route.
The comment credited the rule to the caller's DNS namespace, which is
the M5 reason and implies this side would accept a rename if the caller
allowed one. It would not: register_build stamps the team slug onto
every template alias it claims and names render as slug/alias, so a
rename without rewriting those rows orphans every template the team
owns.
A slug was refused on reconcile because template names are stored as
'<slug>/<alias>' and a rename would have addressed the project's
templates under a slug it no longer had. Repointing env_aliases.namespace
in the same transaction removes that reason, so renames are allowed.

Two costs stay with the caller and are noted in the contract: the names
its users already type change, and the api service resolves aliases
through a cache, so the old ones answer for up to its TTL. Aliases
predating the namespace column keep their null, which is how they are
still resolved.

teams_slug_unique now applies on the way out as well as in, so a rename
onto a taken slug is the same 409 a create would get.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e498f2e. Configure here.

Comment thread packages/dashboard-api/internal/management/project.go
ben-fornefeld and others added 5 commits July 29, 2026 21:47
Template names are unique on (alias, namespace), so repointing them
first meant a rename onto a taken slug collided there rather than on
teams_slug_unique, reporting a conflict the contract calls 409 as a 500.
Only when both projects held a template of the same name, which is why
the existing test missed it.

Claiming the slug first makes the repoint collision-free by
construction: no other project can hold the namespace once the slug is
ours, and a team's aliases all share one namespace.
…ames

Repointing env_aliases.namespace on a rename broke every template
reference a user had already scripted, to fix an inconsistency that is
merely untidy: names embed the slug they were built under, so a renamed
project keeps its existing template names and only new ones carry the
new slug.

Dropping the side effect also drops what it dragged in — the ordering
constraint against the alias unique index, and the read of the stored
slug that decided whether to repoint. The upsert writes the row and
nothing else.

teams_slug_unique still applies on rename, so a taken slug is the same
409 a create would get.
@ben-fornefeld
ben-fornefeld merged commit f997c39 into main Jul 29, 2026
47 checks passed
@ben-fornefeld
ben-fornefeld deleted the dashboard-api-management-interface-en-1740 branch July 29, 2026 20:41
charlie-e2b added a commit that referenced this pull request Jul 30, 2026
🤖 I have created a release *beep* *boop*
---


## 0.0.1 (2026-07-30)


### Features

* add workspace admin API foundations
([#3314](#3314))
([0f72030](0f72030))
* **api:** LD-gated ClickHouse read switcher
([#3061](#3061))
([29e74ca](29e74ca))
* **api:** soft-delete build layers in DB on user delete
([#3121](#3121))
([ee88776](ee88776))
* **auth:** support admin token team auth
([#2934](#2934))
([5496666](5496666))
* **auth:** verifiers on one axis, and a reusable authenticator
constructor ([#3423](#3423))
([923b99b](923b99b))
* **dashboard-api:** add internal admin route for deleting a user
([#2986](#2986))
([ecc1291](ecc1291))
* **dashboard-api:** add internal team creation
([#2824](#2824))
([375051b](375051b))
* **dashboard-api:** add OIDC admin user bootstrap endpoint
([#2841](#2841))
([6a7a59e](6a7a59e))
* **dashboard-api:** add Ory user profile provider and auth middleware
fix ([#2840](#2840))
([30d40d2](30d40d2))
* **dashboard-api:** add template tags handlers
([#2885](#2885))
([bf52a4b](bf52a4b))
* **dashboard-api:** batch member sync route, and unenumerate
project_type ([#3427](#3427))
([cc16acf](cc16acf))
* **dashboard-api:** expose auth profile admin routes
([#2743](#2743))
([b673a10](b673a10))
* **dashboard-api:** flag sandboxes past data retention
([#3102](#3102))
([9b162bf](9b162bf))
* **dashboard-api:** implement upsertProjectLimits
([#3438](#3438))
([ec1ed29](ec1ed29))
* **dashboard-api:** include build resources in /builds response
([#3009](#3009))
([bf49c32](bf49c32))
* **dashboard-api:** map Ory SSO organizations to E2B teams
([#3094](#3094))
([dbd098f](dbd098f))
* **dashboard-api:** populate Ory identity external_id on admin
bootstrap ([#3062](#3062))
([6c51232](6c51232))
* **dashboard-api:** project upsert, member sync and user purge
([#3442](#3442))
([f997c39](f997c39))
* **dashboard-api:** templates list pagination
([#2904](#2904))
([6882463](6882463))
* **db:** add project_limits, an override the limits owner can write
([#3429](#3429))
([021c2a4](021c2a4))
* improve templates list sorting
([#2983](#2983))
([51ad7ff](51ad7ff))
* **otel:** instrument auth service HTTP client with otelhttp
([#2722](#2722))
([69b085d](69b085d))
* per-team events TTL limit (tier + addons)
([#3181](#3181))
([f76b2cb](f76b2cb))


### Bug Fixes

* added api and orch
([#3454](#3454))
([fda5e45](fda5e45))
* **api:** copy auth/internal into api and dashboard-api image builds
([#3323](#3323))
([bda1fee](bda1fee))
* **api:** invalidate auth cache on API key deletion
([#3324](#3324))
([8b02910](8b02910))
* correct 3 CVES ([#3218](#3218))
([076823b](076823b))
* **dashboard-api:** avoid repeated Ory bootstrap provisioning
([#2940](#2940))
([da5ce59](da5ce59))
* **dashboard-api:** drop removed read-replica accessor in provisioning
tests ([#3340](#3340))
([6addc91](6addc91))
* **dashboard-api:** pass signup metadata to billing provisioning
([#2978](#2978))
([d0ea5b4](d0ea5b4))
* **dashboard-api:** set Ory external_id only after the bootstrap commit
([#3133](#3133))
([00ad04b](00ad04b))
* push client-proxy, dashboard-api, and docker-reverse-proxy image…
([#2953](#2953))
([1d930ee](1d930ee))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
Co-authored-by: Charlie Wyse <charlie.wyse@e2b.dev>
charlie-e2b pushed a commit that referenced this pull request Jul 31, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](dashboard-api-v0.0.1...dashboard-api-v0.1.0)
(2026-07-31)


### Features

* add workspace admin API foundations
([#3314](#3314))
([0f72030](0f72030))
* **api:** LD-gated ClickHouse read switcher
([#3061](#3061))
([29e74ca](29e74ca))
* **api:** soft-delete build layers in DB on user delete
([#3121](#3121))
([ee88776](ee88776))
* **auth:** support admin token team auth
([#2934](#2934))
([5496666](5496666))
* **auth:** verifiers on one axis, and a reusable authenticator
constructor ([#3423](#3423))
([923b99b](923b99b))
* **dashboard-api:** add internal admin route for deleting a user
([#2986](#2986))
([ecc1291](ecc1291))
* **dashboard-api:** add internal team creation
([#2824](#2824))
([375051b](375051b))
* **dashboard-api:** add OIDC admin user bootstrap endpoint
([#2841](#2841))
([6a7a59e](6a7a59e))
* **dashboard-api:** add Ory user profile provider and auth middleware
fix ([#2840](#2840))
([30d40d2](30d40d2))
* **dashboard-api:** add template tags handlers
([#2885](#2885))
([bf52a4b](bf52a4b))
* **dashboard-api:** batch member sync route, and unenumerate
project_type ([#3427](#3427))
([cc16acf](cc16acf))
* **dashboard-api:** expose auth profile admin routes
([#2743](#2743))
([b673a10](b673a10))
* **dashboard-api:** flag sandboxes past data retention
([#3102](#3102))
([9b162bf](9b162bf))
* **dashboard-api:** implement upsertProjectLimits
([#3438](#3438))
([ec1ed29](ec1ed29))
* **dashboard-api:** include build resources in /builds response
([#3009](#3009))
([bf49c32](bf49c32))
* **dashboard-api:** map Ory SSO organizations to E2B teams
([#3094](#3094))
([dbd098f](dbd098f))
* **dashboard-api:** populate Ory identity external_id on admin
bootstrap ([#3062](#3062))
([6c51232](6c51232))
* **dashboard-api:** project upsert, member sync and user purge
([#3442](#3442))
([f997c39](f997c39))
* **dashboard-api:** templates list pagination
([#2904](#2904))
([6882463](6882463))
* **db:** add project_limits, an override the limits owner can write
([#3429](#3429))
([021c2a4](021c2a4))
* improve templates list sorting
([#2983](#2983))
([51ad7ff](51ad7ff))
* **otel:** instrument auth service HTTP client with otelhttp
([#2722](#2722))
([69b085d](69b085d))
* per-team events TTL limit (tier + addons)
([#3181](#3181))
([f76b2cb](f76b2cb))


### Bug Fixes

* added api and orch
([#3454](#3454))
([fda5e45](fda5e45))
* **api:** copy auth/internal into api and dashboard-api image builds
([#3323](#3323))
([bda1fee](bda1fee))
* **api:** invalidate auth cache on API key deletion
([#3324](#3324))
([8b02910](8b02910))
* correct 3 CVES ([#3218](#3218))
([076823b](076823b))
* creating whitespace to test publish
([#3476](#3476))
([5158cc9](5158cc9))
* **dashboard-api:** avoid repeated Ory bootstrap provisioning
([#2940](#2940))
([da5ce59](da5ce59))
* **dashboard-api:** drop removed read-replica accessor in provisioning
tests ([#3340](#3340))
([6addc91](6addc91))
* **dashboard-api:** pass signup metadata to billing provisioning
([#2978](#2978))
([d0ea5b4](d0ea5b4))
* **dashboard-api:** set Ory external_id only after the bootstrap commit
([#3133](#3133))
([00ad04b](00ad04b))
* push client-proxy, dashboard-api, and docker-reverse-proxy image…
([#2953](#2953))
([1d930ee](1d930ee))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
jakubno pushed a commit that referenced this pull request Aug 3, 2026
Leaves 501 for every `/v1/management` operation except project deletion.
Completes the cluster-side half of the contract belt's workspace-api
calls (EN-1740, EN-1741, EN-1743).

## What lands

| Route | Behaviour |
|---|---|
| `PUT /projects/{id}` | Creates from a caller-supplied UUID on the
default tier, or reconciles. 201 vs 200 |
| `PUT`·`DELETE /projects/{id}/members/{userId}` | One shared
implementation |
| `POST /projects/{id}/members/batch` | Same implementation, both
directions in one transaction |
| `DELETE /users/{id}` | Memberships and access tokens; `public.users`
survives |

## Contract changes

**`email` is now required.** The caller synchronizes every property in
the upsert body and sends all of them on every push, so a reconcile is a
complete statement of the project rather than a patch. Runtime-breaking
for a caller that omits it.

**`project_type` is gone.** This side has no column for it and no
opinion about the caller's plan vocabulary — the tier is assigned once
at creation from a local default, and the limits that matter arrive
absolute through `upsertProjectLimits`. Nothing sets
`additionalProperties: false`, so a caller still sending the field has
it **ignored rather than rejected**; the break is at their next codegen,
not at runtime.

The handler branches on whether the project exists rather than on an
insert failing, which is what makes "a create assigns the tier, a
reconcile never touches it" visible in the code.

## Why membership writes moved out of the handlers

Auth caches a copy of the team **per member** under `<userID>-<teamID>`,
and `InvalidateTeamCache` discovers those keys by reading `users_teams`
— so a member already removed is one it structurally cannot see. Writing
and evicting from separate call sites leaves a revoked member
authenticating until the entry expires.

`internal/management` makes that unrepresentable: the write and its
evictions are one call. Three routes converge on it, so the per-member
and batch paths cannot answer differently for the same desired state.

Two consequences worth flagging:

- Eviction covers every user the request **named**, not the rows that
moved. A crash before the eviction leaves a stale entry only a retry
clears, and that retry finds the work already done — keying off what the
statement touched would make the recovery path a no-op.
- Membership changes evict pair keys only. Sweeping the team-wide
entries too would evict every API key on the project for a change none
of them observe.

Review caught the inverse of the first point in `PurgeUser`, fixed in
17128ae: it read the user's teams, then ran an unconditional delete,
then evicted for the earlier read. Under READ COMMITTED those are
separate statement snapshots. It is now `DELETE ... RETURNING team_id`,
so the evicted set is the deleted set by construction.

## Project deletion stays 501

`envs`, `snapshots` and `volumes` reference `teams` with `ON DELETE NO
ACTION` and template deletion only stamps `deleted_at`, so any project
that ever built one pins its team row. Releasing it needs the api
service's orchestrator connections, which this process does not have.

Reason and the three ways out are recorded in the handler, with a test
pinning the 501. Belt drops its caller separately.

## Notes

- `ADMIN_AUTH_PROVIDER_CONFIG` is still absent from
`iac/provider-gcp/dashboard-api.tf`, so these routes answer 401 in
production until EN-2007.
- Cross-service ordering gaps found during review are tracked in EN-2012
rather than fixed here — they need workspace-api live to observe, and
several want a decision rather than a fix.
- `internal/management` handles `project`, handlers handle `team` — the
contract's word meeting the cluster's, translated at the call site.
jakubno pushed a commit that referenced this pull request Aug 3, 2026
🤖 I have created a release *beep* *boop*
---


## 0.0.1 (2026-07-30)


### Features

* add workspace admin API foundations
([#3314](#3314))
([0f72030](0f72030))
* **api:** LD-gated ClickHouse read switcher
([#3061](#3061))
([29e74ca](29e74ca))
* **api:** soft-delete build layers in DB on user delete
([#3121](#3121))
([ee88776](ee88776))
* **auth:** support admin token team auth
([#2934](#2934))
([5496666](5496666))
* **auth:** verifiers on one axis, and a reusable authenticator
constructor ([#3423](#3423))
([f68e713](f68e713))
* **dashboard-api:** add internal admin route for deleting a user
([#2986](#2986))
([ecc1291](ecc1291))
* **dashboard-api:** add internal team creation
([#2824](#2824))
([375051b](375051b))
* **dashboard-api:** add OIDC admin user bootstrap endpoint
([#2841](#2841))
([6a7a59e](6a7a59e))
* **dashboard-api:** add Ory user profile provider and auth middleware
fix ([#2840](#2840))
([30d40d2](30d40d2))
* **dashboard-api:** add template tags handlers
([#2885](#2885))
([bf52a4b](bf52a4b))
* **dashboard-api:** batch member sync route, and unenumerate
project_type ([#3427](#3427))
([6d8dc38](6d8dc38))
* **dashboard-api:** expose auth profile admin routes
([#2743](#2743))
([b673a10](b673a10))
* **dashboard-api:** flag sandboxes past data retention
([#3102](#3102))
([9b162bf](9b162bf))
* **dashboard-api:** implement upsertProjectLimits
([#3438](#3438))
([f4ee390](f4ee390))
* **dashboard-api:** include build resources in /builds response
([#3009](#3009))
([bf49c32](bf49c32))
* **dashboard-api:** map Ory SSO organizations to E2B teams
([#3094](#3094))
([dbd098f](dbd098f))
* **dashboard-api:** populate Ory identity external_id on admin
bootstrap ([#3062](#3062))
([6c51232](6c51232))
* **dashboard-api:** project upsert, member sync and user purge
([#3442](#3442))
([8c90702](8c90702))
* **dashboard-api:** templates list pagination
([#2904](#2904))
([6882463](6882463))
* **db:** add project_limits, an override the limits owner can write
([#3429](#3429))
([5ab6259](5ab6259))
* improve templates list sorting
([#2983](#2983))
([51ad7ff](51ad7ff))
* **otel:** instrument auth service HTTP client with otelhttp
([#2722](#2722))
([69b085d](69b085d))
* per-team events TTL limit (tier + addons)
([#3181](#3181))
([f76b2cb](f76b2cb))


### Bug Fixes

* added api and orch
([#3454](#3454))
([d56e0a8](d56e0a8))
* **api:** copy auth/internal into api and dashboard-api image builds
([#3323](#3323))
([bda1fee](bda1fee))
* **api:** invalidate auth cache on API key deletion
([#3324](#3324))
([8b02910](8b02910))
* correct 3 CVES ([#3218](#3218))
([076823b](076823b))
* **dashboard-api:** avoid repeated Ory bootstrap provisioning
([#2940](#2940))
([da5ce59](da5ce59))
* **dashboard-api:** drop removed read-replica accessor in provisioning
tests ([#3340](#3340))
([6addc91](6addc91))
* **dashboard-api:** pass signup metadata to billing provisioning
([#2978](#2978))
([d0ea5b4](d0ea5b4))
* **dashboard-api:** set Ory external_id only after the bootstrap commit
([#3133](#3133))
([00ad04b](00ad04b))
* push client-proxy, dashboard-api, and docker-reverse-proxy image…
([#2953](#2953))
([1d930ee](1d930ee))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
Co-authored-by: Charlie Wyse <charlie.wyse@e2b.dev>
jakubno pushed a commit that referenced this pull request Aug 3, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.0](dashboard-api-v0.0.1...dashboard-api-v0.1.0)
(2026-07-31)


### Features

* add workspace admin API foundations
([#3314](#3314))
([0f72030](0f72030))
* **api:** LD-gated ClickHouse read switcher
([#3061](#3061))
([29e74ca](29e74ca))
* **api:** soft-delete build layers in DB on user delete
([#3121](#3121))
([ee88776](ee88776))
* **auth:** support admin token team auth
([#2934](#2934))
([5496666](5496666))
* **auth:** verifiers on one axis, and a reusable authenticator
constructor ([#3423](#3423))
([f68e713](f68e713))
* **dashboard-api:** add internal admin route for deleting a user
([#2986](#2986))
([ecc1291](ecc1291))
* **dashboard-api:** add internal team creation
([#2824](#2824))
([375051b](375051b))
* **dashboard-api:** add OIDC admin user bootstrap endpoint
([#2841](#2841))
([6a7a59e](6a7a59e))
* **dashboard-api:** add Ory user profile provider and auth middleware
fix ([#2840](#2840))
([30d40d2](30d40d2))
* **dashboard-api:** add template tags handlers
([#2885](#2885))
([bf52a4b](bf52a4b))
* **dashboard-api:** batch member sync route, and unenumerate
project_type ([#3427](#3427))
([6d8dc38](6d8dc38))
* **dashboard-api:** expose auth profile admin routes
([#2743](#2743))
([b673a10](b673a10))
* **dashboard-api:** flag sandboxes past data retention
([#3102](#3102))
([9b162bf](9b162bf))
* **dashboard-api:** implement upsertProjectLimits
([#3438](#3438))
([f4ee390](f4ee390))
* **dashboard-api:** include build resources in /builds response
([#3009](#3009))
([bf49c32](bf49c32))
* **dashboard-api:** map Ory SSO organizations to E2B teams
([#3094](#3094))
([dbd098f](dbd098f))
* **dashboard-api:** populate Ory identity external_id on admin
bootstrap ([#3062](#3062))
([6c51232](6c51232))
* **dashboard-api:** project upsert, member sync and user purge
([#3442](#3442))
([8c90702](8c90702))
* **dashboard-api:** templates list pagination
([#2904](#2904))
([6882463](6882463))
* **db:** add project_limits, an override the limits owner can write
([#3429](#3429))
([5ab6259](5ab6259))
* improve templates list sorting
([#2983](#2983))
([51ad7ff](51ad7ff))
* **otel:** instrument auth service HTTP client with otelhttp
([#2722](#2722))
([69b085d](69b085d))
* per-team events TTL limit (tier + addons)
([#3181](#3181))
([f76b2cb](f76b2cb))


### Bug Fixes

* added api and orch
([#3454](#3454))
([d56e0a8](d56e0a8))
* **api:** copy auth/internal into api and dashboard-api image builds
([#3323](#3323))
([bda1fee](bda1fee))
* **api:** invalidate auth cache on API key deletion
([#3324](#3324))
([8b02910](8b02910))
* correct 3 CVES ([#3218](#3218))
([076823b](076823b))
* creating whitespace to test publish
([#3476](#3476))
([6b4177f](6b4177f))
* **dashboard-api:** avoid repeated Ory bootstrap provisioning
([#2940](#2940))
([da5ce59](da5ce59))
* **dashboard-api:** drop removed read-replica accessor in provisioning
tests ([#3340](#3340))
([6addc91](6addc91))
* **dashboard-api:** pass signup metadata to billing provisioning
([#2978](#2978))
([d0ea5b4](d0ea5b4))
* **dashboard-api:** set Ory external_id only after the bootstrap commit
([#3133](#3133))
([00ad04b](00ad04b))
* push client-proxy, dashboard-api, and docker-reverse-proxy image…
([#2953](#2953))
([1d930ee](1d930ee))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: e2b-release-please[bot] <298072688+e2b-release-please[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants