Migrating from V1 to V2

6 min read

Complete guide to migrate your integration from the legacy V1 API to the modern V2 API.

Introduction

The V2 API is the recommended version for all new integrations. It offers a cleaner architecture, standardized responses, contextual pricing, and better monitoring capabilities.

The V1 API remains fully available; there is no deprecation date. You can migrate at your own pace, endpoint by endpoint. Both versions coexist and share your workspace credits.

Why migrate?

  • Standard authentication Bearer token header instead of query parameter
  • Structured responses consistent data / error / metadata / quotas envelope
  • Contextual pricing pay less for cached results and stale data
  • Free Check endpoints verify data freshness before spending credits
  • GDPR/CCPA compliance automatic blocklist enforcement with clear 451 status codes
  • Per-key rate limits finer control over API key usage alongside workspace limits
  • Webhook simplification workspace-level URL or per-request override, no dedicated alert endpoints

Migration checklist

Here’s everything you need to do. Each step links to the detailed section below.

  1. Generate a V2 API key (rc_ prefix) from the dashboard
  2. Switch to Bearer header authentication. Authorization: Bearer rc_...
  3. Update endpoint URLs. See the mapping tables
  4. Convert GET to POST with JSON body where applicable
  5. Update response parsing. Access data via response.data instead of root-level fields
  6. Update error handling. Use error.code and error.message instead of title and msg
  7. Update quota tracking. Use the new quotas.workspace and quotas.key structure
  8. Configure a webhook URL at workspace level if using async endpoints
  9. Test your new key with GET /v2/usage (free) to validate connectivity
  10. Migrate endpoint by endpoint. Both versions coexist, so you can move gradually

Tip

Start by migrating your most-used endpoint first. Once you are comfortable with the V2 response format, the remaining endpoints follow the same pattern.

Authentication

V1: Query parameter

V1 authenticates via a query parameter using keys with the sk_ prefix:

V1 Authentication
Bash
curl "https://api.reversecontact.com/v1/enrichment/profile?apikey=sk_your_api_key&linkedin_url=https://social.com/in/example"

V2: Bearer header (mandatory)

V2 uses the standard Authorization header with keys using the rc_ prefix:

V2 Authentication
Bash
curl -X POST https://api.reversecontact.com/v2/fetch/persons \
  -H "Authorization: Bearer rc_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://social.com/in/example"}'

Tip

Generate a V2 API key (rc_ prefix) from the API Keys page in your dashboard. Your existing sk_ keys continue to work with V1 endpoints.

Response format

V1 response

V1 mixes data fields at the root level alongside quota information:

V1 Response
JSON
{
  "success": true,
  "credits_consumed": 2,
  "credits_left": 5479,
  "rate_limit_left": 997,
  "daily_rate_limit_left": 997,
  "minute_rate_limit_left": 48,
  "next_minute_rate_limit_reset": "2025-01-15T09:31:00.000Z",
  "quotas": {
    "credits": { "total": 10000, "used": 4521, "left": 5479 },
    "daily_rate_limit": { "limit": 1000, "used": 3, "left": 997, "next_reset": "..." },
    "minute_rate_limit": { "limit": 50, "used": 2, "left": 48, "next_reset": "..." }
  },
  "metadata": {
    "source": "cache",
    "request_id": "a1b2c3d4-...",
    "executionTimeMs": 1234
  },
  "person": {
    "firstName": "Jane",
    "lastName": "Doe"
  }
}

V2 response

V2 uses a standardized envelope with clear separation between data, errors, metadata, and quotas:

V2 Response
JSON
{
  "success": true,
  "data": {
    "firstName": "Jane",
    "lastName": "Doe"
  },
  "error": null,
  "metadata": {
    "requestId": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
    "executionTimeMs": 1234
  },
  "quotas": {
    "creditsConsumed": 2,
    "workspace": {
      "id": "ws_...",
      "credits": { "total": 10000, "used": 4521, "left": 5479 },
      "minuteRateLimit": { "limit": 50, "used": 2, "left": 48, "nextReset": "..." },
      "dailyLimit": null,
      "hasUnlimitedCredits": false,
      "allowDailyOvercost": false
    },
    "key": {
      "id": "key_...",
      "dailyLimit": null,
      "minuteRateLimit": { "limit": 50, "used": 2, "left": 48, "nextReset": null }
    }
  }
}

Key differences

