> ## Documentation Index
> Fetch the complete documentation index at: https://smartcar.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from V2 to V3 APIs

> A step-by-step guide for transitioning your integration from per-vehicle user tokens to application-level access tokens.

## Overview

This guide walks through transitioning your existing integration from per-vehicle user tokens to application-level access tokens using API Authentication.

**Important:** This migration is optional. Your existing per-vehicle user tokens continue to work indefinitely. Migrate at your own pace.

## Before You Begin

Ensure you have the following:

* Active Smartcar developer account with dashboard access
* Existing integration using per-vehicle user tokens and Smartcar Connect
* Ability to update your backend database schema
* Development and staging environments ready for testing
* Team familiarity with your current token storage approach

## Migration Overview

The transition follows four steps. You control the pace.

1. **Create API credentials** — Generate your Client ID and Secret in the Dashboard
2. **Capture `user_id` from Connect** — Store the `user_id` returned in the Connect redirect URL
3. **Backfill existing connections** — Use the Connections endpoint to retrieve user IDs for existing connections
4. **Switch to application tokens** — Replace per-vehicle user tokens with your application access token + `sc-user-id` header

## Phase 1: Enable API Authentication (Parallel Operation)

<Steps>
  <Step title="Create API credentials in Dashboard">
    1. Log in to your [Smartcar Dashboard](https://dashboard.smartcar.com)
    2. Navigate to **API Credentials** in your application settings
    3. Click **Create Secret**
    4. Copy the `client_id` and `client_secret` and store them securely

    <Warning>
      Store these credentials as environment variables. Never commit them to version control.
    </Warning>
  </Step>

  <Step title="Continue using per-vehicle user tokens">
    Keep your existing token refresh and storage logic unchanged. You'll phase this out in Phase 3.
  </Step>

  <Step title="Test API Authentication">
    Exchange your credentials for an access token:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://iam.smartcar.com/oauth2/token \
        -H "Content-Type: application/x-www-form-urlencoded" \
        -d "grant_type=client_credentials" \
        -d "client_id=YOUR_CLIENT_ID" \
        -d "client_secret=YOUR_CLIENT_SECRET"
      ```

      ```javascript Node.js theme={null}
      const axios = require('axios');

      async function getAppToken() {
        const response = await axios.post('https://iam.smartcar.com/oauth2/token', {
          grant_type: 'client_credentials',
          client_id: process.env.SMARTCAR_CLIENT_ID,
          client_secret: process.env.SMARTCAR_CLIENT_SECRET
        }, {
          headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
        });

        return response.data.access_token;
      }

      getAppToken().then(token => console.log('Access Token:', token));
      ```

      ```python Python theme={null}
      import os
      import requests

      def get_app_token():
          url = "https://iam.smartcar.com/oauth2/token"
          data = {
              "grant_type": "client_credentials",
              "client_id": os.getenv("SMARTCAR_CLIENT_ID"),
              "client_secret": os.getenv("SMARTCAR_CLIENT_SECRET")
          }
          response = requests.post(url, data=data)
          return response.json()["access_token"]

      token = get_app_token()
      print(f"Access Token: {token}")
      ```
    </CodeGroup>

    <Note>
      Access tokens are valid for 1 hour. Cache them and refresh when expired. Build a token refresh loop in Phase 2.
    </Note>
  </Step>

  <Step title="Run Phase 1 in staging">
    Deploy your API credentials to your staging environment. Make test API calls using the access token. Verify responses match your per-vehicle user token calls.
  </Step>
</Steps>

## Phase 2: Update Integration

<Steps>
  <Step title="Capture userId from Connect redirect">
    When users complete Smartcar Connect, the redirect now includes a `userId` parameter:

    ```
    https://example.com/callback?code=CODE&userId=a4c82e9f-...&state=STATE
    ```

    Update your callback handler to extract and store this userId:

    <CodeGroup>
      ```javascript Node.js theme={null}
      app.get('/callback', (req, res) => {
        const { code, userId, state } = req.query;

        // Verify state token (CSRF protection)
        if (state !== req.session.state) {
          return res.status(400).send('Invalid state');
        }

        // Store userId for later
        req.session.smartcarUserId = userId;

        // Exchange code for per-vehicle user token (Phase 1 & 2)
        const tokenData = await exchangeCodeForToken(code);

        // Store both for now (Phase 1 & 2)
        req.user.smartcarUserId = userId;
        req.user.smartcarToken = tokenData.access_token;
        await req.user.save();

        res.redirect('/dashboard');
      });
      ```

      ```python Python theme={null}
      from flask import request, session
      from urllib.parse import parse_qs

      @app.route('/callback')
      def callback():
          code = request.args.get('code')
          user_id = request.args.get('userId')
          state = request.args.get('state')

          # Verify state token (CSRF protection)
          if state != session.get('state'):
              return 'Invalid state', 400

          # Store userId
          session['smartcar_user_id'] = user_id

          # Exchange code for per-vehicle user token
          token_data = exchange_code_for_token(code)

          # Store both
          current_user.smartcar_user_id = user_id
          current_user.smartcar_token = token_data['access_token']
          db.session.commit()

          return redirect('/dashboard')
      ```
    </CodeGroup>
  </Step>

  <Step title="Store userId ↔ user ID mappings">
    Create a new database column to store the Smartcar userId alongside your internal user ID. See the schema changes section below.
  </Step>

  <Step title="Query connections by userId">
    Start querying the connections endpoint with the userId filter:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X GET "https://vehicle.api.smartcar.com/v3/connections?filter[user_id]=a4c82e9f-..." \
        -H "Authorization: Bearer {access_token}" \
        -H "sc-user-id: a4c82e9f-..."
      ```

      ```javascript Node.js theme={null}
      const axios = require('axios');

      async function getUserConnections(appToken, userId) {
        const response = await axios.get(
          `https://vehicle.api.smartcar.com/v3/connections?filter[user_id]=${userId}`,
          {
            headers: {
              'Authorization': `Bearer ${appToken}`,
              'sc-user-id': userId
            }
          }
        );
        return response.data;
      }
      ```

      ```python Python theme={null}
      import requests

      def get_user_connections(app_token, user_id):
          url = f"https://vehicle.api.smartcar.com/v3/connections?filter[user_id]={user_id}"
          headers = {
              "Authorization": f"Bearer {app_token}",
              "sc-user-id": user_id
          }
          response = requests.get(url, headers=headers)
          return response.json()
      ```
    </CodeGroup>

    <Tip>
      The `sc-user-id` header tells Smartcar which user's connections you're accessing. Always include it.
    </Tip>
  </Step>

  <Step title="Handle webhooks with user.id">
    Smartcar webhooks now include `user.id` in the payload. Use this to route events to the correct user:

    ```json theme={null}
    {
      "eventId": "b9e4d2a6-5f18-43c7-a0b3-8d1f6e9c7a54",
      "eventType": "VEHICLE_STATE",
      "data": {
        "vehicle": {
          "id": "vehicle123",
          "make": "Tesla",
          "model": "Model S",
          "year": 2020,
          "vin": "5YJSA1E26FFP12345"
        },
        "signals": [],
        "user": {
          "id": "a4c82e9f-..."
        }
      },
      "triggers": [],
      "meta": {}
    }
    ```

    Update your webhook handler to extract and use the user ID:

    <CodeGroup>
      ```javascript Node.js theme={null}
      app.post('/webhooks/smartcar', (req, res) => {
        const { data, eventType } = req.body;
        const userId = data.user.id;
        const vehicle = data.vehicle;

        // Route to correct user
        const userRecord = User.findBySmartcarId(userId);

        if (eventType === 'VEHICLE_STATE') {
          handleVehicleStateChange(userRecord, vehicle);
        }

        res.status(200).send('OK');
      });
      ```

      ```python Python theme={null}
      @app.route('/webhooks/smartcar', methods=['POST'])
      def handle_webhook():
          payload = request.json
          data = payload['data']
          user_id = data['user']['id']
          event_type = payload['eventType']
          vehicle = data['vehicle']

          # Route to correct user
          user_record = User.query.filter_by(smartcar_user_id=user_id).first()

          if event_type == 'VEHICLE_STATE':
              handle_vehicle_state_change(user_record, vehicle)

          return 'OK', 200
      ```
    </CodeGroup>
  </Step>
</Steps>

## Phase 3: Full Migration

<Steps>
  <Step title="Switch to API Authentication">
    Update all API calls to use the access token and `sc-user-id` header:

    <CodeGroup>
      ```javascript Node.js theme={null}
      // Before (per-vehicle user token per vehicle)
      async function getVehicleInfo(vehicleId, vehicleToken) {
        const response = await axios.get(
          `https://api.smartcar.com/v2.0/vehicles/${vehicleId}/info`,
          {
            headers: {
              'Authorization': `Bearer ${vehicleToken}`
            }
          }
        );
        return response.data;
      }

      // After (application access token + userId)
      async function getVehicleInfo(vehicleId, appToken, userId) {
        const response = await axios.get(
          `https://vehicle.api.smartcar.com/v3/vehicles/${vehicleId}`,
          {
            headers: {
              'Authorization': `Bearer ${appToken}`,
              'sc-user-id': userId
            }
          }
        );
        return response.data;
      }
      ```

      ```python Python theme={null}
      # Before (per-vehicle user token per vehicle)
      def get_vehicle_info(vehicle_id, vehicle_token):
          headers = {'Authorization': f'Bearer {vehicle_token}'}
          response = requests.get(
              f'https://api.smartcar.com/v2.0/vehicles/{vehicle_id}/info',
              headers=headers
          )
          return response.json()

      # After (application access token + userId)
      def get_vehicle_info(vehicle_id, app_token, user_id):
          headers = {
              'Authorization': f'Bearer {app_token}',
              'sc-user-id': user_id
          }
          response = requests.get(
              f'https://vehicle.api.smartcar.com/v3/vehicles/{vehicle_id}',
              headers=headers
          )
          return response.json()
      ```
    </CodeGroup>
  </Step>

  <Step title="Remove per-vehicle user token refresh logic">
    Delete the code that refreshes per-vehicle user tokens. Application access tokens are re-requested from the token endpoint when expired.

    <Warning>
      Ensure your access token cache and refresh loop are working before removing per-vehicle user token logic.
    </Warning>
  </Step>

  <Step title="Decommission token storage">
    Once all API calls use API Authentication, remove the `vehicle_access_token` column from your database. You only need to store the `smartcar_user_id` per user.
  </Step>
</Steps>

## Database Schema Changes

### Before (Per-Vehicle User Tokens)

```sql theme={null}
CREATE TABLE users (
  id INT PRIMARY KEY,
  email VARCHAR(255),
  name VARCHAR(255),
  created_at TIMESTAMP
);

