ttling. Recognizing this split early prevents weeks of debugging attribution gaps and endpoint deprecation surprises.
Core Solution
Building a resilient Careerjet integration requires treating the v4 endpoint as a strict contract, not a flexible query interface. The implementation must handle authentication, IP routing, response type discrimination, and server-side context adaptation.
Step 1: Register and Whitelist
Before writing code, navigate to the Careerjet publisher portal. Register your domain, generate an API key, and add your server's outbound IP addresses to the whitelist. The dashboard accepts up to eight IPv4 addresses. If your infrastructure uses NAT or cloud load balancers, whitelist the egress IPs, not the internal cluster addresses.
Step 2: Implement HTTP Basic Authentication
Careerjet v4 uses standard HTTP Basic Auth, but with a non-standard twist: the password field must remain empty. The credential string is formatted as api_key: and then Base64-encoded. This differs from typical bearer token or API key header patterns, making it easy to misconfigure if you assume modern OAuth2 conventions.
Step 3: Adapt Server-Side Context
The v4 spec expects user_ip and user_agent because it was built for browser interactions. In a server-side context (cron jobs, backend services, data pipelines), there is no end-user browser. You must pass the server's outbound IP and a valid browser user-agent string. Careerjet's gateway validates these fields for analytics routing. Omitting them or passing null triggers request rejection.
Step 4: Handle Discriminated Responses
The API returns a type field that dictates payload structure. A JOBS type contains the search results. A LOCATIONS type indicates the location parameter was too vague, and Careerjet is returning a list of resolvable regions. Your client must branch on this field before parsing job objects.
Step 5: Production-Grade TypeScript Implementation
The following implementation uses native fetch, strict typing, environment-driven configuration, and explicit response routing. It avoids the legacy endpoint entirely and enforces production safety checks.
interface CareerjetConfig {
apiKey: string;
serverIp: string;
userAgent: string;
baseUrl: string;
timeoutMs: number;
}
interface JobListing {
title: string;
company: string;
locations: string[];
description: string;
salary: string;
url: string;
}
interface JobsResponse {
type: 'JOBS';
jobs: JobListing[];
message?: string;
}
interface LocationsResponse {
type: 'LOCATIONS';
message: string;
locations?: string[];
}
type CareerjetApiResponse = JobsResponse | LocationsResponse;
class CareerjetClient {
private config: CareerjetConfig;
constructor(config: CareerjetConfig) {
this.config = config;
}
private buildAuthHeader(): string {
const credentials = Buffer.from(`${this.config.apiKey}:`).toString('base64');
return `Basic ${credentials}`;
}
async searchJobs(
keywords: string,
location: string,
localeCode: string = 'en_KE'
): Promise<JobListing[]> {
const params = new URLSearchParams({
locale_code: localeCode,
keywords,
location,
sort: 'date',
page_size: '100',
user_ip: this.config.serverIp,
user_agent: this.config.userAgent,
});
const url = `${this.config.baseUrl}/v4/query?${params.toString()}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: this.buildAuthHeader(),
Referer: 'https://your-production-domain.com/jobs/',
},
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`Careerjet API failed: ${response.status} ${response.statusText}`);
}
const data: CareerjetApiResponse = await response.json();
if (data.type !== 'JOBS') {
console.warn(`[Careerjet] Ambiguous location resolved to: ${data.message}`);
return [];
}
return data.jobs.map((job) => ({
title: job.title,
company: job.company,
locations: job.locations,
description: job.description,
salary: job.salary,
url: job.url,
}));
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
throw new Error('Careerjet request timed out');
}
throw error;
}
}
}
// Usage example
const client = new CareerjetClient({
apiKey: process.env.CAREERJET_API_KEY!,
serverIp: process.env.SERVER_OUTBOUND_IP!,
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
baseUrl: 'https://search.api.careerjet.net',
timeoutMs: 15000,
});
client.searchJobs('software engineer', 'Nairobi').then((jobs) => {
console.log(`Retrieved ${jobs.length} listings`);
});
Architecture Decisions & Rationale
- Native
fetch over axios: Reduces bundle size and aligns with modern Node.js/edge runtime standards. Timeout handling uses AbortController for precise cancellation.
- Discriminated Union Typing: TypeScript enforces compile-time safety when branching on
type === 'JOBS'. This prevents runtime crashes when the API returns location suggestions instead of job arrays.
- Environment-Driven Configuration: API keys, server IPs, and timeouts are externalized. This prevents credential leakage and allows different whitelisted IPs for staging vs production.
- Explicit
Referer Header: Careerjet's gateway validates the Referer against registered domains. Hardcoding a placeholder breaks domain verification. The header must match your publisher portal registration.
- Salary Field Preservation: The API returns salary as a human-readable string (e.g.,
"KES 80,000 – 120,000 per month"). Parsing this into numeric ranges requires locale-aware regex. The client preserves the raw string to avoid data loss, leaving normalization to the application layer.
Pitfall Guide
1. Assuming "Public" Means Unauthenticated
Explanation: AI summaries and legacy documentation often label Careerjet as a "public API." In reality, it means publicly registrable. The v4 endpoint rejects unauthenticated requests with a 403.
Fix: Always verify the current endpoint against the publisher dashboard. Treat "public" as "open registration," not "zero auth."
2. Hardcoding Placeholder Affiliate IDs
Explanation: The legacy endpoint accepts any affid string. Developers use placeholders like "test123" to bypass early errors. This breaks attribution tracking and violates publisher revenue agreements.
Fix: Migrate to the v4 endpoint immediately. Remove affid from all request payloads. Use the registered API key for authentication.
3. Ignoring IP Whitelisting Requirements
Explanation: Careerjet enforces strict IP allowlisting. Even with correct credentials, requests from unregistered IPs return 403. Cloud environments with dynamic egress IPs or NAT gateways frequently trigger this.
Fix: Identify your infrastructure's outbound IP range. Add all egress addresses to the publisher dashboard before deployment. Use infrastructure-as-code to document whitelisted IPs.
4. Mishandling Ambiguous Location Responses
Explanation: Passing vague location strings (e.g., "East Africa" or "Remote") triggers a LOCATIONS response type. Developers who assume every response contains a jobs array encounter undefined errors.
Fix: Implement explicit type branching. Log the message field when type === 'LOCATIONS' and either prompt the user to select a region or fallback to a default city.
5. Passing Invalid user_ip or user_agent in Server Contexts
Explanation: The v4 spec requires these fields for analytics routing. Server-side cron jobs or backend services often omit them or pass null, causing gateway rejection.
Fix: Pass the server's outbound IPv4 address and a standard browser user-agent string. Careerjet's gateway validates format, not actual browser presence.
6. Skipping HTTPS Enforcement
Explanation: The legacy endpoint accepts HTTP. The v4 endpoint mandates HTTPS. Mixing protocols causes mixed-content warnings in browsers and potential credential interception.
Fix: Enforce https:// in all endpoint URLs. Configure your HTTP client to reject non-TLS connections.
7. Over-Parsing the Salary Field
Explanation: Salary data arrives as localized strings. Attempting to extract numeric values without locale context breaks for currencies like KES, NGN, or ZAR.
Fix: Store the raw string in your database. Implement a separate normalization service that handles currency parsing, range extraction, and locale conversion.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Client-side widget embedding | Use Careerjet's official JS embed | Handles auth, routing, and compliance automatically | Zero infrastructure cost |
| Backend data pipeline / cron | v4 API with server-side auth | Requires user_ip/user_agent, supports batch processing | Moderate compute cost, high compliance value |
| High-volume aggregation (>10k req/day) | Implement response caching + v4 API | Reduces API calls, avoids rate limits, preserves attribution | Cache infrastructure cost, lower API overhead |
| Ambiguous location search | Fallback to LOCATIONS response parsing | Prevents empty results, improves user experience | Minimal dev overhead, higher data accuracy |
Configuration Template
Copy this template into your .env or secrets manager. Adjust values to match your infrastructure.
# Careerjet v4 API Configuration
CAREERJET_API_KEY=your_registered_api_key_here
CAREERJET_SERVER_IP=203.0.113.1
CAREERJET_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
CAREERJET_BASE_URL=https://search.api.careerjet.net
CAREERJET_TIMEOUT_MS=15000
CAREERJET_DEFAULT_LOCALE=en_KE
CAREERJET_REFER=https://your-production-domain.com/jobs/
Quick Start Guide
- Register & Whitelist: Log into the Careerjet publisher portal, register your domain, generate an API key, and add your server's outbound IP to the whitelist.
- Configure Environment: Populate the configuration template with your API key, server IP, and production domain. Ensure secrets are injected at runtime.
- Initialize Client: Instantiate the
CareerjetClient with your environment variables. Verify the Referer header matches your registered domain exactly.
- Execute Test Query: Run a search with a precise location (e.g.,
"Nairobi") and keywords. Confirm the response returns type: 'JOBS' and a populated jobs array.
- Deploy & Monitor: Ship the integration. Monitor gateway response codes and log
LOCATIONS type responses to refine location resolution logic.