2026-05-31·5 min read·sota.io Team

EU Data Act Cloud Switching 2026: Parallel Operations, Transition Assistance & Provider Cooperation Architecture

Post #1419 in the sota.io EU Cloud Compliance Series

EU Data Act Cloud Switching Parallel Operations and Transition Assistance Architecture 2026

The EU Data Act cloud switching obligation is not a point-in-time export event — it is an active transition period during which both the outgoing provider and, where technically feasible, the incoming provider have overlapping legal obligations. Article 24 of Regulation (EU) 2023/2854 introduces switching assistance: a structured handover protocol that requires the outgoing provider to actively support continuity of service during the customer's move.

This is the fourth post in our EU-DATA-ACT-CLOUD-SWITCHING-2026 series. The first post covered the switching request API and the 30-day state machine. The second post covered data format standards and interoperability. The third post covered Art.27 prohibitions on obstacles and lock-in patterns. This post covers the transition architecture: how to build a compliant parallel operations system and what switching assistance means in code.


What Art.24 Actually Requires

Article 24 of the EU Data Act requires providers of data processing services to offer switching assistance — a set of support obligations that begins when the customer initiates the switch and continues through the transition period. The provision distinguishes between two categories:

Assistance to the Switching Customer

The outgoing provider must:

Assistance to the Receiving Provider

Where the customer authorises it, the outgoing provider must cooperate directly with the receiving provider. This cooperation obligation is new in EU law — prior data portability rights (e.g., under GDPR Art.20) created no obligation to engage with the receiving party. Under Art.24, the outgoing provider must:

The synchronisation obligation applies only where "technically feasible" — a standard that will be interpreted by NCAs based on the provider's technical capabilities, not self-declaration.


Parallel Operations Architecture

The operational challenge for most SaaS platforms is that customers rarely switch cleanly: they run both platforms simultaneously for days or weeks, updating records on one while the other is being configured. Art.24 implicitly anticipates this by requiring the outgoing provider to communicate changes during the transition window.

The Three-Phase Transition Model

A compliant transition has three phases:

enum SwitchingPhase {
  EXPORT_PREPARATION = 'export_preparation',  // Days 1-5: prepare full data export
  PARALLEL_OPERATION = 'parallel_operation',  // Days 5-30: dual-write + sync
  CUTOVER = 'cutover',                        // Day 30: final export, service end
}

interface TransitionState {
  customerId: string;
  phase: SwitchingPhase;
  switchInitiatedAt: Date;
  exportCompletedAt?: Date;
  parallelStartedAt?: Date;
  cutovertAt?: Date;
  receivingProviderEndpoint?: string;
  receivingProviderAuthorised: boolean;
}

Phase 1: Export Preparation (Days 1-5)

The outgoing provider generates the full data export in the formats requested by the customer. This must be a point-in-time consistent snapshot. For large datasets, multi-part chunked exports are acceptable provided the snapshot timestamp is consistent across chunks.

async function initiateExportPreparation(
  customerId: string,
  requestedFormats: ExportFormat[]
): Promise<ExportJob> {
  const snapshotTimestamp = new Date();
  
  const job = await db.exportJobs.create({
    customerId,
    snapshotAt: snapshotTimestamp,
    formats: requestedFormats,
    status: 'preparing',
    estimatedCompletionAt: addBusinessDays(snapshotTimestamp, 5),
  });

  // All subsequent exports use snapshotTimestamp for consistency
  await queue.enqueue('export-customer-data', {
    jobId: job.id,
    customerId,
    snapshotAt: snapshotTimestamp.toISOString(),
    formats: requestedFormats,
  });

  return job;
}

Phase 2: Parallel Operation (Days 5-30)

This is the most complex phase. The customer is actively using both platforms. The outgoing provider must communicate changes to the receiving provider (if authorised by the customer) so that the receiving platform can maintain a near-current view of the customer's data.

The communication can take two forms:

