API Integration Guide

This guide explains how Facets web components authenticate, make API calls, access user context, and navigate within the platform.

Authentication Model: Session Cookie Inheritance

Web components embedded in Facets automatically inherit the active session cookie. This means:

  • You do NOT need API keys, tokens, or explicit authentication headers
  • The browser sends the session cookie with every fetch() call automatically
  • You MUST use relative URLs (not absolute) for this to work
// Correct -- relative URL, session cookie sent automatically
const response = await fetch('/cc-ui/v1/audit-logs?size=10');

// WRONG -- absolute URL bypasses cookie inheritance
const response = await fetch('https://mycompany.console.facets.cloud/cc-ui/v1/audit-logs');

// WRONG -- explicit auth header (not needed and may conflict)
const response = await fetch('/cc-ui/v1/audit-logs', {
  headers: { 'Authorization': 'Bearer ...' }
});

Making API Calls

Basic GET Request

async fetchData() {
  try {
    this.setLoading(true);
    this.clearError();

    const response = await fetch('/cc-ui/v1/your-endpoint');

    if (!response.ok) {
      throw new Error(`API request failed with status ${response.status}`);
    }

    const data = await response.json();
    this.data = data.content || data;  // Paginated endpoints use .content
    this.updateUI();
  } catch (error) {
    this.showError(`Failed to load data: ${error.message}`);
    console.error('API error:', error);
  } finally {
    this.setLoading(false);
  }
}

GET with Query Parameters

async fetchWithParams() {
  const params = new URLSearchParams({
    number: this.currentPage.toString(),
    size: this.pageSize.toString(),
  });

  // Add optional filters
  if (this.filters.search) {
    params.append('search', this.filters.search);
  }

  const response = await fetch(`/cc-ui/v1/your-endpoint?${params}`);
  const data = await response.json();

  // Paginated responses include:
  // data.content    - array of items
  // data.totalPages - total number of pages
  // data.number     - current page (0-indexed)
  // data.totalElements - total item count
}

POST Request

async createItem(payload) {
  const response = await fetch('/cc-ui/v1/your-endpoint', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    const error = await response.text();
    throw new Error(`Create failed: ${error}`);
  }

  return response.json();
}

PUT Request

async updateItem(id, payload) {
  const response = await fetch(`/cc-ui/v1/your-endpoint/${id}`, {
    method: 'PUT',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload),
  });

  if (!response.ok) {
    throw new Error(`Update failed: ${response.status}`);
  }

  return response.json();
}

DELETE Request

async deleteItem(id) {
  const response = await fetch(`/cc-ui/v1/your-endpoint/${id}`, {
    method: 'DELETE',
  });

  if (!response.ok) {
    throw new Error(`Delete failed: ${response.status}`);
  }
}

User Context

Facets passes user information to your component as a JSON-encoded user attribute. Parse it in connectedCallback():

connectedCallback() {
  // Parse user data from attribute
  const userAttr = this.getAttribute('user');
  if (userAttr) {
    try {
      this.user = JSON.parse(userAttr);
      // this.user may contain:
      // {
      //   email: "[email protected]",
      //   name: "John Doe",
      //   roles: [...],
      //   ...
      // }
    } catch (e) {
      console.warn('Failed to parse user attribute:', e);
    }
  }

  this.setupEventListeners();
  this.fetchData();
}

Using User Data

// Display current user
renderHeader() {
  const greeting = this.user ? `Hello, ${this.user.name}` : 'Hello';
  this.shadowRoot.getElementById('greeting').textContent = greeting;
}

// Filter data by current user
fetchMyItems() {
  const email = this.user?.email || '';
  return fetch(`/cc-ui/v1/items?createdBy=${encodeURIComponent(email)}`);
}

Contextual Attributes

When registering a web component, you can define contextualAttributes -- a key-value map that Facets passes to your component as HTML attributes:

{
  "contextualAttributes": {
    "theme": "dark",
    "maxItems": "50",
    "defaultProject": "production"
  }
}

Read them in your component:

connectedCallback() {
  this.theme = this.getAttribute('theme') || 'light';
  this.maxItems = parseInt(this.getAttribute('maxItems') || '10', 10);
  this.defaultProject = this.getAttribute('defaultProject');
}

Navigation Bridge

To navigate within the Facets UI from your web component, dispatch a facets-navigate custom event:

navigateTo(route, queryParams = {}) {
  this.dispatchEvent(new CustomEvent('facets-navigate', {
    bubbles: true,
    composed: true,  // Required to cross Shadow DOM boundary
    detail: { route, queryParams }
  }));
}

// Examples:
this.navigateTo('/projects/my-project');
this.navigateTo('/projects/my-project/environments', { tab: 'clusters' });

The composed: true flag is essential -- it allows the event to bubble out of the Shadow DOM and reach the Facets navigation handler.

Common API Endpoints

PurposeMethodPathResponse
Audit logsGET/cc-ui/v1/audit-logsPaginated
List projectsGET/cc-ui/v1/stacksArray
Project detailsGET/cc-ui/v1/{stackName}Object
List environmentsGET/cc-ui/v1/{stackName}/clustersArray
List releasesGET/cc-ui/v1/{stackName}/releasesPaginated
List resourcesGET/cc-ui/v1/{stackName}/resourceTypesArray
Current userGET/cc-ui/v1/users/currentObject
List usersGET/cc-ui/v1/usersArray
Web componentsGET/cc-ui/v1/web-componentsArray
Create web componentPOST/cc-ui/v1/web-componentsObject

For the full API reference, use the Facets Swagger documentation at https://<your-instance>.console.facets.cloud/swagger-ui/index.html.

Error Handling Patterns

HTTP Status Codes

StatusMeaningAction
200SuccessProcess response
401UnauthorizedSession expired -- prompt user to refresh the page
403ForbiddenUser lacks permission -- show a clear message
404Not foundResource doesn't exist -- show empty state
500Server errorShow generic error, log details to console

Recommended Pattern

async safeFetch(url, options = {}) {
  const response = await fetch(url, options);

  if (response.status === 401) {
    this.showError('Session expired. Please refresh the page.');
    return null;
  }

  if (response.status === 403) {
    this.showError('You do not have permission to access this resource.');
    return null;
  }

  if (!response.ok) {
    const text = await response.text().catch(() => '');
    throw new Error(`Request failed (${response.status}): ${text || 'Unknown error'}`);
  }

  return response.json();
}