JobIn API Documentation

Version: 1.0 · Base URL: https://api.jobin.ailaile.cn


Table of Contents

  1. Authentication
  2. Plans & Rate Limits
  3. AI Models
  4. Data Sources
  5. Flow Diagrams
  6. Error Codes
  7. Account
  8. Jobs
  9. Billing
  10. System
  11. Quick Reference

Authentication

All requests (except public endpoints) require an API Key via one of two headers:

Authorization: Bearer jl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-API-Key: jl_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

API Key format: Starts with jl_live_ followed by 64 hex chars (72 chars total). Plaintext is shown only once at creation.

Public endpoints (no key required): POST /auth/register · POST /auth/login · POST /billing/webhook · GET /health


Plans & Rate Limits

PlanMonthly RequestsPer MinuteData SourcesAI FeaturesPrice
Free1,000101$0
Starter50,000603$49/mo
Pro500,0003005✓ lite + flagship$149/mo
Scale3,000,0001,0006✓ all$299/mo

Rate limit headers returned on every authenticated response:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-Plan: pro

Public endpoint protection: /auth/register is limited to 5 req/IP/min; /auth/login to 10 req/IP/min.


AI Models

Use X-AI-Provider and X-AI-Model headers to select a model per request. Use GET /jobs/providers for live availability status.

Currently available: Qwen (Alibaba) · Coming Soon: Anthropic · OpenAI · Google · DeepSeek

ProviderAliasModel IDTierMin PlanStatus
anthropichaikuclaude-haiku-4-5liteStarter🔜 Coming Soon
anthropicsonnetclaude-sonnet-4-6flagshipPro🔜 Coming Soon
openaigpt-4o-minigpt-4o-miniliteStarter🔜 Coming Soon
openaigpt-4ogpt-4oflagshipPro🔜 Coming Soon
googleflashgemini-2.0-flashliteFree🔜 Coming Soon
googleprogemini-1.5-proflagshipPro🔜 Coming Soon
qwenturboqwen-turboliteFree✅ Available
qwenplusqwen-plusflagshipPro✅ Available
deepseekchatdeepseek-chatliteStarter🔜 Coming Soon
deepseekreasonerdeepseek-reasonerflagshipPro🔜 Coming Soon
X-AI-Provider: qwen
X-AI-Model: turbo

Data Sources

SourceCoverageSpecialtyPlan
AdzunaGlobalWidest coverage, strong salary dataFree+
RemotiveGlobalRemote-first jobsStarter+
FindworkUK + USDev/tech roles, skills pre-taggedStarter+
ReedUKBest UK structured salary dataPro+
ArbeitnowEU / GermanyVisa-sponsored tagPro+
The MuseUS / GlobalCompany culture-focused rolesScale

Smart routing: country=gb → prioritizes Reed; country=us → prioritizes The Muse; EU countries (de at ch nl pl cz) → prioritizes Arbeitnow; remote=true → prioritizes Remotive and Findwork.

Results from multiple sources are deduplicated by fingerprint (title + company + city) and merged to fill missing fields.


Flow Diagrams

Diagram 1 — Account & Key Management

REGISTRATION
─────────────────────────────────────────────────────────────
  Client ──POST /auth/register──► API
                                   ├─ Create user account
                                   └─ Generate API Key (hash stored)
         ◄── jl_live_... returned (plaintext shown once only)

KEY RECOVERY  (if key is lost)
─────────────────────────────────────────────────────────────
  Client ──POST /auth/login──► Returns session token
         ──POST /auth/keys/rotate──► Revoke all keys → new jl_live_...

KEY MANAGEMENT  (ongoing)
─────────────────────────────────────────────────────────────
  GET  /auth/me                Plan · usage · key list
  POST /auth/keys              Create additional key
  POST /auth/keys/rotate       Revoke all → one new key  [distributed lock]
  DEL  /auth/keys/:prefix      Revoke specific key

Diagram 2 — Request Processing Pipeline