interface ChangeEvent {
  eventId: string;
  eventType: 'create' | 'update' | 'delete';
  entityType: string;
  entityId: string;
  timestamp: string;
  payload: Record<string, unknown>;
  schemaVersion: string;
}

class TransitionSyncService {
  private receivingWebhookUrl: string;
  private customerId: string;
  private signingKey: string;

  async publishChange(event: ChangeEvent): Promise<void> {
    const signature = await this.signPayload(JSON.stringify(event));
    
    await fetch(this.receivingWebhookUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Dataact-Signature': signature,
        'X-Customer-Id': this.customerId,
        'X-Event-Id': event.eventId,
      },
      body: JSON.stringify(event),
    });
  }

  async publishBatch(events: ChangeEvent[]): Promise<void> {
    // Batch endpoint for high-volume changes during parallel period
    const signature = await this.signPayload(JSON.stringify(events));

    await fetch(`${this.receivingWebhookUrl}/batch`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-Dataact-Signature': signature,
        'X-Customer-Id': this.customerId,
        'X-Batch-Size': String(events.length),
      },
      body: JSON.stringify(events),
    });
  }

  private async signPayload(payload: string): Promise<string> {
    const encoder = new TextEncoder();
    const key = await crypto.subtle.importKey(
      'raw', encoder.encode(this.signingKey),
      { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']
    );
    const sig = await crypto.subtle.sign('HMAC', key, encoder.encode(payload));
    return Buffer.from(sig).toString('base64');
  }
}

Phase 3: Cutover (Day 30)

At cutover, the outgoing provider:

  1. Generates a final delta export covering all changes since the snapshot export
  2. Ceases publishing change events
  3. Begins the service termination sequence (but must not delete data until the retention period expires)
async function executeCutover(customerId: string): Promise<CutoverResult> {
  const transition = await db.transitions.findByCustomer(customerId);
  
  // Final delta export
  const deltaExport = await generateDeltaExport(
    customerId,
    transition.snapshotAt,
    new Date()
  );

  // Stop sync
  await db.transitions.update(customerId, {
    phase: SwitchingPhase.CUTOVER,
    cutovertAt: new Date(),
    syncActive: false,
  });

  // Notify receiving provider of cutover completion
  if (transition.receivingProviderEndpoint) {
    await notifyCutoverComplete(
      transition.receivingProviderEndpoint,
      customerId,
      deltaExport.exportId
    );
  }

  return { success: true, deltaExportId: deltaExport.exportId };
}

Provider Cooperation API: What to Expose

The Art.24 cooperation obligation requires you to provide a machine-readable interface that an authorised receiving provider can call. This is distinct from the customer-facing export API. The provider cooperation interface must support:

1. Transition Authorisation Endpoint

// POST /v1/switching/authorize-receiver
interface ReceiverAuthRequest {
  customerId: string;
  customerAuthToken: string;      // Customer-signed JWT proving consent
  receiverIdentity: {
    name: string;
    euVatNumber?: string;
    contactEmail: string;
    webhookEndpoint: string;      // Where to push change events
    webhookSecret: string;        // For HMAC verification
  };
}

interface ReceiverAuthResponse {
  transitionId: string;
  authorisedAt: string;
  syncStartsAt: string;           // When Phase 2 begins
  exportAvailableAt: string;      // When Phase 1 export is ready
  deltaApiEndpoint: string;       // Pull API endpoint for receiving provider
}

2. Delta Change API

// GET /v1/switching/{transitionId}/changes?since={cursor}&limit={n}
interface DeltaResponse {
  transitionId: string;
  customerId: string;
  cursor: string;             // Opaque pagination cursor
  hasMore: boolean;
  events: ChangeEvent[];
  generatedAt: string;
}

3. Metadata Export Endpoint

The metadata export provides the receiving provider with schema information, integration configurations, and environment metadata — things that are not part of the customer's data but are necessary to replicate the customer's setup.

