API Authentication¶
Secure access to the OpenProspect API using API keys for external integrations or OAuth 2.0 for internal tools.
Which Authentication Method Should I Use?¶
| Use Case | Method | Best For |
|---|---|---|
| Server-to-server integration | API Keys | External developers, automation, production systems |
| Web application (internal) | OAuth 2.0 + PKCE | Internal tools, admin dashboards |
| CI/CD pipelines | API Keys | Automated workflows, deployment scripts |
For external developers: Use API Keys — they're simpler, more secure for server-to-server use, and don't require user interaction.
Quick Start: API Keys¶
1. Get Your API Key¶
Create an API key from the OpenProspect dashboard:
- Log in to app.openprospect.io
- Go to Settings > Developer > API Keys
- Click Create API Key
- Choose a preset (Orders, Read Only, Full Access, or Custom), set the Rate Limit Tier and Expiration, then save the key securely
Important: The full key is shown only once at creation time. Copy it immediately and store it securely.
Key Format:
- Production:
lnc_live_...(32-character base62) - Testing:
lnc_test_...(32-character base62)
The prefix prevents accidental use of test keys in production.
2. Store Your API Key Securely¶
# Environment variable (recommended)
export OPENPROSPECT_API_KEY="lnc_live_A3mK9pX2vL8hQ5nR7zT1wY4jB6cE0dF"
# Or use a .env file (never commit to git!)
echo "OPENPROSPECT_API_KEY=lnc_live_..." >> .env
3. Make Your First API Call¶
import os
import httpx
api_key = os.getenv("OPENPROSPECT_API_KEY")
response = httpx.get(
"https://api.openprospect.io/api/v1/orders",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
},
params={"limit": 10}
)
orders = response.json()
print(f"Found {len(orders['orders'])} orders")
const apiKey = process.env.OPENPROSPECT_API_KEY;
const response = await fetch(
"https://api.openprospect.io/api/v1/orders?limit=10",
{
method: "GET",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
}
}
);
const orders = await response.json();
console.log(`Found ${orders.orders.length} orders`);
const apiKey = process.env.OPENPROSPECT_API_KEY!;
interface OrderListResponse {
orders: Array<{ id: string; title: string; status: string }>;
total: number;
}
const response = await fetch(
"https://api.openprospect.io/api/v1/orders?limit=10",
{
method: "GET",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
}
}
);
const orders: OrderListResponse = await response.json();
console.log(`Found ${orders.orders.length} orders`);
using System.Net.Http.Headers;
var apiKey = Environment.GetEnvironmentVariable("OPENPROSPECT_API_KEY");
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var response = await client.GetAsync(
"https://api.openprospect.io/api/v1/orders?limit=10");
var orders = await response.Content.ReadFromJsonAsync<OrderListResponse>();
Console.WriteLine($"Found {orders.Orders.Count} orders");
API Key Management¶
Revoking API Keys¶
Immediately deactivate a compromised or unused API key:
curl -X DELETE "https://api.openprospect.io/api/v1/api-keys/${KEY_ID}" \
-H "Authorization: Bearer ${JWT_TOKEN}"
Note: API key revocation requires JWT authentication with
admin:writescope. Keys are managed via the dashboard or admin API.
Effect is immediate — the key stops working instantly.
Rotation¶
API keys are long-lived credentials. To rotate:
- Create a new key with the same scopes via the dashboard
- Update your systems to use the new key
- Verify the new key works
- Revoke the old key
When to rotate:
- Every 90 days (recommended minimum)
- Immediately if compromised
- When team members with access leave
- Before decommissioning old systems
Scopes and Permissions¶
API keys use scope-based permissions for fine-grained access control.
Available Scopes¶
| Scope | Description | Typical Use Case |
|---|---|---|
companies:read |
Read company data and profiles | Read-only integrations, CRM sync |
companies:write |
Create and update company records | Data enrichment services |
prospects:read |
Read prospect data and contacts | Lead management systems |
prospects:write |
Create and update prospect records | Contact enrichment |
prospect_searches:read |
Read prospect searches (ICPs) | Monitoring dashboards |
prospect_searches:write |
Create and update prospect searches | ICP configuration |
job_search:read |
Read job postings and search results | Job market analysis |
job_search:write |
Create and manage job searches | Hiring signal tracking |
handelsregister:read |
Read German commercial register data | Legal data lookup |
webtech_intel:read |
Read website technology intelligence | Tech stack analysis |
delivery:read |
Read delivered companies and feedback data | Delivery monitoring |
delivery:write |
Update feedback on delivered companies | Quality feedback loop |
organization:read |
Read organization settings and data | Account info |
organization:write |
Update organization settings | Account configuration |
orders:read |
Read enrichment order status and results | Order tracking, result retrieval |
orders:write |
Create enrichment orders | Enrichment workflow |
blacklists:read |
Read company blacklists and entries | Blacklist management |
blacklists:write |
Create, update, and delete company blacklists | Blacklist CRUD, assignment |
Presets¶
When creating an API key, you can choose a preset to quickly configure scopes. These match the options in the dashboard:
| Preset | Scopes | Description |
|---|---|---|
| Orders | orders:read, orders:write |
Create enrichment/discovery orders, track status, retrieve results |
| Read Only | orders:read, delivery:read, companies:read, prospects:read |
View orders, deliveries, companies, and prospects |
| Full Access | All public scopes | Complete API access for all endpoints |
| Custom | (choose individually) | Select specific scopes for your use case |
Scope Enforcement¶
Insufficient scopes return 403 Forbidden:
{
"code": "AUTHORIZATION_ERROR",
"message": "Insufficient permissions. Required scopes: orders:write",
"detail": {
"required_scopes": ["orders:write"],
"user_scopes": ["orders:read"]
}
}
Example: Scope-Restricted Request¶
# This will fail with 403 if API key lacks 'orders:write' scope
response = httpx.post(
"https://api.openprospect.io/api/v1/orders",
headers={"Authorization": f"Bearer {api_key}"},
json={
"title": "Q1 Enrichment",
"features": ["CONTACTS"],
"companies": [{"company_name": "Example Corp"}]
}
)
if response.status_code == 403:
error = response.json()
print(f"Missing scopes: {error['required_scopes']}")
print(f"Your scopes: {error['user_scopes']}")
const response = await fetch(
"https://api.openprospect.io/api/v1/orders",
{
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
title: "Q1 Enrichment",
features: ["CONTACTS"],
companies: [{ company_name: "Example Corp" }]
})
}
);
if (response.status === 403) {
const error = await response.json();
console.log(`Missing scopes: ${error.required_scopes}`);
console.log(`Your scopes: ${error.user_scopes}`);
}
Security Best Practices¶
1. Storage¶
DO:
- Store in environment variables
- Use secret management systems (AWS Secrets Manager, HashiCorp Vault, 1Password)
- Encrypt at rest in configuration files
- Use different keys for development, staging, and production
DON'T:
- Hard-code in source code
- Commit to version control (add
*.envto.gitignore) - Share keys between team members
- Log keys in plain text
- Send keys via email or Slack
2. Scope Principle of Least Privilege¶
Only grant the minimum scopes needed:
# Good: Enrichment partner needs only order scopes
Scopes: orders:read, orders:write
# Bad: Overly permissive
Scopes: companies:write, delivery:write, prospects:write, orders:write
3. Environment Separation¶
Always use the correct environment:
# Development
OPENPROSPECT_API_KEY="lnc_test_..."
OPENPROSPECT_API_URL="http://localhost:8000/api/v1"
# Production
OPENPROSPECT_API_KEY="lnc_live_..."
OPENPROSPECT_API_URL="https://api.openprospect.io/api/v1"
Test keys (lnc_test_) work only against test environments. Production keys (lnc_live_) prevent accidental test key usage in production.
Error Handling¶
Common Authentication Errors¶
| Status | Error Code | Cause | Solution |
|---|---|---|---|
| 401 | AUTHENTICATION_ERROR |
Invalid, expired, or revoked key | Check key, generate new one if needed |
| 401 | AUTHENTICATION_ERROR |
No Authorization header | Add Authorization: Bearer <key> header |
| 403 | AUTHORIZATION_ERROR |
API key lacks required scope | Use a key with the required scopes |
| 429 | rate_limit_exceeded |
Too many requests | Implement exponential backoff |
Security Note: All authentication failures return generic messages to prevent enumeration attacks. The API does NOT distinguish between "key doesn't exist" vs "key is revoked" vs "key is expired".
Error Handling Examples¶
import httpx
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def make_api_request(endpoint: str, api_key: str):
response = httpx.get(
f"https://api.openprospect.io/api/v1/{endpoint}",
headers={"Authorization": f"Bearer {api_key}"}
)
if response.status_code == 401:
raise AuthenticationError("Authentication failed. Check your API key.")
if response.status_code == 403:
error = response.json()
raise PermissionError(
f"Insufficient permissions. Required: {error['detail']['required_scopes']}"
)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
raise RateLimitError(f"Rate limited. Retry after {retry_after}s")
response.raise_for_status()
return response.json()
async function makeAPIRequest(endpoint, apiKey) {
const response = await fetch(
`https://api.openprospect.io/api/v1/${endpoint}`,
{
headers: {
"Authorization": `Bearer ${apiKey}`
}
}
);
if (response.status === 401) {
throw new Error("Authentication failed. Check your API key.");
}
if (response.status === 403) {
const error = await response.json();
throw new Error(
`Insufficient permissions. Required: ${error.detail.required_scopes}`
);
}
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || "60";
throw new Error(`Rate limited. Retry after ${retryAfter}s`);
}
if (!response.ok) {
throw new Error(`API error: ${response.statusText}`);
}
return await response.json();
}
class APIError extends Error {
constructor(
message: string,
public statusCode: number,
public errorCode?: string
) {
super(message);
}
}
async function makeAPIRequest<T>(
endpoint: string,
apiKey: string
): Promise<T> {
const response = await fetch(
`https://api.openprospect.io/api/v1/${endpoint}`,
{
headers: {
"Authorization": `Bearer ${apiKey}`
}
}
);
if (response.status === 401) {
throw new APIError(
"Authentication failed. Check your API key.",
401,
"AUTHENTICATION_ERROR"
);
}
if (response.status === 403) {
const error = await response.json();
throw new APIError(
`Insufficient permissions. Required: ${error.detail.required_scopes}`,
403,
"AUTHORIZATION_ERROR"
);
}
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || "60";
throw new APIError(
`Rate limited. Retry after ${retryAfter}s`,
429,
"rate_limit_exceeded"
);
}
if (!response.ok) {
throw new APIError(`API error: ${response.statusText}`, response.status);
}
return await response.json();
}
public class APIClient
{
private readonly HttpClient _client;
public async Task<T> MakeAPIRequest<T>(string endpoint, string apiKey)
{
_client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
var response = await _client.GetAsync(
$"https://api.openprospect.io/api/v1/{endpoint}");
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new AuthenticationException(
"Authentication failed. Check your API key.");
}
if (response.StatusCode == HttpStatusCode.Forbidden)
{
throw new UnauthorizedAccessException(
"Insufficient permissions. Check your API key scopes.");
}
if (response.StatusCode == HttpStatusCode.TooManyRequests)
{
var retryAfter = response.Headers.RetryAfter?.Delta?.TotalSeconds ?? 60;
throw new HttpRequestException(
$"Rate limited. Retry after {retryAfter}s");
}
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<T>();
}
}
Migration: OAuth to API Keys¶
If you're currently using OAuth/JWT tokens and want to switch to API keys:
Key Differences¶
| Feature | OAuth/JWT | API Keys |
|---|---|---|
| Lifetime | 1 hour (auto-refreshed) | Long-lived (manual rotation) |
| Refresh | Automatic with refresh token | No refresh — manually rotate |
| Use Case | User-facing applications | Server-to-server integrations |
| Headers | Authorization: Bearer <jwt> |
Authorization: Bearer lnc_live_... |
| Scopes | All internal scopes | Restricted external scopes |
Migration Steps¶
- Create API key with required scopes via the dashboard
- Update authentication code — headers remain the same format
- Remove refresh logic — not needed for API keys
- Implement rotation schedule — every 90 days minimum
- Test thoroughly before revoking OAuth credentials
Internal Authentication (OAuth 2.0 + PKCE)¶
For internal tools only — External developers should use API keys.
The OpenProspect admin dashboard uses OAuth 2.0 with PKCE for user authentication. OAuth 2.0 is available for internal tool development. For external integrations, API keys are the recommended authentication method.
Next Steps¶
With authentication configured:
- Enrichment Quick Start — Create your first enrichment order in 5 minutes
- Enrichment Order Guide — Full enrichment workflow reference
- Discovery Quick Start — Create a blacklist and discovery order in 5 minutes
- Discovery Order Guide — ICP-based company discovery
- Blacklist Management Guide — Manage company exclusion lists
- Error Reference — Handle API errors gracefully
Tips¶
- Test keys first: Use
lnc_test_keys against test environments before production - Rotate regularly: Rotate keys every 30-90 days
- Use minimal scopes: Grant only what's needed (e.g.,
orders:read+orders:writefor enrichment) - Separate keys per service: Don't share one key across multiple services
- Version your integration: Include version in User-Agent header
Questions? Check our Error Reference or contact support.