Aspect V1 V2
Data location Root level (e.g. response.person) Inside response.data
Request ID metadata.request_id (snake_case) metadata.requestId (camelCase)
Data source metadata.source ("cache" or "fresh") Not in body (available in logs)
Quota fields Flat fields at root + nested quotas Structured quotas.workspace + quotas.key
Rate limits Workspace-level only Workspace + per-key limits

Tip

When migrating your response parser, the main change is accessing response.data instead of the root-level data field (e.g. response.person, response.company).

Error handling

V1 error format

404 Not Found
JSON
{
  "success": false,
  "title": "Not Found",
  "msg": "No data found"
}

V2 error format

404 Not Found
JSON
{
  "success": false,
  "data": null,
  "error": {
    "code": "REQUEST_ERROR",
    "message": "Profile not found"
  },
  "metadata": {
    "requestId": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
    "executionTimeMs": 234
  }
}

V2 error codes

Status Description
400 Invalid JSON body or endpoint-specific validation
401 Missing or invalid API key
402 Insufficient credits
403 API key lacks required permission, or endpoint requires a paid plan
404 No result found for your query
422 Schema or validation error (e.g. malformed input, disposable email, missing webhook URL)
429 Rate limit exceeded. Wait and retry after nextReset
451 Data Subject Blocked. Profile removed per GDPR/CCPA request
503 Temporary service issue. Retry with backoff
504 The lookup timed out. Retry with backoff

For the full error reference, see Error handling & retries.

Endpoint mapping

Most V1 endpoints have a V2 equivalent. V2 standardizes all endpoints to POST with JSON body and Bearer header authentication.

Person endpoints

V1 V2 Delivery
GET /v1/enrichment/profile POST /v2/fetch/persons Sync
POST /v2/fetch/persons/live Async
GET /v1/enrichment POST /v2/enrich/persons Sync
POST /v1/enrichment/match POST /v2/enrich/persons Sync
POST /v1/enrichment/persons/search POST /v2/search/persons Sync

Company endpoints

V1 V2 Delivery
GET /v1/enrichment/company POST /v2/fetch/companies Sync
POST /v2/fetch/companies/live Async
GET /v1/enrichment/company/domain POST /v2/enrich/companies Sync
POST /v1/enrichment/companies/search POST /v2/search/companies Sync

Email & contact endpoints

V1 V2 Delivery
GET /v1/enrichment/resolve/email POST /v2/enrich/persons Sync
POST /v1/enrichment/profile-from-email POST /v2/enrich/persons Sync

Job endpoints

V1 V2 Delivery
GET /v1/enrichment/jobs/* Not ported

Utility endpoints

V1 V2 Delivery
GET /v1/workspaces/quotas GET /v2/usage Sync
GET /v1/logs/requests Via Dashboard
GET /v1/logs/reports, POST /v1/logs/report Via Dashboard

Request format changes

Here are two common examples. All other endpoints follow the same pattern; see the full Endpoint mapping above.

Fetch a person profile (live)

Before (V1):

V1
Bash
curl "https://api.reversecontact.com/v1/enrichment/profile?apikey=sk_your_key&linkedin_url=https://social.com/in/janedoe"

After (V2):

V2
Bash
# webhookUrl is optional if a default is configured in Settings > Webhooks
curl -X POST https://api.reversecontact.com/v2/fetch/persons/live \
  -H "Authorization: Bearer rc_your_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://social.com/in/janedoe"}'

Credits & pricing

V2 introduces contextual pricing: the credit cost depends on whether data comes from a live scrape or cache, and whether a result was found or not.

For the full credit breakdown per endpoint, see Rate limits & credits.

What’s new in V2

  • Check endpoints free endpoints to verify data existence and freshness before spending credits.
  • Company Search new company search endpoint (1 credit).
  • GDPR/CCPA blocklist automatic enforcement with 451 status code.
  • Disposable email detection 422 error when a disposable email domain is used.

Rate limiting

Both V1 and V2 enforce minute-level and daily rate limits at the workspace level.

What changes in V2

  • Per-key limits V2 adds rate limits scoped to individual API keys. See API key management.
  • Structured quota response rate limit status is available in every response under quotas.workspace.minuteRateLimit and quotas.key.minuteRateLimit.
  • nextReset field tells you exactly when the current minute window resets (ISO 8601 timestamp).

For the full details, see Rate limits & credits.

Async results

V2 delivers async results in two ways: pull on demand via polling (recommended, no public endpoint required), or push to a webhook URL configured in Settings > Webhooks or per-request via webhookUrl.

See the Polling guide for the recommended path with side-by-side comparison, or Webhooks for push delivery setup.

Previous

Industry values

Next

Enrich profile