Unclassified

Platform-to-Frontend Contract

Generated: July 29, 2026
Purpose: Define the clear frontend contract from the Platform


Core Principle

The frontend should not infer architecture, call multiple internal services, or reconstruct tenant state. It should receive a server-authoritative description of:

Who the user is, which workspace they are in, what they can see, what is available, what state it is in, and what actions they may perform.


1. One Platform Boundary

The frontend should call only one base URL:

NEXT_PUBLIC_IW_API_URL=https://api.integratewise.ai

All browser requests should flow through:

Frontend
↓
Platform Gateway
↓
Identity / Configuration / Projection / Integration / Governance
↓
Spine and internal services

The frontend must not call:

  • MCP Pool directly
  • Provider Management MCP directly
  • Nango or Descope directly
  • D1, Neon, KV or R2
  • Provider APIs
  • Hermes
  • Individual Cloudflare Workers

2. The First API Call

After login or workspace change, the frontend should call:

GET /api/v1/workspace/discovery

This should be the frontend's initial source of truth.

Suggested Response

interface WorkspaceDiscoveryResponse {
  contractVersion: string;
  traceId: string;
  generatedAt: string;
  state: "ready" | "partial" | "degraded" | "pending";

  principal: {
    userId: string;
    displayName: string;
    email?: string;
    roles: string[];
    permissions: string[];
  };

  workspace: {
    tenantId: string;
    workspaceId: string;
    spineNetworkId: string;
    name: string;
    environment: "development" | "customer-zero" | "production";
  };

  navigation: NavigationSection[];

  projections: ProjectionDescriptor[];

  connectors: {
    available: number;
    connected: number;
    degraded: number;
    syncRunning: number;
  };

  capabilities: CapabilityDescriptor[];

  approvals: {
    pending: number;
    urgent: number;
  };

  continuity: {
    freshness: string;
    lastHydratedAt?: string;
    contextAvailable: boolean;
  };

  activation: {
    complete: boolean;
    blockers: ActivationBlocker[];
  };

  health: {
    gateway: "healthy" | "degraded" | "unavailable";
    spine: "healthy" | "degraded" | "unavailable";
    integrations: "healthy" | "degraded" | "unavailable";
  };
}

This prevents the frontend from hardcoding:

  • Sidebar entries
  • Role-specific pages
  • Tenant IDs
  • Connector availability
  • Capability visibility
  • Approval availability
  • Activation status

3. What the Platform Must Expose

A. Identity and Workspace Context

The frontend needs a trusted context describing:

interface WorkspaceContext {
  userId: string;
  tenantId: string;
  workspaceId: string;
  spineNetworkId: string;

  roles: string[];
  permissions: string[];

  department?: string;
  industry?: string;

  plan?: string;
  entitlements: string[];
}

The Platform must derive this from the authenticated session.

The browser must never be authoritative for:

tenantId
userId
role
permissions
workspaceId
environment

The frontend may request a workspace switch, but the Platform validates and resolves it.


B. Navigation Contract

The Platform should tell the frontend which modules to render.

interface NavigationItem {
  id: string;
  label: string;
  icon?: string;

  route: string;
  projectionId?: string;

  visibility: "visible" | "hidden" | "disabled";
  access: "read" | "operate" | "admin";

  badge?: {
    value: number;
    severity?: "info" | "warning" | "critical";
  };

  reasonDisabled?: string;
}

interface NavigationSection {
  id: string;
  label: string;
  items: NavigationItem[];
}

Example:

{
  "id": "platform",
  "label": "Platform",
  "items": [
    {
      "id": "integration-manager",
      "label": "Integration Manager",
      "route": "/platform/integrations",
      "visibility": "visible",
      "access": "admin"
    },
    {
      "id": "approvals",
      "label": "Governance",
      "route": "/approvals",
      "visibility": "visible",
      "access": "operate",
      "badge": {
        "value": 6,
        "severity": "warning"
      }
    }
  ]
}