// GET /v1/switching/{transitionId}/metadata
interface TransitionMetadata {
  schemaVersion: string;
  exportedAt: string;
  dataModels: DataModelDescriptor[];
  integrations: IntegrationConfig[];
  webhookSubscriptions: WebhookDescriptor[];
  customFields: CustomFieldDefinition[];
  permissionSchemas: PermissionSchema[];
}

Compliance Pitfalls in the Parallel Operations Period

Pitfall 1: Failing to Maintain Service Quality During Sync

Some providers reduce API rate limits or disable integrations once a switching request is active. Under Art.27 this is a prohibited obstacle. The switching request must not change the customer's service tier or API access.

// WRONG: Throttling API calls during transition
async function handleApiRequest(customerId: string, req: Request): Promise<Response> {
  const transition = await getTransition(customerId);
  if (transition?.isActive) {
    // DO NOT DO THIS — Art.27 violation
    return rateLimitResponse('API access reduced during transition');
  }
  return processRequest(req);
}

// CORRECT: Service continues unchanged
async function handleApiRequest(customerId: string, req: Request): Promise<Response> {
  // No branch on transition status for service access
  return processRequest(req);
}

Pitfall 2: Incomplete Delta Events

Delta events must cover all entity types that were included in the initial export. If the initial export included invoices, contacts, projects, and files, the delta feed must include events for all four entity types. Selective delta feeds that omit less-used entity types are non-compliant — the receiving provider will receive an inconsistent view of the customer's data.

Pitfall 3: Missing Deletion Events

Customers delete records during the transition period. If your delta feed omits deletion events, the receiving provider will have records that no longer exist at the outgoing provider. After cutover, the customer's data will appear corrupted. Deletion events are mandatory.

interface DeleteEvent extends ChangeEvent {
  eventType: 'delete';
  payload: {
    deletedAt: string;
    deletedBy: 'user' | 'system' | 'api';
    retentionExpiry?: string;   // When outgoing provider will physically delete
  };
}

Pitfall 4: Synchronous Cutover Without Delta Export

Some implementations treat cutover as an atomic event — they terminate the sync and simultaneously hand over the export, with no mechanism for the receiving provider to reconcile changes that occurred during the delivery latency. The correct approach generates a delta export covering the window between the snapshot export timestamp and the cutover event, ensuring no changes are lost.


What NCAs Look for in Transition Audits

National competent authorities enforcing the EU Data Act cloud switching provisions have begun auditing providers on the parallel operations period. The audit typically includes:

Documentation review:

Technical review:

Customer complaint analysis:

Stress test (for large providers):


Integration with Your Existing Compliance Stack

If you have implemented the Art.23 switching request API from the first post in this series, the parallel operations system integrates at the state machine level:

// Extend the switching state machine with Phase 2 transitions
const switchingMachine = createMachine({
  states: {
    idle: {
      on: { SWITCH_REQUESTED: 'export_preparation' }
    },
    export_preparation: {
      on: { EXPORT_COMPLETE: 'parallel_operation' },
      after: { [5 * DAY_MS]: 'export_preparation' }  // SLA reminder
    },
    parallel_operation: {
      entry: ['startDeltaSync', 'notifyReceivingProvider'],
      on: { CUTOVER_REQUESTED: 'cutover' },
      after: { [30 * DAY_MS]: 'cutover' }  // Auto-cutover at deadline
    },
    cutover: {
      entry: ['stopDeltaSync', 'generateFinalDelta', 'notifyCutoverComplete'],
      on: { CUTOVER_COMPLETE: 'completed' }
    },
    completed: {
      type: 'final'
    }
  }
});

Next in This Series

The fifth and final post in this series will be the complete developer toolkit: a checklist covering Art.23-28 compliance, the NCA enforcement timeline by member state, how to estimate your risk exposure, and a reference architecture for a fully compliant switching stack.

Series navigation:

EU-Native Hosting

Ready to move to EU-sovereign infrastructure?

sota.io is a German-hosted PaaS — no CLOUD Act exposure, no US jurisdiction, full GDPR compliance by design. Deploy your first app in minutes.