fetchApiKeys method

Future<ProjectApiKeys> fetchApiKeys({
  1. required String projectRef,
  2. required String accessToken,
})

Fetches the project's API keys via the Management API.

Returns a _ProjectApiKeys containing the anon key and the service role key. The service role key is required to:

  • Populate the SUPABASE_SERVICE_ROLE_KEY Edge Function secret so ai-proxy can read/write protected tables.
  • Write the functions bundle hash into _schema_meta.functions_hash (via a short-lived PostgREST call) so the incremental update detector in SchemaMigrator can surface drift.

The service role key itself is NEVER persisted by the app — it is held in memory for the bootstrap flow and dropped when the setup dialog closes.

Implementation

Future<ProjectApiKeys> fetchApiKeys({
  required String projectRef,
  required String accessToken,
}) async {
  final url = '$_baseUrl/v1/projects/$projectRef/api-keys?reveal=true';

  final Response<dynamic> response;
  try {
    response = await _dio.get<dynamic>(
      url,
      options: Options(
        headers: _authHeaders(accessToken),
        sendTimeout: _timeout,
        receiveTimeout: _timeout,
        validateStatus: (_) => true,
      ),
    );
  } on DioException catch (e) {
    throw ProvisioningException(_dioMessage(e, 'fetching project API keys'));
  }

  final status = response.statusCode ?? 0;
  if (status < 200 || status >= 300) {
    throw ProvisioningException(
      _mapStatus(status, projectRef, response.data),
    );
  }

  final data = response.data;
  if (data is! List) {
    throw ProvisioningException(
      'Unexpected api-keys response shape from Supabase.',
    );
  }

  String? anon;
  String? serviceRole;
  for (final entry in data) {
    if (entry is! Map) continue;
    final name = entry['name'] as String?;
    final key = entry['api_key'] as String?;
    if (key == null || key.isEmpty) continue;
    if (name == 'anon') anon = key;
    if (name == 'service_role') serviceRole = key;
  }

  if (serviceRole == null) {
    throw ProvisioningException(
      'Supabase did not return a service_role key — make sure the access '
      'token has the "secrets:read" scope.',
    );
  }
  return ProjectApiKeys(anonKey: anon ?? '', serviceRoleKey: serviceRole);
}