CREATE TABLE vehicles (
  id INT PRIMARY KEY,
  user_id INT,
  vin VARCHAR(17),
  vehicle_access_token VARCHAR(255),
  token_created_at TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);
```

### After (API Authentication with userId Mapping)

```sql theme={null}
CREATE TABLE users (
  id INT PRIMARY KEY,
  email VARCHAR(255),
  name VARCHAR(255),
  smartcar_user_id VARCHAR(36),
  created_at TIMESTAMP
);

CREATE TABLE vehicles (
  id INT PRIMARY KEY,
  user_id INT,
  vin VARCHAR(17),
  smartcar_vehicle_id VARCHAR(36),
  FOREIGN KEY (user_id) REFERENCES users(id)
);
```

<Info>
  You can keep the `smartcar_vehicle_id` for reference, but you no longer need token storage.
</Info>

## Code Changes: Before & After

<CodeGroup>
  ```javascript Before theme={null}
  // Old approach: Store and refresh per-vehicle tokens
  async function getVehicleData(user) {
    const vehicle = await Vehicle.findOne({ userId: user.id });

    // Check token expiration
    if (isTokenExpired(vehicle.tokenCreatedAt)) {
      const newToken = await refreshVehicleToken(vehicle.refreshToken);
      vehicle.vehicleAccessToken = newToken;
      await vehicle.save();
    }

    // Make API call with vehicle token
    const response = await axios.get(
      `https://vehicle.api.smartcar.com/v3/vehicles/${vehicle.id}/signals`,
      {
        headers: {
          'Authorization': `Bearer ${vehicle.vehicleAccessToken}`
        }
      }
    );

    return response.data;
  }
  ```

  ```javascript After theme={null}
  // New approach: Use application access token + user ID header
  async function getVehicleData(user) {
    const vehicle = await Vehicle.findOne({ userId: user.id });
    const appToken = await getAppToken(); // Cached and refreshed automatically

    // Make API call with application access token
    const response = await axios.get(
      `https://vehicle.api.smartcar.com/v3/vehicles/${vehicle.id}/signals`,
      {
        headers: {
          'Authorization': `Bearer ${appToken}`,
          'sc-user-id': user.smartcarUserId
        }
      }
    );

    return response.data;
  }
  ```
</CodeGroup>

## Testing Your Migration

Use this checklist to validate each phase:

**Phase 1 Validation:**

* [ ] API credentials created and stored securely
* [ ] Access token obtained successfully
* [ ] Staging environment receives access token
* [ ] Test API call with access token returns same data as per-vehicle user token call
* [ ] Both opaque and access token calls work in parallel without conflicts

**Phase 2 Validation:**

* [ ] `userId` captured from Connect redirect
* [ ] `smartcar_user_id` stored in user database
* [ ] Connections query with `userId` filter returns results
* [ ] Webhook handler routes events correctly using `user.id`
* [ ] Old per-vehicle user token calls still work for existing connections

**Phase 3 Validation:**

* [ ] All API calls updated to use access token + `sc-user-id` header
* [ ] Token refresh loop running and caching access tokens
* [ ] Opaque token refresh code removed from codebase
* [ ] Database migration completed (`vehicle_access_token` removed)
* [ ] Production traffic flows through API Authentication
* [ ] No errors in error logs for authentication failures

## Rollback Plan

If you encounter issues, rolling back is simple:

1. **Phase 1 Rollback:** Delete API credentials from the Dashboard. No code changes needed.

2. **Phase 2 Rollback:** Stop consuming `userId` from Connect redirect. Revert webhook handler to previous version. Per-vehicle user tokens still work.

3. **Phase 3 Rollback:** Restore per-vehicle user token refresh logic from version control. Redeploy previous code. Vehicles remain connected.

<Note>
  Because per-vehicle user tokens continue to work indefinitely, you can roll back at any phase without losing access to vehicles.
</Note>

## What's Next

* [Setup Guide](/getting-started/how-to/api-authentication) — Complete implementation steps
* [FAQ](/getting-started/how-to/m2m/faq) — Common questions and answers
* [Webhooks Overview](/integrations/webhooks/overview) — Set up event-driven updates
* [Connections API](/api-reference/list-connections) — Manage vehicle connections