Every authenticated request to a /jobs/* endpoint passes through this pipeline:

  Incoming request
        │
        ▼
  ┌─────────────────────────────────────────────────┐
  │  Auth Middleware                                 │──── 401 (invalid key)
  │  Validate API Key → inject user + plan context  │
  └─────────────────────────────────────────────────┘
        │
        ▼
  ┌─────────────────────────────────────────────────┐
  │  Quota Middleware                                │──── 403 (endpoint access)
  │  Endpoint access · monthly quota · rate limit   │──── 429 (quota / rate)
  └─────────────────────────────────────────────────┘
        │
        ▼
  ┌─────────────────────────────────────────────────┐
  │  KV Cache Check                                  │
  │  Key = query + plan (+ model for AI endpoints)  │──── HIT → return immediately
  └─────────────────────────────────────────────────┘
        │ MISS
        ▼
  ┌─────────────────────────────────────────────────┐
  │  Source Router                                   │
  │  Select sources by plan (1–6) · parallel fetch  │
  │  Context routing (country / remote flag)         │
  └─────────────────────────────────────────────────┘
        │  concurrent fetch
        ├──► Adzuna
        ├──► Reed / Remotive / Findwork
        └──► Arbeitnow / The Muse
                    │
                    ▼
  ┌─────────────────────────────────────────────────┐
  │  Dedup + Merge                                   │
  │  Fingerprint by title+company+city               │
  │  Complement missing fields across sources        │
  └─────────────────────────────────────────────────┘
        │
        ▼
  ┌─────────────────────────────────────────────────┐
  │  AI Enhance  (Pro+ with enhance=true)            │
  │  Skill extraction · seniority · remote detect   │
  │  15s timeout → graceful degrade on failure       │
  └─────────────────────────────────────────────────┘
        │
        ▼
  Write KV cache + Log usage → Return response

Diagram 3 — Subscription & Billing Lifecycle

NEW SUBSCRIPTION
─────────────────────────────────────────────────────────────
  Client ──POST /billing/checkout──►
    { plan, success_url, cancel_url }
                │
                ▼
    Returns Stripe Checkout URL
                │
    User completes payment on Stripe
                │
    Redirect → success_url
                │
    (background) Stripe fires webhook ──►

WEBHOOK EVENTS  (automatic, Stripe → /billing/webhook)
─────────────────────────────────────────────────────────────
  checkout.session.completed    → Activate plan (matched by userId)
  subscription.updated          → Apply change + notify user email
  subscription.deleted          → Revert to Free + notify user email
  invoice.payment_failed        → Mark past_due, retain plan (grace period)
                                  Stripe retries; only downgrades on deletion

  After plan update → client polls GET /auth/me until plan field changes

SELF-SERVICE
─────────────────────────────────────────────────────────────
  POST /billing/portal ──► Stripe Customer Portal URL
    User can: upgrade · downgrade · cancel · view invoices
    Any change triggers webhook → automatic plan sync

Error Codes

CodeMeaning
400Bad Request — missing or invalid parameters
401Unauthorized — API Key missing, invalid, or revoked
403Forbidden — plan cannot access this endpoint or model tier. Response includes upgrade URL
409Conflict — email already registered, or already on this plan
429Too Many Requests — monthly quota or per-minute rate limit exceeded
503Service Unavailable — AI provider timed out (15s limit), retry later
500Internal Server Error

429 response:

{
  "error": "Monthly request limit exceeded",
  "limit": 50000,
  "used": 50001,
  "resets": "2025-05-01T00:00:00.000Z",
  "upgrade": "https://api.jobin.ailaile.cn/pricing"
}

Account

POST /auth/register

Public · 5 req/IP/min

FieldTypeRequiredDescription
emailstringAccount email
passwordstringMinimum 8 characters
curl -X POST https://api.jobin.ailaile.cn/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"dev@example.com","password":"mypassword"}'

201 Response:

{
  "message": "Registration successful",
  "api_key": "jl_live_a1b2c3d4...",
  "prefix": "jl_live_a1b2****",
  "plan": "free",
  "notice": "Save your API key now — it will not be shown again."
}

POST /auth/login

Public · 10 req/IP/min

Use if you've lost your API Key. Login → get session token → call POST /auth/keys/rotate.

FieldTypeRequiredDescription
emailstringAccount email
passwordstringAccount password

200 Response:

{
  "access_token": "eyJhbGci...",
  "refresh_token": "abc123...",
  "expires_in": 3600
}

GET /auth/me

Requires API Key · Free+

200 Response:

{
  "plan": "pro",
  "monthly_used": 12480,
  "monthly_limit": 500000,
  "endpoints": [
    "/jobs/search",
    "/jobs/salary",
    "/jobs/trends",
    "/jobs/insight"
  ],
  "api_keys": [
    {
      "name": "Default Key",
      "key_prefix": "jl_live_a1b2****",
      "last_used_at": "2025-04-12T08:32:00Z"
    }
  ]
}

POST /auth/keys

Requires API Key · Free+

FieldTypeRequiredDescription
namestringKey label, defaults to "Default Key"

201 Response:

{
  "key": "jl_live_...",
  "prefix": "jl_live_ab12****",
  "notice": "Save this key securely. It will not be shown again."
}

POST /auth/keys/rotate

Requires API Key · Free+

Revoke all active keys and generate one new key. Irreversible. Protected by a distributed lock — concurrent rotation requests return 429.

FieldTypeRequiredDescription
namestringNew key label, defaults to "Rotated Key"

DELETE /auth/keys/:prefix

Requires API Key · Free+

Revoke a specific key by its prefix (e.g. jl_live_ab12****). Get the prefix from GET /auth/me.

200 Response: { "message": "Key revoked" }


Jobs

GET /jobs/search

Requires API Key · Free+

Multi-source job aggregation with optional AI skill extraction.

ParameterTypeRequiredDescription
qstringSearch keyword, e.g. python developer
locationstringCity or region
countrystringCountry code, default gb. Supports: gb us au ca de fr
remotebooleanPrioritize remote-friendly sources
enhancebooleanAI skill extraction — Pro+ only. Gracefully degrades to unenhanced on timeout

AI Headers (when enhance=true):

HeaderDescription
X-AI-Providerqwen (others coming soon)
X-AI-ModelModel alias or full ID, e.g. turbo

Results cached 15 min per query + plan. Cache hits return X-Cache: HIT.

curl "https://api.jobin.ailaile.cn/jobs/search?q=python+developer&country=gb&enhance=true" \
  -H "Authorization: Bearer jl_live_..." \
  -H "X-AI-Provider: qwen" \
  -H "X-AI-Model: turbo"

200 Response:

{
  "meta": {
    "total": 42,
    "enhanced": true,
    "aiProvider": "qwen",
    "aiModel": "qwen-turbo",
    "sources": [
      { "name": "adzuna", "count": 22 },
      { "name": "reed", "count": 12 }
    ]
  },
  "jobs": [
    {
      "id": "adzuna:12345678",
      "source": "adzuna",
      "title": "Senior Python Developer",
      "company": "Tech Ltd",
      "location": { "city": "London", "country": "GB", "region": "" },
      "salary": {
        "min": 80000,
        "max": 100000,
        "currency": "GBP",
        "period": "year"
      },
      "skills": ["Python", "FastAPI", "Docker"],
      "seniority": "senior",
      "remote": false,
      "url": "https://...",
      "postedAt": "2025-04-10T09:00:00Z",
      "fetchedAt": "2025-04-12T08:00:00Z"
    }
  ]
}

GET /jobs/salary

Requires API Key · Starter+

Salary P25/P50/P75 percentiles with histogram.

ParameterTypeRequiredDescription
qstringJob title keyword
locationstringCity or region
countrystringCountry code, default gb

Cached 6 hours per query + plan.

200 Response:

{
  "query": "data scientist",
  "currency": "GBP",
  "sampleSize": 87,
  "p25": 55000,
  "median": 72000,
  "p75": 90000,
  "min": 35000,
  "max": 140000,
  "histogram": { "40000": 8, "50000": 15, "60000": 22 }
}

GET /jobs/trends

Requires API Key · Pro+ · X-AI-Provider + X-AI-Model required

AI-powered skill trend analysis.

ParameterTypeRequiredDescription
qstringRole or industry keyword
locationstringCity or region
countrystringDefault gb

Cached 6 hours per query + model.

200 Response:

{
  "query": "machine learning",
  "sampleSize": 63,
  "aiProvider": "qwen",
  "aiModel": "qwen-turbo",
  "topSkills": [{ "skill": "Python", "count": 52, "ratio": 0.83 }],
  "remoteRatio": 0.41,
  "seniority": { "junior": 8, "mid": 22, "senior": 27, "lead": 6 }
}

GET /jobs/insight

Requires API Key · Pro+ · X-AI-Provider + X-AI-Model required

AI-generated market intelligence report. Same query + same model = cached 6 hours at no extra AI cost.

ParameterTypeRequiredDescription
qstringRole or industry keyword
locationstringCity or region
countrystringDefault gb

200 Response:

{
  "query": "frontend developer",
  "location": "london",
  "insight": {
    "summary": "London's frontend market remains competitive...",
    "topSkills": [{ "skill": "React", "trend": "rising" }],
    "salaryRange": { "p25": 55000, "median": 72000, "p75": 88000 },
    "remoteRatio": 0.38,
    "keyFindings": ["Next.js up 34% YoY"],
    "dataQuality": "high",
    "sampleSize": 62,
    "aiProvider": "qwen",
    "aiModel": "qwen-turbo"
  }
}

GET /jobs/providers

Requires API Key · Free+

Returns AI providers and models available for the current plan with live availability status.

200 Response:

{
  "plan": "pro",
  "defaultProvider": "qwen",
  "available": [
    {
      "provider": "qwen",
      "status": "available",
      "models": [
        { "alias": "turbo", "tier": "lite" },
        { "alias": "plus", "tier": "flagship" }
      ]
    },
    { "provider": "anthropic", "status": "coming_soon", "models": [] }
  ]
}

Billing

POST /billing/checkout

Requires API Key · Free+

Creates a Stripe Checkout URL. After payment, poll GET /auth/me until plan changes.

FieldTypeRequiredDescription
planstringstarter · pro · scale
success_urlstringRedirect after payment. Supports {CHECKOUT_SESSION_ID}. Must be https:// or http://localhost
cancel_urlstringRedirect if cancelled. Same URL requirements
curl -X POST https://api.jobin.ailaile.cn/billing/checkout \
  -H "Authorization: Bearer jl_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "plan": "pro", "success_url": "https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}", "cancel_url": "https://yourapp.com/cancel" }'

200 Response: { "url": "https://checkout.stripe.com/c/pay/cs_live_..." }

409 Response: { "error": "Already on this plan or higher.", "portal": "https://api.jobin.ailaile.cn/billing/portal" }


POST /billing/portal

Requires API Key · Starter+

Returns a Stripe Customer Portal URL for self-service subscription management.

200 Response: { "url": "https://billing.stripe.com/p/session/..." }


POST /billing/webhook

Stripe only — Do not call directly

EventAction
checkout.session.completedActivate paid plan
customer.subscription.updatedApply change + notify email
customer.subscription.deletedRevert to Free + notify email
invoice.payment_failedMark past_due, retain plan (grace period)

System

GET /health

Public

{ "status": "ok", "ts": "2025-04-12T09:00:00.000Z" }

Quick Reference

MethodPathAuthMin Plan
POST/auth/register
POST/auth/login
GET/auth/meAPI KeyFree
POST/auth/keysAPI KeyFree
POST/auth/keys/rotateAPI KeyFree
DELETE/auth/keys/:prefixAPI KeyFree
GET/jobs/searchAPI KeyFree
GET/jobs/salaryAPI KeyStarter
GET/jobs/trendsAPI KeyPro
GET/jobs/insightAPI KeyPro
GET/jobs/providersAPI KeyFree
POST/billing/checkoutAPI KeyFree
POST/billing/portalAPI KeyStarter
POST/billing/webhookStripe
GET/health