Breaking changes in Kameleo 4.0

  • Created

Kameleo 4.0 introduces a modernised Local API that’s simpler to use, easier to integrate, and aligned with REST best practices. Most changes were driven by long‑standing community requests and developer feedback collected since the 3.x series. Kameleo’s user base has grown from hobby‑level scrapers to large automation teams. To make day‑to‑day scripting less verbose and to expose advanced fingerprint features in a consistent way, we refactored several core concepts and rationalised a few legacy endpoints.

New terminology: Base Profiles ➜ Fingerprints

All BaseProfile* entities and related endpoints are now called Fingerprint*. “Fingerprint” more accurately describes the bundle of browser and device attributes you select before creating a virtual profile.

Impact on SDK / API calls:

  • GET /base-profiles → GET /fingerprints

  • DTOs and properties such as BaseProfile or BaseProfileId are renamed accordingly.

Android mobile app sunset

Kameleo 4.0 no longer supports launching native mobile profiles on physical Android devices through the Kameleo Mobile App. Instead, emulate mobile fingerprints on desktop via Chroma. Consequently, the launcher field has been removed from the Profile schema.

Fingerprint search by browser version

You can now filter the /fingerprints list by the exact major browser version through the browserVersion parameter, which is ideal when a target site flags mismatched user‑agent versions. The GUI will add the same filter in an upcoming minor release.

Universal language support

Starting in Kameleo 4.0, any Fingerprint can be launched in any of our 120 supported languages, so the language filter has been removed from the /fingerprint API endpoint. Choose a fingerprint, pick your locale, and Kameleo handles all the regional quirks behind the scenes.

HTTP verb corrections

Endpoint Old verb New verb Reason
/profiles/{id}/start GET POST Action causes a state change, so it shouldn’t be idempotent.
/profiles/{id}/duplicate GET POST Consistency with other create‑style operations.
/profiles/{id}/upgrade GET POST Operation alters the internal profile state, so it shouldn’t be idempotent.

Data location & app settings hierarchy

The default location for all data folders (Workspace, Logs, Kernels, etc) has been changed:

  • Windows: %LOCALAPPDATA%\Programs\Kameleo %APPDATA%\Kameleo
  • macOS: ~/Library/Application Support/Kameleo (unchanged)

Starting in Kameleo 4.0, multiple configuration sources are supported, so Kameleo now merges appsettings.json from the executable's directory, and the data folder. For example, on Windows, it reads the config file from both %APPDATA%\Programs\Kameleo\appsettings.json (the default installation directory) and %APPDATA%\Kameleo\appsettings.json (the new data folder).

Unified profile state

PersistenceState has been removed. All status information (running, stopped, upgrading, etc.) is now encoded in a single enum: ProfileLifetimeState.

Create profiles with one‑line calls

Every field in the CreateProfileRequest body is optional. If you omit a property, Kameleo automatically chooses the most realistic value based on the selected fingerprint, proxy, and OS.

Local API Client changes

We ship Client packages to wrap our API to make development easier for you. The above mentioned changes effect the Local API Clients as well. Here we list the most important changes that will help you to understand how to migrate to Kameleo 4.0.0

Create profile

Old code

  • const chromeBaseProfileList = await client.searchBaseProfiles({
        deviceType: 'desktop',
        osFamily: 'windows',
        browserProduct: 'chrome',
        language: 'es-es',
    });
    
    const createProfileRequest = BuilderForCreateProfile
        .forBaseProfile(chromeBaseProfileList[0].id)
        .setName('create profile example')
        .setRecommededDefaults()
        .build();
    const profile = await client.createProfile({
        body: createProfileRequest,
    });
  • var baseProfileList = await client.SearchBaseProfilesAsync("desktop", "windows", "chrome", "es-es");
    
    var createProfileRequest = BuilderForCreateProfile
        .ForBaseProfile(selectedBaseProfile.Id)
        .SetName("create profile example")
        .SetRecommendedDefaults()
        .Build();
    var profile = await client.CreateProfileAsync(createProfileRequest);
  • base_profiles = client.search_base_profiles(
        device_type='desktop',
        browser_product='chrome',
        language='es-es'
    )
    
    create_profile_request = BuilderForCreateProfile \
        .for_base_profile(base_profiles[0].id) \
        .set_name('create profile example') \
        .set_recommeded_defaults()
        .build()
    profile = client.create_profile(body=create_profile_request)