The frontend renders this. It should not independently decide what an admin, manager or member sees.


C. Projection Contract

The Platform should expose views as projection definitions rather than making the frontend understand database tables.

interface ProjectionDescriptor {
  id: string;
  version: string;
  title: string;

  type: "table" | "cards" | "timeline" | "canvas" | "graph" | "inbox" | "queue" | "detail";

  entityTypes: string[];

  endpoint: string;

  capabilities: string[];

  freshness: {
    generatedAt: string;
    sourceUpdatedAt?: string;
    stale: boolean;
  };

  state: "ready" | "partial" | "degraded" | "pending";
}

Fetching a view:

GET /api/v1/projections/inbox
GET /api/v1/projections/approvals
GET /api/v1/projections/account-success

Response:

interface ProjectionResponse<T> {
  contractVersion: string;
  projectionId: string;
  traceId: string;

  state: "ready" | "partial" | "degraded" | "pending";

  generatedAt: string;
  sourceFreshness?: string;

  schema: ProjectionSchema;
  data: T;

  allowedCapabilities: string[];

  provenance: {
    sources: string[];
    evidenceIds?: string[];
  };

  remediation?: {
    code: string;
    message: string;
    action?: string;
  };
}

The frontend should render the projection rather than assemble raw Spine entities itself.


D. Connector Catalog

The Platform must expose a canonical connector catalog.

GET /api/v1/connectors/catalog
interface ConnectorDefinition {
  id: string;
  providerKey: string;

  name: string;
  description: string;
  category: string;

  iconUrl?: string;

  authentication: "oauth2" | "api-key" | "service-account" | "mcp" | "none";

  supportedEntities: string[];
  supportedCapabilities: string[];

  availability: {
    status: "available" | "private-alpha" | "beta" | "unavailable";
    reason?: string;
  };
}

The Customer Zero marketplace can keep its UI, but the actual connector list and status should come from this endpoint.


E. Tenant Connections

The frontend needs a clear connection model.

GET /api/v1/connectors/connections
POST /api/v1/connectors/:provider/connect
GET /api/v1/connectors/connections/:id
DELETE /api/v1/connectors/connections/:id
interface ConnectionSummary {
  connectionId: string;
  provider: string;

  status: "pending" | "connected" | "degraded" | "expired" | "revoked";

  health: "healthy" | "degraded" | "unknown";

  owner: {
    type: "user" | "workspace";
    id: string;
  };

  scopes: string[];

  lastValidatedAt?: string;
  lastUsedAt?: string;

  sync: {
    status: "idle" | "running" | "failed" | "disabled";
    lastCompletedAt?: string;
    nextScheduledAt?: string;
  };

  availableActions: string[];
}

The response must never contain:

  • Access tokens
  • Refresh tokens
  • Client secrets
  • API keys
  • Provider-management credentials
  • Raw authorization references unless required for a trusted server flow

F. OAuth Connection Initiation

The frontend should request a connection:

POST /api/v1/connectors/vercel/connect

Request:

{
  "workspaceId": "ws_customer_zero",
  "returnPath": "/platform/connectors"
}

Response:

{
  "state": "human_consent_required",
  "authorizationUrl": "https://...",
  "connectionAttemptId": "attempt_123",
  "expiresAt": "2026-07-29T06:00:00Z"
}

The frontend redirects the user.

It should not:

  • Generate OAuth state
  • Exchange OAuth codes
  • Store credentials
  • Mark a connection as active
  • Derive tenant identity from callback parameters

G. Sync Contract

The Platform should expose sync independently from connection state.

POST /api/v1/connectors/connections/:id/sync
GET /api/v1/connectors/connections/:id/sync-status
GET /api/v1/connectors/connections/:id/sync-history
interface SyncStatus {
  syncJobId?: string;

  state: "idle" | "queued" | "running" | "completed" | "failed" | "cancelled";

