// Copyright (c) 2026 Probo Inc . // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. package drivers import ( "context" "encoding/json" "fmt" "net/http" "go.probo.inc/probo/pkg/coredata" ) // IntercomDriver fetches workspace admins from Intercom via Bearer // token-authenticated REST API requests. type IntercomDriver struct { httpClient *http.Client } var _ Driver = (*IntercomDriver)(nil) type intercomAdminsResponse struct { Type string `json:"type"` Admins []struct { Type string `json:"type"` ID string `json:"id"` Name string `json:"name"` Email string `json:"email"` JobTitle string `json:"job_title"` HasInboxSeat bool `json:"has_inbox_seat"` } `json:"admins"` } const ( intercomAdminsEndpoint = "https://api.intercom.io/admins" intercomAPIVersion = "2.11" ) func NewIntercomDriver(httpClient *http.Client) *IntercomDriver { return &IntercomDriver{ httpClient: httpClient, } } func (d *IntercomDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) { resp, err := d.fetchAdmins(ctx) if err != nil { return nil, err } var records []AccountRecord for _, a := range resp.Admins { record := AccountRecord{ Email: a.Email, FullName: a.Name, Roles: intercomRoles(a.HasInboxSeat), JobTitle: a.JobTitle, IsAdmin: false, // Intercom API does not expose admin role information ExternalID: a.ID, MFAStatus: coredata.MFAStatusUnknown, AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown, AccountType: coredata.AccessReviewEntryAccountTypeUser, } if record.Email != "" || record.FullName != "" { records = append(records, record) } } return records, nil } func (d *IntercomDriver) fetchAdmins(ctx context.Context) (*intercomAdminsResponse, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, intercomAdminsEndpoint, nil) if err != nil { return nil, fmt.Errorf("cannot create intercom admins request: %w", err) } req.Header.Set("Accept", "application/json") req.Header.Set("Intercom-Version", intercomAPIVersion) httpResp, err := d.httpClient.Do(req) if err != nil { return nil, fmt.Errorf("cannot execute intercom admins request: %w", err) } defer func() { _ = httpResp.Body.Close() }() if httpResp.StatusCode < 200 || httpResp.StatusCode >= 300 { return nil, fmt.Errorf("cannot fetch intercom admins: unexpected status %d", httpResp.StatusCode) } var resp intercomAdminsResponse if err := json.NewDecoder(httpResp.Body).Decode(&resp); err != nil { return nil, fmt.Errorf("cannot decode intercom admins response: %w", err) } return &resp, nil } // intercomRole returns a role label based on whether the admin has an inbox // seat. The Intercom API does not expose a proper role field, so this is the // best approximation available: users with inbox seats are active agents, // those without are limited/viewer users. func intercomRoles(hasInboxSeat bool) []string { if hasInboxSeat { return []string{"Agent"} } return []string{"Viewer"} }