Migrating to V2

Migrating to V2

The Doppel V2 API is a drop-in replacement for V1 with a new authentication model. Every V1 endpoint is available at the same path under /v2/ and accepts the same request bodies, query parameters, and response shapes.

In other words: if your integration works against /v1/..., the migration to /v2/... is fundamentally an authentication change.

What changed

V1V2
Auth headersx-api-key (organization-wide), or x-api-key + x-user-api-key (user-specific). See Authentication.Authorization: Bearer <access_token>
Token issuanceStatic keys: organization-wide keys are issued by your Doppel sales representative; user-specific keys are self-service from Doppel Vision API settings. See Authentication.OAuth 2.0 client credentials at POST /oauth/token; access_token expires after expires_in seconds (24 hours at the time of writing)
Org scope headerx-organization-code: <ORG> (optional; per the V1 OpenAPI spec, required only when your user belongs to multiple organizations)Not part of the V2 contract — organization is derived from the org_id claim in the JWT
Path prefix/v1/<resource>/v2/<resource>
Request/response bodiesUnchanged for all non-deprecated endpoints

If your code does not use deprecated endpoints (see below), the only required code changes are: (1) exchange your client credentials for a token, (2) replace the V1 auth headers with Authorization: Bearer ..., (3) drop x-organization-code if you were sending one, (4) change /v1/ to /v2/ in your request URLs.

Deprecated endpoints

The following V1 endpoints are documented as deprecated: true in the V2 OpenAPI spec. They still work in V2, but new integrations should not depend on them.

  • POST /v2/report — use POST /v2/alert instead
  • GET /v2/report — use GET /v2/alert instead
  • PUT /v2/report — use PUT /v2/alert instead
  • GET /v2/reports — use GET /v2/alerts instead

The report resource and the alert resource refer to the same underlying object. The migration is a rename: replace report with alert (and reports with alerts) in the path. Request bodies and response shapes are otherwise identical.

Step 1: Get an OAuth access token

Your Client ID and Client Secret are issued by your Doppel representative and replace the V1 x-api-key and x-user-api-key pair. Exchange them for an access token via POST /oauth/token. See the V2 Authentication guide on the V2 docs site for the full token-exchange flow (including refresh handling).

Step 2: Replace your auth headers

V1 (before)

The example below shows the user-specific V1 mode (x-api-key + x-user-api-key) with an explicit x-organization-code. If you were authenticating with x-api-key alone (organization-wide), simply drop the other two headers from the V1 request.

cURL

curl --request GET \
  --url "https://api.doppel.com/v1/alerts?queue_state=needs_confirmation" \
  --header "x-api-key: <YOUR_ORG_API_KEY>" \
  --header "x-user-api-key: <YOUR_USER_API_KEY>" \
  --header "x-organization-code: ACM"

Python

import requests

response = requests.get(
    "https://api.doppel.com/v1/alerts",
    headers={
        "x-api-key": "<YOUR_ORG_API_KEY>",
        "x-user-api-key": "<YOUR_USER_API_KEY>",
        "x-organization-code": "ACM",
    },
    params={"queue_state": "needs_confirmation"},
)

Node.js

const url = new URL("https://api.doppel.com/v1/alerts");
url.searchParams.set("queue_state", "needs_confirmation");

const response = await fetch(url, {
  headers: {
    "x-api-key": "<YOUR_ORG_API_KEY>",
    "x-user-api-key": "<YOUR_USER_API_KEY>",
    "x-organization-code": "ACM",
  },
});

V2 (after)

cURL

curl --request GET \
  --url "https://api.doppel.com/v2/alerts?queue_state=needs_confirmation" \
  --header "Authorization: Bearer <YOUR_ACCESS_TOKEN>"

Python

import requests

response = requests.get(
    "https://api.doppel.com/v2/alerts",
    headers={"Authorization": f"Bearer {access_token}"},
    params={"queue_state": "needs_confirmation"},
)

Node.js

const url = new URL("https://api.doppel.com/v2/alerts");
url.searchParams.set("queue_state", "needs_confirmation");

const response = await fetch(url, {
  headers: { Authorization: `Bearer ${access_token}` },
});

The query string, response shape, and pagination behavior are identical to V1 — only the auth headers and path prefix change.

Step 3: Handle token expiration

V2 access tokens expire after the expires_in window returned by POST /oauth/token (24 hours at the time of writing). Your client must refresh on 401 Unauthorized, or proactively before expiration. A typical pattern:

import time
import requests

_token = None
_token_expires_at = 0


def get_token() -> str:
    global _token, _token_expires_at
    if _token and time.time() < _token_expires_at - 60:
        return _token
    response = requests.post(
        "https://api.doppel.com/oauth/token",
        json={
            "client_id": CLIENT_ID,
            "client_secret": CLIENT_SECRET,
            "audience": "doppel-external",
            "grant_type": "client_credentials",
        },
    )
    response.raise_for_status()
    data = response.json()
    _token = data["access_token"]
    _token_expires_at = time.time() + data["expires_in"]
    return _token

Migration checklist

  • Obtain Client ID and Client Secret from your Doppel representative.
  • Implement the OAuth token-exchange flow (see the V2 Authentication guide).
  • Replace x-api-key (and, if you were sending it, x-user-api-key) with Authorization: Bearer <token>.
  • Drop the x-organization-code header if you were sending one — V2 derives the organization from the token.
  • Change /v1/ to /v2/ in every request URL.
  • Add token-refresh handling on 401 Unauthorized.
  • If you call /v1/report* or /v1/reports, switch to /v2/alert* or /v2/alerts.
  • Run your existing integration tests against /v2/ to confirm response shapes are unchanged.

Need help?

If you hit a difference between V1 and V2 that is not covered here, contact your Doppel representative — please include the request URL, the V1 vs V2 response, and any HTTP status codes you observed.