  stage?: "loading" | "normalizing" | "storing" | "projecting" | "reconciling";

  progress?: {
    processed: number;
    total?: number;
    percentage?: number;
  };

  startedAt?: string;
  completedAt?: string;

  error?: {
    code: string;
    message: string;
    retryable: boolean;
  };
}

This gives the marketplace real state instead of showing a fake "connected" status.


H. Capability Contract

The frontend should know which actions are available for the current user and object.

GET /api/v1/capabilities?projectionId=inbox
GET /api/v1/capabilities?entityType=account&entityId=acc_123
interface CapabilityDescriptor {
  id: string;
  name: string;
  description: string;

  mode: "read" | "propose" | "execute";

  availability:
    | "available"
    | "requires-approval"
    | "missing-connection"
    | "not-authorized"
    | "unavailable";

  requiredPermissions: string[];
  requiredConnections: string[];

  governance?: {
    approvalRequired: boolean;
    policyId?: string;
  };

  inputSchema?: Record<string, unknown>;
}

This lets the frontend render:

  • Enabled button
  • Disabled button
  • Connect provider
  • Request approval
  • Execute
  • Explain why unavailable

I. Governance and Approvals

The frontend needs explicit proposal and approval APIs.

GET /api/v1/governance/approvals
GET /api/v1/governance/approvals/:id
POST /api/v1/governance/approvals/:id/approve
POST /api/v1/governance/approvals/:id/reject
interface ApprovalItem {
  id: string;

  title: string;
  description: string;

  proposedCapability: string;

  requestedBy: {
    type: "user" | "twin" | "system";
    id: string;
  };

  target: {
    entityType?: string;
    entityId?: string;
    provider?: string;
  };

  risk: "low" | "medium" | "high" | "critical";

  consequences: string[];

  evidence: {
    id: string;
    label: string;
    source: string;
  }[];

  status: "pending" | "approved" | "rejected" | "expired" | "executing" | "completed" | "failed";

  availableActions: string[];
}

The approval endpoint should return an approval result, not let the frontend call the provider afterward.

Correct flow:

Frontend approves
→ Governance validates
→ Governance issues authority
→ Hermes executes
→ Platform returns execution state

J. Twin Contract

The frontend should not call models directly.

It should call semantic Twin operations:

POST /api/v1/twin/ask
POST /api/v1/twin/assign
GET /api/v1/twin/assignments
GET /api/v1/twin/assignments/:id

Ask request:

interface AskTwinRequest {
  context: {
    projectionId?: string;
    entityRefs?: string[];
    selectedRecordIds?: string[];
  };

  question: string;
}

Assign request:

interface AssignTwinRequest {
  objective: string;

  context: {
    projectionId?: string;
    entityRefs?: string[];
  };

  constraints?: string[];

  desiredOutcome?: string;
}

The Platform resolves the actual permitted context. The frontend only sends references to the active surface.


K. Signals and Attention

For Home and Inbox, expose a common attention contract.

GET /api/v1/signals
GET /api/v1/work/items
GET /api/v1/notifications
interface AttentionItem {
  id: string;

  kind: "task" | "approval" | "signal" | "notification" | "blocked-work" | "sync-error";

  title: string;
  summary?: string;

  severity: "info" | "warning" | "critical";

  status: string;

  dueAt?: string;

  relatedEntities: {
    type: string;
    id: string;
    label: string;
  }[];

  projectionTarget?: {
    route: string;
    projectionId?: string;
  };

  allowedCapabilities: string[];
}

This enables Inbox, My Work, Approvals and Notifications to share a consistent data shape.


4. Common Response Envelope

Every Platform response should have the same operational metadata.

interface PlatformResponse<T> {
  contractVersion: string;
  traceId: string;

  tenantId: string;
  workspaceId: string;

  generatedAt: string;
  freshness?: string;

  state: "ready" | "partial" | "degraded" | "pending";

  data: T;