New code

  • const fingerprints = await client.fingerprint.searchFingerprints("desktop", "windows", "chrome", "134");
    
    const createProfileRequest = {
        fingerprintId: fingerprints[0].id,
        name: "create profile example",
        language: "es-es",
    };
    const profile = await client.profile.createProfile(createProfileRequest);
  • var fingerprints = await client.Fingerprint.SearchFingerprintsAsync("desktop", "windows", "chrome", "134");
    var createProfileRequest = new CreateProfileRequest(fingerprints[0])
    {
        Name = "create profile example",
        Language = "es-es",
    };
  • fingerprints = client.fingerprint.search_fingerprints(
        device_type='desktop',
        browser_product='chrome',
        browser_version='134',
    )
    
    create_profile_request = CreateProfileRequest(
        fingerprint_id=fingerprints[0].id,
        language="es-es",
        name='create profile example',)
    profile = client.profile.create_profile(create_profile_request)

Set proxy

Old code

  • var createProfileRequest = BuilderForCreateProfile
        .ForBaseProfile(baseProfileList[0].Id)
        .SetName("start with proxy example")
        .SetRecommendedDefaults()
        .SetProxy("socks5", new Server(proxyHost, proxyPort, proxyUsername, proxyPassword))
        .Build();
    
    var profile = await client.CreateProfileAsync(createProfileRequest);
  • const createProfileRequest = BuilderForCreateProfile
        .forBaseProfile(chromeBaseProfileList[0].id)
        .setName('start with proxy example')
        .setRecommendedDefaults()
        .setProxy(
            'socks5',
            {
                host: process.env.PROXY_HOST || '',
                port: Number(process.env.PROXY_PORT) || 1080,
                id: process.env.PROXY_USERNAME || '',
                secret: process.env.PROXY_PASSWORD || '',
            },
        )
        .build();
    const profile = await client.createProfile({
        body: createProfileRequest,
    });
  • create_profile_request = BuilderForCreateProfile \
        .for_base_profile(base_profiles[0].id) \
        .set_name('start with proxy example') \
        .set_recommended_defaults() \
        .set_proxy('socks5', Server(host=PROXY_HOST, port=PROXY_PORT, id=PROXY_USERNAME, secret=PROXY_PASSWORD)) \
        .build()
    profile = client.create_profile(body=create_profile_request)

New code

  • const createProfileRequest = {
        fingerprintId: fingerprints[0].id,
        name: "start with proxy example",
        proxy: {
            value: "socks5",
            extra: {
                host: "",
                port: 1080,
                id: "",
                secret: "",
            },
        },
    };
    const profile = await client.profile.createProfile(createProfileRequest);
  • var createProfileRequest = new CreateProfileRequest(fingerprints[0].Id)
    {
        Name = "start with proxy example",
        Proxy = new (ProxyConnectionType.Socks5, new Server(proxyHost, proxyPort, proxyUsername, proxyPassword))
    };
    
    var profile = await client.Profile.CreateProfileAsync(createProfileRequest);
  • create_profile_request = CreateProfileRequest(
        fingerprint_id=fingerprints[0].id,
        name='start with proxy example',
        proxy=ProxyChoice(
            value='socks5',
            extra=Server(host=PROXY_HOST, port=PROXY_PORT, id=PROXY_USERNAME, secret=PROXY_PASSWORD)
        ))
    profile = client.profile.create_profile(create_profile_request)

Basic operations

Old code

  • // Start the profile
    await client.startProfile(profile.id);
    
    // Stop the profile
    await client.stopProfile(profile.id);
    
    // Delete original profile
    // Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    await client.deleteProfile(profile.id);
  • // Start the profile
    await client.StartProfileAsync(profile.Id);
    
    // Stop the profile
    await client.StopProfileAsync(profile.Id);
    
    // Delete original profile
    // Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    await client.DeleteProfileAsync(profile.Id);
  • # Start the browser profile
    client.start_profile(profile.id)
    
    # Stop the browser by stopping the Kameleo profile
    client.stop_profile(profile.id)
    
    # Delete original profile
    # Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    client.delete_profile(profile.id)

New code

  • // Start the profile
    await client.profile.startProfile(profile.id);
    
    // Stop the profile
    await client.profile.stopProfile(profile.id);
    
    // Delete original profile
    // Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    await client.profile.deleteProfile(profile.id);
  • // Start the profile
    await client.Profile.StartProfileAsync(profile.Id);
    
    // Stop the profile
    await client.Profile.StopProfileAsync(profile.Id);
    
    // Delete original profile
    // Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    await client.Profile.DeleteProfileAsync(profile.Id);
  • # Start the duplicated browser profile
    client.profile.start_profile(profile.id)
    
    # Stop the browser by stopping the Kameleo profile
    client.profile.stop_profile(profile.id)
    
    # Delete original profile
    # Profiles need to be deleted explicitly becase they are persisted so they are available after restarting Kameleo
    client.profile.delete_profile(profile.id)

Was this article helpful?

0 out of 0 found this helpful