diff --git a/client.gen.go b/client.gen.go index 9a9f0b6..3f4f78b 100644 --- a/client.gen.go +++ b/client.gen.go @@ -138,6 +138,11 @@ type ClientInterface interface { // UploadAddonAsset request UploadAddonAsset(ctx context.Context, teamName TeamName, addonType AddonType, addonName AddonName, versionName VersionName, reqEditors ...RequestEditorFn) (*http.Response, error) + // CreateEnvZeroHandoffWithBody request with any body + CreateEnvZeroHandoffWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateEnvZeroHandoff(ctx context.Context, body CreateEnvZeroHandoffJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // CQHealthCheck request CQHealthCheck(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -735,6 +740,30 @@ func (c *Client) UploadAddonAsset(ctx context.Context, teamName TeamName, addonT return c.Client.Do(req) } +func (c *Client) CreateEnvZeroHandoffWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEnvZeroHandoffRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateEnvZeroHandoff(ctx context.Context, body CreateEnvZeroHandoffJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEnvZeroHandoffRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) CQHealthCheck(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewCQHealthCheckRequest(c.Server) if err != nil { @@ -3262,6 +3291,46 @@ func NewUploadAddonAssetRequest(server string, teamName TeamName, addonType Addo return req, nil } +// NewCreateEnvZeroHandoffRequest calls the generic CreateEnvZeroHandoff builder with application/json body +func NewCreateEnvZeroHandoffRequest(server string, body CreateEnvZeroHandoffJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateEnvZeroHandoffRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateEnvZeroHandoffRequestWithBody generates requests for CreateEnvZeroHandoff with any type of body +func NewCreateEnvZeroHandoffRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/envzero/handoff") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewCQHealthCheckRequest generates requests for CQHealthCheck func NewCQHealthCheckRequest(server string) (*http.Request, error) { var err error @@ -9114,6 +9183,11 @@ type ClientWithResponsesInterface interface { // UploadAddonAssetWithResponse request UploadAddonAssetWithResponse(ctx context.Context, teamName TeamName, addonType AddonType, addonName AddonName, versionName VersionName, reqEditors ...RequestEditorFn) (*UploadAddonAssetResponse, error) + // CreateEnvZeroHandoffWithBodyWithResponse request with any body + CreateEnvZeroHandoffWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEnvZeroHandoffResponse, error) + + CreateEnvZeroHandoffWithResponse(ctx context.Context, body CreateEnvZeroHandoffJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEnvZeroHandoffResponse, error) + // CQHealthCheckWithResponse request CQHealthCheckWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*CQHealthCheckResponse, error) @@ -9827,6 +9901,33 @@ func (r UploadAddonAssetResponse) StatusCode() int { return 0 } +type CreateEnvZeroHandoffResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *CreateEnvZeroHandoff201Response + JSON400 *BadRequest + JSON401 *RequiresAuthentication + JSON409 *EnvZeroConflictError + JSON429 *TooManyRequests + JSON500 *InternalError +} + +// Status returns HTTPResponse.Status +func (r CreateEnvZeroHandoffResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateEnvZeroHandoffResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type CQHealthCheckResponse struct { Body []byte HTTPResponse *http.Response @@ -12744,6 +12845,23 @@ func (c *ClientWithResponses) UploadAddonAssetWithResponse(ctx context.Context, return ParseUploadAddonAssetResponse(rsp) } +// CreateEnvZeroHandoffWithBodyWithResponse request with arbitrary body returning *CreateEnvZeroHandoffResponse +func (c *ClientWithResponses) CreateEnvZeroHandoffWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEnvZeroHandoffResponse, error) { + rsp, err := c.CreateEnvZeroHandoffWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEnvZeroHandoffResponse(rsp) +} + +func (c *ClientWithResponses) CreateEnvZeroHandoffWithResponse(ctx context.Context, body CreateEnvZeroHandoffJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEnvZeroHandoffResponse, error) { + rsp, err := c.CreateEnvZeroHandoff(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEnvZeroHandoffResponse(rsp) +} + // CQHealthCheckWithResponse request returning *CQHealthCheckResponse func (c *ClientWithResponses) CQHealthCheckWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*CQHealthCheckResponse, error) { rsp, err := c.CQHealthCheck(ctx, reqEditors...) @@ -14659,6 +14777,67 @@ func ParseUploadAddonAssetResponse(rsp *http.Response) (*UploadAddonAssetRespons return response, nil } +// ParseCreateEnvZeroHandoffResponse parses an HTTP response from a CreateEnvZeroHandoffWithResponse call +func ParseCreateEnvZeroHandoffResponse(rsp *http.Response) (*CreateEnvZeroHandoffResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateEnvZeroHandoffResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest CreateEnvZeroHandoff201Response + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest BadRequest + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest RequiresAuthentication + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest EnvZeroConflictError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest TooManyRequests + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest InternalError + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseCQHealthCheckResponse parses an HTTP response from a CQHealthCheckWithResponse call func ParseCQHealthCheckResponse(rsp *http.Response) (*CQHealthCheckResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) diff --git a/models.gen.go b/models.gen.go index 89ea038..2e9b397 100644 --- a/models.gen.go +++ b/models.gen.go @@ -12,9 +12,10 @@ import ( ) const ( - BasicAuthScopes = "basicAuth.Scopes" - BearerAuthScopes = "bearerAuth.Scopes" - CookieAuthScopes = "cookieAuth.Scopes" + BasicAuthScopes = "basicAuth.Scopes" + BearerAuthScopes = "bearerAuth.Scopes" + CookieAuthScopes = "cookieAuth.Scopes" + EnvzeroHandoffAuthScopes = "envzeroHandoffAuth.Scopes" ) // Defines values for APIKeyScope. @@ -69,6 +70,11 @@ const ( EmailTeamInvitationRequestRoleMember EmailTeamInvitationRequestRole = "member" ) +// Defines values for EnvZeroConflictErrorCode. +const ( + EnvZeroConflictErrorCodeEmailExists EnvZeroConflictErrorCode = "email_exists" +) + // Defines values for ManagedDatabaseStatus. const ( ManagedDatabaseStatusExpired ManagedDatabaseStatus = "expired" @@ -623,6 +629,35 @@ type CreateAddonVersionRequest struct { PluginDeps *[]string `json:"plugin_deps,omitempty"` } +// CreateEnvZeroHandoff201Response defines model for CreateEnvZeroHandoff_201_response. +type CreateEnvZeroHandoff201Response struct { + ExpiresAt time.Time `json:"expires_at"` + HandoffId openapi_types.UUID `json:"handoff_id"` + + // SignupUrl Cloud signup URL carrying the handoff capability; env0 redirects the user here. + SignupUrl string `json:"signup_url"` +} + +// CreateEnvZeroHandoffRequest defines model for CreateEnvZeroHandoff_request. +type CreateEnvZeroHandoffRequest struct { + // Email The env0 user's email; prefilled on the signup page. + Email interface{} `json:"email"` + + // EnvzeroApiKeyId env0 API key id for the envzero source plugin, passed to platform when the signup consumes the handoff. + EnvzeroApiKeyId interface{} `json:"envzero_api_key_id"` + + // EnvzeroApiKeySecret env0 API key secret for the envzero source plugin. Held encrypted; never stored in cleartext. + EnvzeroApiKeySecret interface{} `json:"envzero_api_key_secret"` + + // EnvzeroOrgId env0 organization id. One live handoff per org. + EnvzeroOrgId interface{} `json:"envzero_org_id"` + Source interface{} `json:"source"` + + // Staging Provision the CloudQuery Platform tenant on the staging platform instead of production. Must be true when (and only when) the token is signed with the staging credential — the prod and staging credentials are never interchangeable. Rejected when this cloud deployment has no staging platform configured. Defaults to false. + Staging *interface{} `json:"staging,omitempty"` + AdditionalProperties map[string]interface{} `json:"-"` +} + // CreatePlatformDestinationSession201Response defines model for CreatePlatformDestinationSession_201_response. type CreatePlatformDestinationSession201Response struct { // ApiUrl Base URL of the tenant's platform API (e.g. https://acme.us.platform.cloudquery.io). The CLI uses it to reach /external-syncs/* directly — no CQ_PLATFORM_API_URL configuration needed. @@ -663,6 +698,9 @@ type CreatePlatformSignupRequest struct { // Company Company name (free text). Captured for analytics. On the auto-create-team path it is also used as the new Cloud team's `display_name` (the team `name` slug stays the auto-generated subdomain). Company interface{} `json:"company"` + // Handoff Optional env0 handoff capability ("."), carried by the signup_url env0 redirected the user from. When present and valid, the new tenant is tagged source=envzero and the env0 API key from the handoff is attached for platform provisioning. The signup email must match the handoff's email. + Handoff *interface{} `json:"handoff,omitempty"` + // JobTitle User's job title (free text, e.g. "Engineering Manager"). Captured for analytics. Distinct from the Cloud team membership role (admin/member). JobTitle interface{} `json:"job_title"` @@ -796,6 +834,17 @@ type EmailTeamInvitationRequest struct { // EmailTeamInvitationRequestRole defines model for EmailTeamInvitationRequest.Role. type EmailTeamInvitationRequestRole string +// EnvZeroConflictError env0 provisioning conflict +type EnvZeroConflictError struct { + // Code Machine-readable conflict reason. `email_exists` means the email already runs a CloudQuery Platform tenant (or another org's live handoff holds it), so env0 should fall back to its manual connect flow instead of retrying. A Cloud-only account never triggers this — its owner signs in on the signup page and continues the handoff. + Code EnvZeroConflictErrorCode `json:"code"` + Message string `json:"message"` + Status int `json:"status"` +} + +// EnvZeroConflictErrorCode Machine-readable conflict reason. `email_exists` means the email already runs a CloudQuery Platform tenant (or another org's live handoff holds it), so env0 should fall back to its manual connect flow instead of retrying. A Cloud-only account never triggers this — its owner signs in on the signup page and continues the handoff. +type EnvZeroConflictErrorCode string + // FieldError defines model for FieldError. type FieldError struct { Errors *[]string `json:"errors,omitempty"` @@ -2626,6 +2675,9 @@ type UpdateAddonVersionJSONRequestBody = AddonVersionUpdate // CreateAddonVersionJSONRequestBody defines body for CreateAddonVersion for application/json ContentType. type CreateAddonVersionJSONRequestBody = CreateAddonVersionRequest +// CreateEnvZeroHandoffJSONRequestBody defines body for CreateEnvZeroHandoff for application/json ContentType. +type CreateEnvZeroHandoffJSONRequestBody = CreateEnvZeroHandoffRequest + // UpsertPlatformDestinationSecretJSONRequestBody defines body for UpsertPlatformDestinationSecret for application/json ContentType. type UpsertPlatformDestinationSecretJSONRequestBody = UpsertPlatformDestinationSecretRequest @@ -3247,6 +3299,139 @@ func (a ConsumePlatformTenantMagicLinkRequest) MarshalJSON() ([]byte, error) { return json.Marshal(object) } +// Getter for additional properties for CreateEnvZeroHandoffRequest. Returns the specified +// element and whether it was found +func (a CreateEnvZeroHandoffRequest) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for CreateEnvZeroHandoffRequest +func (a *CreateEnvZeroHandoffRequest) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for CreateEnvZeroHandoffRequest to handle AdditionalProperties +func (a *CreateEnvZeroHandoffRequest) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["email"]; found { + err = json.Unmarshal(raw, &a.Email) + if err != nil { + return fmt.Errorf("error reading 'email': %w", err) + } + delete(object, "email") + } + + if raw, found := object["envzero_api_key_id"]; found { + err = json.Unmarshal(raw, &a.EnvzeroApiKeyId) + if err != nil { + return fmt.Errorf("error reading 'envzero_api_key_id': %w", err) + } + delete(object, "envzero_api_key_id") + } + + if raw, found := object["envzero_api_key_secret"]; found { + err = json.Unmarshal(raw, &a.EnvzeroApiKeySecret) + if err != nil { + return fmt.Errorf("error reading 'envzero_api_key_secret': %w", err) + } + delete(object, "envzero_api_key_secret") + } + + if raw, found := object["envzero_org_id"]; found { + err = json.Unmarshal(raw, &a.EnvzeroOrgId) + if err != nil { + return fmt.Errorf("error reading 'envzero_org_id': %w", err) + } + delete(object, "envzero_org_id") + } + + if raw, found := object["source"]; found { + err = json.Unmarshal(raw, &a.Source) + if err != nil { + return fmt.Errorf("error reading 'source': %w", err) + } + delete(object, "source") + } + + if raw, found := object["staging"]; found { + err = json.Unmarshal(raw, &a.Staging) + if err != nil { + return fmt.Errorf("error reading 'staging': %w", err) + } + delete(object, "staging") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for CreateEnvZeroHandoffRequest to handle AdditionalProperties +func (a CreateEnvZeroHandoffRequest) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + object["email"], err = json.Marshal(a.Email) + if err != nil { + return nil, fmt.Errorf("error marshaling 'email': %w", err) + } + + object["envzero_api_key_id"], err = json.Marshal(a.EnvzeroApiKeyId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'envzero_api_key_id': %w", err) + } + + object["envzero_api_key_secret"], err = json.Marshal(a.EnvzeroApiKeySecret) + if err != nil { + return nil, fmt.Errorf("error marshaling 'envzero_api_key_secret': %w", err) + } + + object["envzero_org_id"], err = json.Marshal(a.EnvzeroOrgId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'envzero_org_id': %w", err) + } + + object["source"], err = json.Marshal(a.Source) + if err != nil { + return nil, fmt.Errorf("error marshaling 'source': %w", err) + } + + if a.Staging != nil { + object["staging"], err = json.Marshal(a.Staging) + if err != nil { + return nil, fmt.Errorf("error marshaling 'staging': %w", err) + } + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + // Getter for additional properties for CreatePlatformSignupRequest. Returns the specified // element and whether it was found func (a CreatePlatformSignupRequest) Get(fieldName string) (value interface{}, found bool) { @@ -3280,6 +3465,14 @@ func (a *CreatePlatformSignupRequest) UnmarshalJSON(b []byte) error { delete(object, "company") } + if raw, found := object["handoff"]; found { + err = json.Unmarshal(raw, &a.Handoff) + if err != nil { + return fmt.Errorf("error reading 'handoff': %w", err) + } + delete(object, "handoff") + } + if raw, found := object["job_title"]; found { err = json.Unmarshal(raw, &a.JobTitle) if err != nil { @@ -3328,6 +3521,13 @@ func (a CreatePlatformSignupRequest) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'company': %w", err) } + if a.Handoff != nil { + object["handoff"], err = json.Marshal(a.Handoff) + if err != nil { + return nil, fmt.Errorf("error marshaling 'handoff': %w", err) + } + } + object["job_title"], err = json.Marshal(a.JobTitle) if err != nil { return nil, fmt.Errorf("error marshaling 'job_title': %w", err) diff --git a/spec.json b/spec.json index b058349..343e900 100644 --- a/spec.json +++ b/spec.json @@ -4297,6 +4297,61 @@ "x-internal" : true } }, + "/api/envzero/handoff" : { + "post" : { + "description" : "env0's entry point for the EZ→CQ seamless onboarding flow (ATL-404). env0's backend calls this when an org admin clicks Connect; cloud answers with a signup URL that hands the user to cloud's own signup with the handoff attached. Authenticated with env0's signed `ezhf_` bearer token (audience `envzero-handoff-create`); the token's `org` claim must match `envzero_org_id`.\nOne live handoff per org: a retried call while the previous handoff is live answers 201 with the SAME `handoff_id` and `signup_url` (refreshing the stored env0 API key); after expiry or consumption the handoff is replaced wholesale. An email that already runs a CloudQuery Platform tenant (or is held by another org's live handoff) returns 409 with code `email_exists` and env0 offers its manual connect fallback. An email with only a Cloud account is NOT a conflict: the signup page doubles as login, so the owner signs in and continues the same handoff.\n", + "operationId" : "CreateEnvZeroHandoff", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CreateEnvZeroHandoff_request" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CreateEnvZeroHandoff_201_response" + } + } + }, + "description" : "Handoff created (or replayed while still live — same body either way, since env0 treats any non-201 as a failure).\n" + }, + "400" : { + "$ref" : "#/components/responses/BadRequest" + }, + "401" : { + "$ref" : "#/components/responses/RequiresAuthentication" + }, + "409" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/EnvZeroConflictError" + } + } + }, + "description" : "The email already owns a CloudQuery Platform tenant, or another org's live handoff holds it. A Cloud-only account does not conflict.\n" + }, + "429" : { + "$ref" : "#/components/responses/TooManyRequests" + }, + "500" : { + "$ref" : "#/components/responses/InternalError" + } + }, + "security" : [ { + "envzeroHandoffAuth" : [ ] + } ], + "tags" : [ "platform" ], + "x-internal" : true + } + }, "/teams/{team_name}/platform/tenant/{tenant_id}" : { "get" : { "description" : "Read provisioning status of a Platform tenant owned by the given Cloud team. The authenticated user must be a member of `team_name`. Used by the signup wizard to poll until `status` becomes `active`.\n", @@ -7520,6 +7575,24 @@ "enum" : [ "pending", "created", "active" ], "type" : "string" }, + "EnvZeroConflictError" : { + "additionalProperties" : false, + "description" : "env0 provisioning conflict", + "properties" : { + "message" : { + "type" : "string" + }, + "status" : { + "type" : "integer" + }, + "code" : { + "description" : "Machine-readable conflict reason. `email_exists` means the email already runs a CloudQuery Platform tenant (or another org's live handoff holds it), so env0 should fall back to its manual connect flow instead of retrying. A Cloud-only account never triggers this — its owner signs in on the signup page and continues the handoff.", + "enum" : [ "email_exists" ], + "type" : "string" + } + }, + "required" : [ "code", "message", "status" ] + }, "PlatformTenantSummary" : { "description" : "Summary view of a Platform tenant returned by the self-serve list / status endpoints. Same shape as `POST /platform-signup` and `GET /teams/{team_name}/platform/tenant/{tenant_id}` responses.\n", "properties" : { @@ -8617,6 +8690,11 @@ "maxLength" : 255, "minLength" : 1 }, + "handoff" : { + "description" : "Optional env0 handoff capability (\".\"), carried by the signup_url env0 redirected the user from. When present and valid, the new tenant is tagged source=envzero and the env0 API key from the handoff is attached for platform provisioning. The signup email must match the handoff's email.\n", + "maxLength" : 200, + "minLength" : 1 + }, "team_name" : { "allOf" : [ { "$ref" : "#/components/schemas/TeamName" @@ -8648,6 +8726,56 @@ }, "required" : [ "status", "subdomain", "team_name", "tenant_id" ] }, + "CreateEnvZeroHandoff_request" : { + "additionalProperties" : { }, + "properties" : { + "email" : { + "description" : "The env0 user's email; prefilled on the signup page.", + "format" : "email", + "maxLength" : 250, + "minLength" : 1 + }, + "envzero_org_id" : { + "description" : "env0 organization id. One live handoff per org.", + "maxLength" : 250, + "minLength" : 1 + }, + "envzero_api_key_id" : { + "description" : "env0 API key id for the envzero source plugin, passed to platform when the signup consumes the handoff.\n", + "maxLength" : 512, + "minLength" : 1 + }, + "envzero_api_key_secret" : { + "description" : "env0 API key secret for the envzero source plugin. Held encrypted; never stored in cleartext.\n", + "maxLength" : 4096, + "minLength" : 1 + }, + "source" : { + "enum" : [ "envzero" ] + }, + "staging" : { + "description" : "Provision the CloudQuery Platform tenant on the staging platform instead of production. Must be true when (and only when) the token is signed with the staging credential — the prod and staging credentials are never interchangeable. Rejected when this cloud deployment has no staging platform configured. Defaults to false.\n" + } + }, + "required" : [ "email", "envzero_api_key_id", "envzero_api_key_secret", "envzero_org_id", "source" ] + }, + "CreateEnvZeroHandoff_201_response" : { + "properties" : { + "handoff_id" : { + "format" : "uuid", + "type" : "string" + }, + "signup_url" : { + "description" : "Cloud signup URL carrying the handoff capability; env0 redirects the user here.\n", + "type" : "string" + }, + "expires_at" : { + "format" : "date-time", + "type" : "string" + } + }, + "required" : [ "expires_at", "handoff_id", "signup_url" ] + }, "RequestPlatformTenantMagicLink_201_response" : { "properties" : { "magic_url" : { @@ -8876,6 +9004,11 @@ "cookieAuth" : { "scheme" : "cookie", "type" : "http" + }, + "envzeroHandoffAuth" : { + "description" : "env0's signed ezhf_ handoff token (prefix + base64url(claimsJSON) + \".\" + base64url(HMAC-SHA256)), audience `envzero-handoff-create`. Verified in the handler, not the OpenAPI layer.", + "scheme" : "bearer", + "type" : "http" } } }