  errors?: {
    code: string;
    message: string;
    retryable: boolean;
  }[];

  provenance?: {
    sources: string[];
    evidenceIds?: string[];
  };
}

The frontend should never silently replace an error with demo data.

Example:

{
  "contractVersion": "2026-07-29.1",
  "traceId": "tr_01J...",
  "tenantId": "iw-customer-zero",
  "workspaceId": "ws_primary",
  "generatedAt": "2026-07-29T05:20:00Z",
  "state": "degraded",
  "data": {
    "items": []
  },
  "errors": [
    {
      "code": "SALESFORCE_SYNC_UNAVAILABLE",
      "message": "Salesforce data is temporarily unavailable.",
      "retryable": true
    }
  ]
}

5. Frontend Vocabulary

The frontend should understand only these core concepts:

Entity
Projection
Capability
Signal
Context
Twin

Optionally, for operational implementation:

Connection
Approval
Sync

It should not understand internal terms such as:

MCP Pool binding
Cloudflare service binding
Nango connection ID
Descope management app
D1 table
Neon migration
Hermes queue
Provider adapter
Worker name

Those are Platform implementation details.


6. Recommended Frontend SDK Structure

lib/platform/
├── client.ts
├── types.ts
├── discovery.ts
├── projections.ts
├── connectors.ts
├── capabilities.ts
├── governance.ts
├── twin.ts
├── signals.ts
└── hooks/
    ├── use-workspace-discovery.ts
    ├── use-projection.ts
    ├── use-connectors.ts
    ├── use-capabilities.ts
    ├── use-approvals.ts
    └── use-twin-assignment.ts

The frontend should use domain-level calls:

platform.workspace.discover();
platform.projections.get("inbox");
platform.connectors.connect("vercel");
platform.connectors.sync(connectionId);
platform.capabilities.forEntity(entityRef);
platform.governance.approve(approvalId);
platform.twin.ask(input);

Not generic calls like:

fetch("/api/platform/some-worker/some-route");

7. Minimum Platform APIs Needed Now

To make the current Customer Zero frontend real, the immediate minimum is:

EndpointPurpose
GET /api/v1/workspace/discoveryInitial frontend contract
GET /api/v1/connectors/catalogConnector Marketplace
GET /api/v1/connectors/connectionsConnection states
POST /api/v1/connectors/:provider/connectStart authorization
DELETE /api/v1/connectors/connections/:idDisconnect
POST /api/v1/connectors/connections/:id/syncTrigger sync
GET /api/v1/connectors/connections/:id/sync-statusShow progress
GET /api/v1/projections/inboxInbox Workbench
GET /api/v1/projections/approvalsApprovals Workbench
GET /api/v1/capabilitiesAvailable actions
POST /api/v1/governance/approvals/:id/approveGoverned approval
POST /api/v1/governance/approvals/:id/rejectRejection
POST /api/v1/twin/askAsk Your Twin
POST /api/v1/twin/assignAssign Your Twin
GET /api/v1/platform/healthPlatform health

8. Marketplace Onboarding Contract

Partially aligned — the frontend contract is mostly post-onboarding, not the complete marketplace onboarding contract.

The proposed Platform APIs correctly support the Workbench after a marketplace installation has been bound to an IntegrateWise workspace:

  • Workspace discovery
  • Connector catalog
  • Connection status
  • OAuth initiation
  • Sync
  • Projections
  • Capabilities
  • Governance
  • Twin operations

But marketplace onboarding needs an additional layer before /api/v1/workspace/discovery.

Correct Marketplace Onboarding Sequence

Marketplace installation initiated
→ Validate marketplace installation context
→ Authenticate or create IntegrateWise user
→ Select or create IntegrateWise workspace
→ Resolve tenant and Spine Network
→ Review scopes and requested access
→ Reuse existing provider connection when permitted
→ Request provider consent when required
→ Create marketplace installation binding
→ Create connector and Spine connection binding
→ Verify connection health
→ Begin initial hydration
→ Compile initial Workbench projections
→ Redirect user to activated Workbench
→ Frontend calls /api/v1/workspace/discovery

