Skip to content

API Integration Architecture

Critical: No Direct API Access

IMPORTANT: The frontend application does NOT make direct API calls to the schedule API server. All schedule data access is through the IBM watsonx Orchestrate widget.

Why No Direct API Access?

Architectural Decision

The specification explicitly states:

"There should be no direct integration with the schedule API. Instead, the frontend should provide access to the schedule through the Orchestrate embedded widget."

Benefits of This Architecture

  1. Separation of Concerns
  2. Frontend: User interface and authentication
  3. Orchestrate: Business logic and API integration
  4. API Server: Data access and authorization

  5. Security

  6. No API credentials in frontend code
  7. Token management handled by Orchestrate
  8. Reduced attack surface

  9. Flexibility

  10. API changes don't require frontend updates
  11. Orchestrate can aggregate multiple APIs
  12. Easy to add new data sources

  13. AI-Powered Features

  14. Natural language interface
  15. Intelligent data processing
  16. Contextual responses

Correct Architecture

┌─────────────────────────────────────────────────────────────┐
│                    Frontend Application                      │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │              User Interface                            │ │
│  │                                                         │ │
│  │  - Authentication (NextAuth + Keycloak)                │ │
│  │  - Page routing                                        │ │
│  │  - Orchestrate widget embedding                        │ │
│  │  - Token exchange endpoint                             │ │
│  │                                                         │ │
│  │  ❌ NO direct API calls                                │ │
│  │  ❌ NO fetch() to API server                           │ │
│  │  ❌ NO axios/HTTP client for schedule data             │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                          │ Token Exchange (RFC 8693)
                          │ User JWT passed securely
┌─────────────────────────────────────────────────────────────┐
│              IBM watsonx Orchestrate                         │
│                                                              │
│  ┌────────────────────────────────────────────────────────┐ │
│  │           Watson Assistant Chat Widget                 │ │
│  │                                                         │ │
│  │  - Receives user JWT from frontend                     │ │
│  │  - Makes API calls on behalf of user                   │ │
│  │  - Processes schedule data                             │ │
│  │  - Provides conversational interface                   │ │
│  │                                                         │ │
│  │  ✅ ONLY component that calls API                      │ │
│  └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                          │ HTTP + JWT Bearer Token
                          │ GET /api/v1/schedule
┌─────────────────────────────────────────────────────────────┐
│                    API Server                                │
│              http://localhost:9080                           │
│                                                              │
│  - Validates JWT tokens                                      │
│  - Enforces role-based access control                        │
│  - Returns user schedule data                                │
│  - Knows nothing about Orchestrate                           │
└─────────────────────────────────────────────────────────────┘

What Was Removed

During implementation, the following files were created but then removed because they violated the specification:

❌ Removed: lib/api.ts

This file contained direct API client code:

// ❌ INCORRECT - This was removed
export async function fetchSchedule(accessToken: string) {
  const response = await fetch(`${API_URL}/api/v1/schedule`, {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });
  return response.json();
}

Why removed: Frontend should never call API directly.

❌ Removed: components/ui/SchedulePanel.tsx

This component made direct API calls:

// ❌ INCORRECT - This was removed
useEffect(() => {
  if (session?.accessToken) {
    fetchSchedule(session.accessToken)
      .then(setSchedule)
      .catch(setError);
  }
}, [session]);

Why removed: Violated the "no direct API integration" requirement.

Correct Implementation

✅ Token Exchange Endpoint

The ONLY API-related code in the frontend is the token exchange endpoint:

// ✅ CORRECT - app/api/token-exchange/route.ts
export async function POST(request: NextRequest) {
  const session = await getServerSession(authOptions);

  if (!session || !session.accessToken) {
    return NextResponse.json(
      { error: 'unauthorized' },
      { status: 401 }
    );
  }

  // Return token to Orchestrate (not to browser)
  return NextResponse.json({
    access_token: session.accessToken,
    token_type: 'Bearer',
    expires_in: 3600,
  });
}

Purpose: Securely provide user's JWT to Orchestrate widget.

Note: This endpoint does NOT call the API server. It only returns the token.

✅ Orchestrate Widget Component

The widget handles all schedule data access:

// ✅ CORRECT - components/orchestrate/OrchestrateWidget.tsx
export default function OrchestrateWidget() {
  useEffect(() => {
    // Load Watson Assistant Chat widget
    const script = document.createElement('script');
    script.src = 'https://web-chat.global.assistant.watson.appdomain.cloud/...';

    script.onload = () => {
      window.watsonAssistantChatOptions = {
        integrationID: process.env.NEXT_PUBLIC_ORCHESTRATE_INTEGRATION_ID,
        region: process.env.NEXT_PUBLIC_ORCHESTRATE_REGION,
        serviceInstanceID: process.env.NEXT_PUBLIC_ORCHESTRATE_SERVICE_INSTANCE_ID,
        onLoad: async (instance: any) => {
          // Get token via token exchange
          const tokenResponse = await fetch('/api/token-exchange', {
            method: 'POST',
          });

          const { access_token } = await tokenResponse.json();

          // Pass token to Orchestrate
          instance.updateUserPayload({
            context: {
              skills: {
                'main skill': {
                  user_defined: {
                    jwt_token: access_token,
                  },
                },
              },
            },
          });

          await instance.render();
        },
      };

      window.loadWatsonAssistantChat();
    };

    document.body.appendChild(script);
  }, []);

  return <div id="watson-assistant-chat-container" />;
}

