Skip to main content

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 required to use API v3.0, which only supports application-level (M2M) access tokens. Per-vehicle user tokens are a v2.0 concept and cannot be used with v3.0.
The Vehicles API v2.0 will be deprecated by Q4 of 2026. We recommend migrating to the latest version as soon as possible to ensure continued support and access to new features.

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)

1

Create API credentials in Dashboard

  1. Log in to your Smartcar Dashboard
  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
Store these credentials as environment variables. Never commit them to version control.
2

Continue using per-vehicle user tokens

Keep your existing token refresh and storage logic unchanged. You’ll phase this out in Phase 3.
3

Test API Authentication

Exchange your credentials for an access token:
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"
Access tokens are valid for 1 hour. Cache them and refresh when expired. Build a token refresh loop in Phase 2.
4

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.

Phase 2: Update Integration

1

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:
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');
});
2

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.
3

Query connections by userId

Start querying the connections endpoint with the userId filter:
curl -X GET "https://vehicle.api.smartcar.com/v3/connections?filter[user_id]=a4c82e9f-..." \
  -H "Authorization: Bearer {access_token}" \
  -H "sc-user-id: a4c82e9f-..."
The sc-user-id header tells Smartcar which user’s connections you’re accessing. Always include it.
4

Handle webhooks with user.id

Smartcar webhooks now include user.id in the payload. Use this to route events to the correct user:
{
  "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:
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');
});

Phase 3: Full Migration

1

Switch to API Authentication

Update all API calls to use the access token and sc-user-id header:
// 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;
}
2

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.
Ensure your access token cache and refresh loop are working before removing per-vehicle user token logic.
3

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.

Database Schema Changes

Before (Per-Vehicle User Tokens)

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)

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)
);
You can keep the smartcar_vehicle_id for reference, but you no longer need token storage.

Code Changes: Before & After

// 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;
}

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.
Because per-vehicle user tokens continue to work until the Vehicles API v2.0 is deprecated, you can roll back at any phase before then without losing access to vehicles.

What’s Next