So /workspace/discovery is the first Workbench request, but it is not the first marketplace onboarding request.

Two Separate Platform Contracts

1. Marketplace Acquisition and Installation Contract

This owns the channel-specific onboarding process.

Vercel / ChatGPT / GitHub / Notion / Coda
→ marketplace installation API
→ IntegrateWise identity and workspace selection
→ tenant-bound installation

2. Product Runtime Contract

This is the contract already described:

IntegrateWise Workbench
→ workspace discovery
→ projections
→ connectors
→ capabilities
→ governance
→ Twin

The frontend should not combine these into one generic connector flow.


Marketplace Onboarding APIs Required

A provider-neutral API family would be preferable:

POST /api/v1/marketplace/installations/start
GET /api/v1/marketplace/installations/:attemptId
POST /api/v1/marketplace/installations/:attemptId/workspace
POST /api/v1/marketplace/installations/:attemptId/consent
POST /api/v1/marketplace/installations/:attemptId/activate
GET /api/v1/marketplace/installations/:attemptId/status
POST /api/v1/marketplace/installations/:attemptId/cancel
DELETE /api/v1/marketplace/installations/:installationId

Channel-specific callbacks can remain separate:

GET /api/v1/marketplace/vercel/callback
POST /api/v1/marketplace/vercel/webhooks

GET /api/v1/marketplace/github/callback
POST /api/v1/marketplace/github/webhooks

The channel adapter converts provider-specific payloads into one canonical installation model.


Canonical Installation Model

interface MarketplaceInstallation {
  id: string;
  channel: "vercel" | "chatgpt" | "github" | "notion" | "coda" | "mcp-directory";

  externalInstallationId: string;
  externalAccountId?: string;
  externalTeamId?: string;

  tenantId: string;
  workspaceId: string;
  spineNetworkId: string;

  connectorBindingId?: string;
  spineConnectionId?: string;
  credentialRef?: string;

  grantedScopes: string[];

  status:
    | "initiated"
    | "identity_required"
    | "workspace_required"
    | "consent_required"
    | "binding"
    | "hydrating"
    | "active"
    | "degraded"
    | "revoked"
    | "failed";

  createdAt: string;
  activatedAt?: string;
}

This installation record is different from a normal connector connection.

A connector answers:

"Can this workspace communicate with Vercel?"

A marketplace installation answers:

"Which external Vercel installation is bound to which IntegrateWise tenant, workspace, Spine Network, connector, permissions, and lifecycle?"


Marketplace Installation Start Request

POST /api/v1/marketplace/installations/start
{
  "channel": "vercel",
  "externalInstallationId": "icfg_123",
  "externalTeamId": "team_456",
  "requestedScopes": ["projects:read", "deployments:read"],
  "returnUrl": "https://vercel.com/dashboard/integrations"
}

Response:

{
  "installationAttemptId": "mai_123",
  "nextStep": "identity_required",
  "continueUrl": "https://app.integratewise.ai/marketplace/onboarding/mai_123",
  "expiresAt": "2026-07-29T08:00:00Z"
}

The browser should receive only a resumable installation attempt. It should not receive credentials or authoritative tenant information.


Workspace Selection

After authentication:

POST /api/v1/marketplace/installations/mai_123/workspace
{
  "workspaceId": "ws_customer_zero"
}

The Platform validates:

  • The user belongs to that workspace.
  • The user has installation authority.
  • The workspace belongs to the correct environment.
  • The external installation is not already bound elsewhere.
  • Existing connector connections can be reused.
  • Requested scopes comply with workspace policy.

Consent Model

Marketplace consent and provider OAuth consent are not always the same thing.

Marketplace Consent

The user approves:

  • Binding the Vercel installation to the IW workspace
  • Requested project access
  • Data ingestion scope
  • Available capabilities
  • Retention policy
  • Write/action policy

