Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4114,7 +4114,7 @@ Diffed against: <https://raw.githubusercontent.com/e2b-dev/infra/main/spec/opena
- [ ] `GET /teams` — team lookup resolving the teamID stamped on sandboxes, templates and snapshots (medium)
- [x] `GET /templates/{templateID}` (returns the template's build list) — template build history - durations, statuses and failure rates for the build pipeline (medium)
- [ ] `GET /templates/{templateID}/builds/{buildID}/status` — per-build status and logs; see the reasons below (medium)
- [ ] `GET /templates/{templateID}/tags` — template version/tag lookup, needed to attribute sandboxes to a template version (low)
- [x] `GET /templates/{templateID}/tags` — template version/tag lookup, needed to attribute sandboxes to a template version (low)
- [ ] `GET /volumes` — persistent volume inventory and their sandbox attachments (low)
- [ ] `GET /v2/sandboxes/{sandboxID}/logs` — per-sandbox logs for failure analysis; high volume and per-ID fetch, so nice to have (low)

Expand All @@ -4125,6 +4125,7 @@ Not covered, with reasons:
- `/teams` is the one endpoint in the spec that does not accept `ApiKeyAuth` — it is `AuthProviderBearerAuth` only, so the team-scoped API key this source stores cannot call it. Its `Team` schema also returns the team's live `apiKey`, which must not land in a warehouse table. The gap's rationale does not hold either: `ListedSandbox`, `Template` and `SnapshotInfo` carry no `teamID` to resolve.
- `/teams/{teamID}/metrics/max` returns a single `{timestamp, value}` object per `metric` enum value, so it is one scalar per call rather than a table, and it is the max of the `team_metrics` series over the same window.
- `/templates/{templateID}/builds/{buildID}/status` is a second fan-out hop costing one request per build. Its only non-log fields (`templateID`, `buildID`, `status`) already arrive in `template_builds`; the rest is build log entries.
- `/templates/{templateID}/tags` is covered by `template_tags`, one request per template. A tag name is unique only within its template, so the key is `templateID` plus `tag`. The tag's `createdAt` changes when the tag moves to a new build, so the table has no partition key.
- `/teams/{teamID}/metrics` and the sandbox metrics endpoints do take `start`/`end` filters, but the spec documents no ordering guarantee, and a fan-out child's global ascending watermark would advance past sandboxes a partial run has not reached. Both ship full refresh, like the rest of the source.

## Easybill — gaps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@
"envdVersion": "Version of the envd daemon baked into the build.",
},
},
"template_tags": {
"description": "Tags of each sandbox template, with the build each tag currently points at.",
"docs_url": "https://e2b.dev/docs",
"columns": {
"templateID": "Identifier of the template the tag belongs to.",
"tag": "The tag name.",
"buildID": "Identifier of the build associated with this tag.",
"createdAt": "Time when the tag was assigned.",
},
},
"team_metrics": {
"description": "Concurrent sandboxes and sandbox start rate for the team over time.",
"docs_url": "https://e2b.dev/docs",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import SourceSetupIntro from "../\_snippets/source-setup-intro.mdx"
import SyncModes from "../\_snippets/sync-modes.mdx"
import TroubleshootingLink from "../\_snippets/dw-troubleshooting-link.mdx"

[E2B](https://e2b.dev) runs secure cloud sandboxes for executing AI-agent-generated code. Linking E2B as a source syncs your sandbox infrastructure into the PostHog data warehouse: running and paused sandboxes, templates and their build history, snapshots, and CPU, memory and disk usage per sandbox. You can then join agent sandbox activity with your product analytics.
[E2B](https://e2b.dev) runs secure cloud sandboxes for executing AI-agent-generated code. Linking E2B as a source syncs your sandbox infrastructure into the PostHog data warehouse: running and paused sandboxes, templates with their build history and tags, snapshots, and CPU, memory and disk usage per sandbox. You can then join agent sandbox activity with your product analytics.

## Prerequisites

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ class E2BEndpointConfig:
data_selector="builds",
fanout=TEMPLATE_FANOUT,
),
# GET /templates/{templateID}/tags — the tags of one template and the build each one points at.
# A tag name is unique within a template only. `createdAt` is when the tag was last assigned, so
# moving a tag to a new build changes it, which rules it out as a partition key.
"template_tags": E2BEndpointConfig(
name="template_tags",
path="/templates/{templateID}/tags",
primary_keys=["templateID", "tag"],
page_size_param=None,
fanout=TEMPLATE_FANOUT,
),
# GET /teams/{teamID}/metrics — concurrent sandboxes and sandbox start rate over time.
"team_metrics": E2BEndpointConfig(
name="team_metrics",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,27 @@ def test_fanout_checkpoints_under_its_own_slot_and_resumes_from_it(self) -> None
assert saved.next_token is None
assert saved.fanout is not None and sorted(saved.fanout["completed"]) == ["/templates/t1", "/templates/t2"]

def test_template_tags_carry_their_template_id(self) -> None:
# Tag names repeat across templates ("latest" everywhere), so a row that loses its templateID
# collides with another template's tag on the composite key.
result = _driver().run(
"template_tags",
[
_response([{"templateID": "t1"}, {"templateID": "t2"}]),
_response([{"tag": "latest", "buildID": "b1"}]),
_response([{"tag": "latest", "buildID": "b2"}]),
],
)
assert result.raised is None
assert sorted(result.rows, key=lambda row: row["templateID"]) == [
{"tag": "latest", "buildID": "b1", "templateID": "t1"},
{"tag": "latest", "buildID": "b2", "templateID": "t2"},
]
assert result.paths == ["/v2/templates", "/templates/t1/tags", "/templates/t2/tags"]
# The tags endpoint documents no page-size or cursor param.
assert result.params("limit")[1:] == [None, None]
assert result.params("nextToken")[1:] == [None, None]


class TestLatestSandboxMetrics:
def test_walks_the_whole_sandbox_list_starting_from_a_resumed_cursor(self) -> None:
Expand Down
Loading