Key points: - Widget loads from Orchestrate - Token passed to widget, not used by frontend - Widget makes API calls, not frontend

Data Flow

Schedule Data Request Flow

1. User asks widget: "Show my schedule"
2. Widget (Orchestrate) receives request
3. Widget uses JWT token (from token exchange)
4. Widget calls: GET http://localhost:9080/api/v1/schedule
   │  Headers: Authorization: Bearer {JWT}
5. API server validates JWT
6. API server returns schedule data
7. Widget processes and displays data
8. User sees schedule in chat interface

Note: Frontend is NOT involved in steps 3-7.

Environment Variables

❌ Removed Variables

These were removed because they're not needed:

# ❌ REMOVED - Frontend doesn't call API
NEXT_PUBLIC_API_URL=http://localhost:9080

✅ Current Variables

Only Orchestrate configuration is needed:

# ✅ CORRECT - For Orchestrate widget
NEXT_PUBLIC_ORCHESTRATE_INTEGRATION_ID=...
NEXT_PUBLIC_ORCHESTRATE_REGION=us-south
NEXT_PUBLIC_ORCHESTRATE_SERVICE_INSTANCE_ID=...

Common Misconceptions

❌ Misconception 1: "Frontend needs API URL"

Wrong: Frontend never calls API, so doesn't need API URL.

Right: Only Orchestrate needs API URL (configured in Orchestrate console).

❌ Misconception 2: "Need to handle API errors in frontend"

Wrong: Frontend doesn't make API calls, so no API errors to handle.

Right: Widget handles API errors and shows appropriate messages.

❌ Misconception 3: "Need loading states for API calls"

Wrong: Frontend doesn't make API calls, so no loading states needed.

Right: Widget has its own loading indicators.

❌ Misconception 4: "Need to cache schedule data"

Wrong: Frontend doesn't receive schedule data, so nothing to cache.

Right: Widget/Orchestrate handles caching if needed.

Security Implications

Benefits of No Direct API Access

  1. No API Credentials in Frontend
  2. No API URL in client code
  3. No risk of credential exposure
  4. Simpler security model

  5. Reduced Attack Surface

  6. Can't bypass Orchestrate
  7. Can't manipulate API calls
  8. Can't access unauthorized data

  9. Token Isolation

  10. Token only used by Orchestrate
  11. Frontend never sees API responses
  12. Cleaner separation of concerns

Token Exchange Security

The token exchange endpoint is secure because:

  1. Server-side validation: Session checked on server
  2. No token exposure: Token not sent to browser
  3. Same-origin only: CORS prevents external access
  4. No caching: Fresh token on each request

Testing

What to Test

DO test: - Token exchange endpoint works - Widget loads correctly - User can interact with widget - Authentication flow works

DON'T test: - Direct API calls (there are none) - API error handling (widget handles it) - Schedule data parsing (widget does it)

Example Tests

// ✅ CORRECT - Test token exchange
describe('Token Exchange', () => {
  it('returns token for authenticated user', async () => {
    const response = await fetch('/api/token-exchange', {
      method: 'POST',
      headers: { Cookie: 'session=...' },
    });

    expect(response.status).toBe(200);
    const data = await response.json();
    expect(data.access_token).toBeDefined();
  });
});

// ❌ INCORRECT - Don't test direct API calls
describe('Schedule API', () => {
  it('fetches schedule', async () => {
    // This test shouldn't exist!
    const response = await fetch('http://localhost:9080/api/v1/schedule');
    // ...
  });
});

Migration Guide

If you have existing code that makes direct API calls, here's how to migrate:

Before (Incorrect)

// ❌ Direct API call
const schedule = await fetch(`${API_URL}/api/v1/schedule`, {
  headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());

setSchedule(schedule);

After (Correct)

// ✅ Use Orchestrate widget
<OrchestrateWidget />

// Widget handles all API calls
// No schedule state needed in frontend

Summary

Aspect Frontend Orchestrate API Server
Makes API calls ❌ No ✅ Yes N/A
Has API URL ❌ No ✅ Yes N/A
Handles schedule data ❌ No ✅ Yes ✅ Yes
Uses JWT token ✅ Yes (for auth) ✅ Yes (for API) ✅ Yes (validates)
Shows schedule UI ❌ No ✅ Yes ❌ No

Next Steps