Provider Consent

The user authorizes Vercel, GitHub, Salesforce, or another provider to issue credentials.

A marketplace may already provide a usable installation credential. In that case, a second OAuth flow should not be created unnecessarily.

The Platform should determine:

Existing approved connection exists
→ reuse it

Marketplace credential is sufficient
→ bind it

Additional provider authorization required
→ request consent

No permitted credential path
→ fail closed

Activation Response

POST /api/v1/marketplace/installations/mai_123/activate
{
  "state": "hydrating",
  "installationId": "mi_789",
  "workspaceId": "ws_customer_zero",
  "connector": {
    "provider": "vercel",
    "connectionId": "conn_456",
    "status": "connected"
  },
  "hydration": {
    "status": "running",
    "stage": "loading"
  },
  "redirectUrl": "https://app.integratewise.ai/workbench"
}

The user can enter the Workbench while hydration continues, provided the frontend clearly renders:

pending
partial
degraded
ready

It must not show seed data as though the installation succeeded.


/workspace/discovery After Marketplace Activation

Once the installation is bound, the existing discovery contract becomes correct.

It should expose marketplace context indirectly:

{
  "state": "partial",
  "workspace": {
    "workspaceId": "ws_customer_zero",
    "spineNetworkId": "spn_123"
  },
  "activation": {
    "complete": false,
    "blockers": [
      {
        "code": "VERCEL_INITIAL_HYDRATION_RUNNING",
        "message": "Vercel project data is still loading."
      }
    ]
  },
  "connectors": {
    "connected": 1,
    "degraded": 0,
    "syncRunning": 1
  },
  "marketplaceInstallations": [
    {
      "channel": "vercel",
      "status": "hydrating"
    }
  ]
}

The frontend does not need Vercel-specific implementation details beyond what is required to render status and remediation.


Frontend Onboarding Screens

The Product repo should render these screens:

1. Welcome / sign in
2. Select or create workspace
3. Review requested access
4. Choose projects or account scope
5. Confirm connection reuse or authorize
6. Activation progress
7. Success / remediation
8. Enter Workbench

The Platform must supply all authoritative data and transitions.

The frontend must not decide:

  • Whether a connection can be reused
  • Which tenant the installation belongs to
  • Whether requested scopes are allowed
  • Whether installation is active
  • Whether OAuth succeeded
  • Whether hydration completed
  • Which capabilities are enabled

Alignment Assessment

ComponentMarketplace onboarding alignment
One typed Gateway clientAligned
Workspace discoveryAligned, but post-installation
Connector catalogAligned
Connector connection APIsAligned as a subordinate step
OAuth initiationAligned, but must be installation-aware
Sync and hydration APIsAligned
Projection APIsAligned after activation
Capability APIsAligned
Governance APIsAligned
Marketplace installation stateMissing
External installation-to-workspace bindingMissing
Workspace selection during installMissing
Scope review and marketplace consentMissing
Connection-reuse decisionMissing
Channel callback abstractionMissing
Lifecycle webhooks and uninstallPartially present
Post-install redirect contractMissing

Final Rule

The Platform must expose business and operational contracts; the frontend must never be required to understand the Platform's internal topology.

The frontend should receive clear answers to:

Who am I?
Which workspace am I in?
What can I see?
What work needs attention?
Which connectors are available?
Which connections are active?
What can I do?
What requires approval?
What is fresh, partial or degraded?
Where should the UI take me next?

That is the contract the Platform now needs to formalize.


Final Answer

The Platform-to-frontend contract is aligned with the operational phase of marketplace onboarding, but it does not yet cover the acquisition and installation phase.

Add a canonical MarketplaceInstallation contract and installation state machine ahead of /workspace/discovery. After activation, the existing discovery, connectors, sync, projections, capabilities, governance, and Twin APIs form the correct frontend boundary.