BldrService
BldrService in Cloud.
Service bldr.service.v1.BldrService, 23 rpcs.
GetMe(GetMeRequest) -> GetMeResponse
── Identity ──────────────────────────────────────────────────────────── The authenticated user plus the organizations they belong to. On the first call for a self-registered user with no organizations, their personal org is provisioned lazily.
Request: GetMeRequest
message GetMeRequest {
// no fields
}Response: GetMeResponse
message GetMeResponse {
optional User user = 1;
repeated Organization organizations = 2;
}UpdateUser(UpdateUserRequest) -> User
Update the caller's own profile (display name). Mirrored to Clerk.
Request: UpdateUserRequest
message UpdateUserRequest {
optional string display_name = 1;
}Response: User
A user, mirrored from Clerk.
Deletion is a mark, never a row removal: a deleted user keeps its id so everything that ever referenced it still resolves, and a Clerk id is never handed out twice. Deleted users are absent from every response here.
message User {
optional string id = 1;
optional string email = 2;
optional string display_name = 3;
}| Field | |
|---|---|
id | Clerk user id (the token sub) |
email | |
display_name |
DeleteUser(DeleteUserRequest) -> DeleteUserResponse
Delete the caller's own account: removed from Clerk, and marked deleted here. A caller can only delete themselves, so there is no id to pass. Orgs the caller merely belongs to survive; their personal org is marked deleted with them, and a shared org they solely own is refused — hand it over or delete it first, so an organization is never orphaned by one departure.
Request: DeleteUserRequest
message DeleteUserRequest {
// no fields
}Response: DeleteUserResponse
message DeleteUserResponse {
// no fields
}ListOrganizations(ListOrganizationsRequest) -> ListOrganizationsResponse
── Organizations ────────────────────────────────────────────────────── The organizations the caller is a member of.
Request: ListOrganizationsRequest
message ListOrganizationsRequest {
// no fields
}Response: ListOrganizationsResponse
message ListOrganizationsResponse {
repeated Organization organizations = 1;
}CreateOrganization(CreateOrganizationRequest) -> Organization
Create a new (shared) organization owned by the caller.
Request: CreateOrganizationRequest
message CreateOrganizationRequest {
optional string name = 1;
}Response: Organization
An organization the caller belongs to, with the caller's role/permissions in it (from the Clerk session).
Exactly one of a caller's organizations is personal. It is created for every
user on their first authenticated call — there is no RPC that makes one,
because there is no state in which a user should not have one — and it is
theirs alone: no other member can be added, its name cannot be changed, and
it cannot be deleted. It goes when the account goes.
message Organization {
optional string id = 1;
optional string name = 2;
optional bool personal = 4;
optional string role = 5;
repeated string permissions = 6;
}| Field | |
|---|---|
id | Clerk org id, or personal:<user_id> |
name | |
personal | the member's own personal org |
role | caller's Clerk org role |
permissions | caller's Clerk permissions in this org |
GetOrganization(GetOrganizationRequest) -> OrganizationDetail
An organization with its members. Caller must be a member.
Request: GetOrganizationRequest
message GetOrganizationRequest {
optional string org_id = 1;
}Response: OrganizationDetail
An organization plus its members.
message OrganizationDetail {
optional Organization organization = 1;
repeated Member members = 2;
}UpdateOrganization(UpdateOrganizationRequest) -> Organization
Rename an organization. Requires org:sys_profile:manage in the active org,
and is refused outright on a personal organization, whose name is fixed.
Request: UpdateOrganizationRequest
message UpdateOrganizationRequest {
optional string org_id = 1;
optional string name = 2;
}Response: Organization
The same message as Organization.
DeleteOrganization(DeleteOrganizationRequest) -> DeleteOrganizationResponse
Delete an organization. Requires org:sys_profile:manage; personal orgs
cannot be deleted this way.
Request: DeleteOrganizationRequest
message DeleteOrganizationRequest {
optional string org_id = 1;
}Response: DeleteOrganizationResponse
message DeleteOrganizationResponse {
// no fields
}ListWorkspaces(ListWorkspacesRequest) -> ListWorkspacesResponse
── Workspaces ────────────────────────────────────────────────────────── The workspaces of an organization. Caller must be a member.
Request: ListWorkspacesRequest
message ListWorkspacesRequest {
optional string org_id = 1;
}Response: ListWorkspacesResponse
message ListWorkspacesResponse {
repeated Workspace workspaces = 1;
}GetWorkspace(GetWorkspaceRequest) -> WorkspaceDetail
One workspace, addressed by its slug within the organization, with its revisions. Caller must be a member.
Request: GetWorkspaceRequest
message GetWorkspaceRequest {
optional string org_id = 1;
optional string slug = 2;
}Response: WorkspaceDetail
A workspace plus its revisions, newest first.
message WorkspaceDetail {
optional Workspace workspace = 1;
repeated WorkspaceRevision revisions = 2;
}CreateWorkspace(CreateWorkspaceRequest) -> Workspace
Create a workspace. Its slug is its identity and cannot be changed afterwards, so this is the only call that sets one. Creating it also creates revision 1, which becomes the current revision — a workspace is never without one.
Request: CreateWorkspaceRequest
message CreateWorkspaceRequest {
optional string org_id = 1;
optional string slug = 2;
}| Field | |
|---|---|
org_id | |
slug | Lowercase letters, digits and hyphens; must start and end alphanumeric. Rejected rather than rewritten: a slug the caller did not choose is a surprise in every URL that follows. |
Response: Workspace
A workspace: a named thing an organization owns, with a history of revisions.
The slug is the identity, not a display label beside one. It is unique within the owning organization, it is what a URL and an API call address the workspace by, and it is fixed at creation — renaming would break every reference to it, and a mutable second name is the thing organizations just stopped having.
message Workspace {
optional string slug = 1;
optional string org_id = 2;
optional uint32 current_revision = 3;
optional int64 created_at_unix = 4;
optional uint32 revision_count = 5;
}| Field | |
|---|---|
slug | unique within the org; immutable |
org_id | |
current_revision | The revision currently in effect. Always >= 1: creating a workspace creates revision 1, and exactly one revision is current at any moment. |
created_at_unix | |
revision_count |
CreateBuild(CreateBuildRequest) -> Build
── Builds ────────────────────────────────────────────────────────────── Start a build of a workspace. Caller must be a member.
Always accepted. A regular build then waits its turn: any number may be queued for a workspace and at most one runs at a time, because each commits the next revision and revision N+1 is defined in terms of N. A dry run runs the same build and commits no revision, so it is ordered against nothing and never waits.
Request: CreateBuildRequest
message CreateBuildRequest {
optional string org_id = 1;
optional string workspace_slug = 2;
optional BuildKind kind = 3;
optional BuildDefinition definition = 4;
}Response: Build
A build of a workspace.
message Build {
optional uint32 number = 1;
optional string workspace_slug = 2;
optional string org_id = 3;
optional BuildKind kind = 4;
optional BuildState state = 5;
optional BuildDefinition definition = 6;
optional uint32 parent_revision = 7;
optional uint32 produced_revision = 8;
optional string created_by = 9;
optional int64 created_at_unix = 10;
optional int64 started_at_unix = 11;
optional int64 finished_at_unix = 12;
optional uint32 queue_position = 13;
optional string message = 14;
}| Field | |
|---|---|
number | Sequential within the workspace, from 1. |
workspace_slug | |
org_id | |
kind | |
state | |
definition | |
parent_revision | The workspace revision this build runs against — its parent snapshot. |
produced_revision | The revision this build committed, or 0 — the one thing that distinguishes the two kinds. Only a successful regular build sets it. |
created_by | |
created_at_unix | |
started_at_unix | |
finished_at_unix | |
queue_position | How many regular builds are ahead of this one — queued or running. 0 unless this build is itself QUEUED. |
message | Why it failed or was cancelled; empty otherwise. |
ListBuilds(ListBuildsRequest) -> ListBuildsResponse
A workspace's builds, newest first.
Request: ListBuildsRequest
message ListBuildsRequest {
optional string org_id = 1;
optional string workspace_slug = 2;
}Response: ListBuildsResponse
message ListBuildsResponse {
repeated Build builds = 1;
}GetBuild(GetBuildRequest) -> Build
One build by its number within the workspace.
Request: GetBuildRequest
message GetBuildRequest {
optional string org_id = 1;
optional string workspace_slug = 2;
optional uint32 number = 3;
}Response: Build
The same message as Build.
CancelBuild(CancelBuildRequest) -> Build
Cancel a build that has not finished. A queued build is dropped; a running one is asked to stop.
Request: CancelBuildRequest
message CancelBuildRequest {
optional string org_id = 1;
optional string workspace_slug = 2;
optional uint32 number = 3;
}Response: Build
The same message as Build.
ListMembers(ListMembersRequest) -> ListMembersResponse
── Membership management ──────────────────────────────────────────────── Members of an organization. Caller must be a member.
Request: ListMembersRequest
message ListMembersRequest {
optional string org_id = 1;
}Response: ListMembersResponse
message ListMembersResponse {
repeated Member members = 1;
}InviteMember(InviteMemberRequest) -> Invitation
Invite a user (by email) to the active org. Requires
org:sys_memberships:manage. Returns the pending invitation.
Request: InviteMemberRequest
message InviteMemberRequest {
optional string org_id = 1;
optional string email = 2;
optional string role = 3;
}| Field | |
|---|---|
org_id | |
email | |
role | e.g. "org:member" / "org:admin" |
Response: Invitation
A pending invitation to join an organization.
message Invitation {
optional string id = 1;
optional string email = 2;
optional string role = 3;
optional string status = 4;
}| Field | |
|---|---|
id | |
email | |
role | |
status | e.g. "pending" |
UpdateMemberRole(UpdateMemberRoleRequest) -> Member
Change a member's role. Requires org:sys_memberships:manage, and is
refused when it would demote the organization's last admin — including the
caller demoting themselves, which is the only way to reach that state.
Request: UpdateMemberRoleRequest
message UpdateMemberRoleRequest {
optional string org_id = 1;
optional string user_id = 2;
optional string role = 3;
}Response: Member
A member of an organization.
message Member {
optional string user_id = 1;
optional string email = 2;
optional string display_name = 3;
optional string role = 4;
repeated string permissions = 5;
}RemoveMember(RemoveMemberRequest) -> RemoveMemberResponse
Remove another member from the org. Requires
org:sys_memberships:manage. To remove yourself, use LeaveOrganization —
leaving has rules of its own that removing someone else does not.
Request: RemoveMemberRequest
message RemoveMemberRequest {
optional string org_id = 1;
optional string user_id = 2;
}Response: RemoveMemberResponse
message RemoveMemberResponse {
// no fields
}LeaveOrganization(LeaveOrganizationRequest) -> LeaveOrganizationResponse
Leave an organization. Needs no permission — membership is the caller's to give up — but it is refused in two cases, because both would leave an organization nobody can run:
- the caller is its last member. There would be nothing left to belong to it, so the organization is to be deleted instead (DeleteOrganization), which is a different decision and a different button;
- the caller is its last admin while other members remain. Someone else has to be promoted first.
A personal organization cannot be left at all: it is the caller, and it goes when the account does.
Request: LeaveOrganizationRequest
message LeaveOrganizationRequest {
optional string org_id = 1;
}Response: LeaveOrganizationResponse
message LeaveOrganizationResponse {
// no fields
}ApproveCliLogin(ApproveCliLoginRequest) -> ApproveCliLoginResponse
Approve a pending bldr login for one of the caller's organizations, and
return the one-time authorization code the CLI exchanges at
CliOAuth.Token. The code lives for ten minutes and redeems once.
Request: ApproveCliLoginRequest
The authorization request the CLI put in the console URL, plus what the person chose on the consent page.
message ApproveCliLoginRequest {
optional string client_id = 1;
optional string redirect_uri = 2;
optional string code_challenge = 3;
optional string code_challenge_method = 4;
optional string org_id = 5;
optional string name = 6;
}| Field | |
|---|---|
client_id | The CLI's OAuth parameters, passed through from the URL unchanged. |
redirect_uri | A loopback http://127.0.0.1:<port>/… (or localhost, [::1]) the CLI listens on, or urn:ietf:wg:oauth:2.0:oob when the CLI has no browser and the console shows the code for pasting. |
code_challenge | |
code_challenge_method | "S256"; nothing else is accepted |
org_id | The organization the CLI will act in. The caller must be a member. |
name | A label for the settings pages, from the CLI. Trimmed and capped. |
Response: ApproveCliLoginResponse
message ApproveCliLoginResponse {
optional string code = 1;
}| Field | |
|---|---|
code | The one-time authorization code. The console appends it to a loopback redirect_uri, or shows it for pasting. |
ListCliAuthorizations(ListCliAuthorizationsRequest) -> ListCliAuthorizationsResponse
Live CLI authorizations. With no org_id, the caller's own, across every
organization. With one, the authorizations in that organization: all of
them for a member holding org:sys_memberships:manage, the caller's own
otherwise.
Request: ListCliAuthorizationsRequest
message ListCliAuthorizationsRequest {
optional string org_id = 1;
}| Field | |
|---|---|
org_id | empty: the caller's own, across organizations |
Response: ListCliAuthorizationsResponse
message ListCliAuthorizationsResponse {
repeated CliAuthorization authorizations = 1;
}| Field | |
|---|---|
authorizations | newest first |
RevokeCliAuthorization(RevokeCliAuthorizationRequest) -> RevokeCliAuthorizationResponse
Revoke a CLI authorization. Its holder may revoke it, and so may anyone
holding org:sys_memberships:manage in the organization it is bound to.
Request: RevokeCliAuthorizationRequest
message RevokeCliAuthorizationRequest {
optional string id = 1;
}Response: RevokeCliAuthorizationResponse
message RevokeCliAuthorizationResponse {
// no fields
}Types used above
WorkspaceRevision
One revision of a workspace. Numbered from 1, sequentially, per workspace.
message WorkspaceRevision {
optional uint32 number = 1;
optional int64 created_at_unix = 2;
optional string created_by = 3;
optional bool current = 4;
}| Field | |
|---|---|
number | |
created_at_unix | |
created_by | user id |
current |
BuildDefinition
What a build builds: the same thing a bldr-workspace.yml says, applied on
top of the workspace's current revision.
This is a diff, exactly as the open-source WorkspaceConfig is: the
workspace's current revision is the parent snapshot, and these members and
options override it. removed_members is this API's spelling of that file's
~ tombstone — proto3 maps cannot carry a null, so removal is a separate
list rather than an absent value.
message BuildDefinition {
optional map<string, BuildMember> members = 1;
optional map<string, string> options = 2;
repeated string removed_members = 3;
repeated string args = 4;
}| Field | |
|---|---|
members | |
options | Free-form build options, as in the workspace file's options:. The reserved key build_controller selects the build controller. |
removed_members | Members of the parent revision to drop from this build. |
args | Build arguments, passed through to the controller. |
BuildMember
One member of the build's workspace definition.
message BuildMember {
optional BuildMemberSource source = 1;
optional map<string, string> options = 2;
}| Field | |
|---|---|
source | |
options | Per-member options carried into the build input. |
BuildMemberSource
Where one workspace member's content comes from.
This mirrors the open-source MemberSource — the same shapes a
bldr-workspace.yml can write — with one omission: there is no path. A path
source means "watch this directory in the working copy", and a build started
through this API has no working copy to watch. Everything else is here.
message BuildMemberSource {
oneof source {
string dag = 1;
string inline_yaml = 2;
ProviderSource provider = 3;
}
}| Field | |
|---|---|
dag | Content already addressed by a CID. |
inline_yaml | A YAML document stored as a single IPLD block. |
provider | Delegate to the node's registered content-provider/<kind> — git, container-registry, … — with free-form settings. |
ProviderSource
message ProviderSource {
optional string kind = 1;
optional map<string, string> settings = 2;
}BuildKind
What a build does with the workspace it runs against.
enum BuildKind {
BUILD_KIND_UNSPECIFIED = 0;
BUILD_KIND_REGULAR = 1;
BUILD_KIND_DRY_RUN = 2;
}| Value | |
|---|---|
BUILD_KIND_UNSPECIFIED | |
BUILD_KIND_REGULAR | On success, the snapshot this build resolved becomes the workspace's next revision. That is what makes these queue: revision N+1 is defined as a diff on N, so two of them cannot run at once — though any number may wait. |
BUILD_KIND_DRY_RUN | The same build, without the revision. It runs exactly as a regular build does — same definition, same members, same targets, same artifacts — and then commits nothing: the workspace's current revision does not move. |
BuildState
enum BuildState {
BUILD_STATE_UNSPECIFIED = 0;
BUILD_STATE_QUEUED = 1;
BUILD_STATE_RUNNING = 2;
BUILD_STATE_SUCCEEDED = 3;
BUILD_STATE_FAILED = 4;
BUILD_STATE_CANCELLED = 5;
}| Value | |
|---|---|
BUILD_STATE_UNSPECIFIED | |
BUILD_STATE_QUEUED | Waiting for the workspace's running build to finish. Regular builds only. |
BUILD_STATE_RUNNING | |
BUILD_STATE_SUCCEEDED | |
BUILD_STATE_FAILED | |
BUILD_STATE_CANCELLED |