v1 · StableLaravel 11 · B2B REST API

Courses API Documentation

The Courses API is a production-grade B2B REST API that delivers course, skill, role, and YouTube reel data to external clients. It is split into two surfaces: a Public REST API authenticated via shared credentials, and a Web Admin Panel protected by session auth.

All public endpoints are reachable under /api/v1/ and accept JSON request bodies. Every protected call requires your client_id and api_key.

Base URL

All API requests should be made to: https://api.WorkFence.io

Authentication

The Courses API uses a shared-secret model. There is no OAuth or JWT — every protected request must include two fields in the POST body.

Credential Fields

FieldTypeDescription
client_idstring (UUID)Issued by your admin. Uniquely identifies your organisation.
api_keystring (32 chars)Issued alongside your client_id. Regeneratable from the dashboard.

Validation Flow

The ValidateClientMiddleware runs these checks in order before your request reaches any handler:

  1. Both client_id and api_key must be present → 401 if missing.
  2. client_id is looked up (5-minute cache) → 401 if not found.
  3. Client status must be active401 if inactive.
  4. api_key must match the stored value → 401 if wrong.
  5. If a domain restriction is set, the Origin header is checked → 401 on mismatch.
401 error shape
json
{
  "success": false,
  "message": "Invalid API key. Regenerate your key from the dashboard."
}

Base URL

Base URL
text
https://api.WorkFence.io/api/v1

All public API endpoints are prefixed with /api/v1. The admin panel lives separately under /admin and is not accessible via API credentials.


Making Your First Request

The quickest way to verify your credentials is to search a single skill. The endpoint returns courses mapped to that skill, sorted by relevance.

  1. Obtain your client_id and api_key from the admin panel.
  2. Send a POST request to /api/v1/skills.
  3. Include your credentials and a skill slug in the JSON body.
  4. Check the success field in the response.

Request

Example request
bash
curl -X POST https://api.WorkFence.io/api/v1/skills \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "api_key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "skill": "javascript",
    "level": "Beginner",
    "language": "en"
  }'

Response

Example response
json
{
  "success": true,
  "skill": "javascript",
  "total_courses": 48,
  "result_count": 24,
  "courses": [
    {
      "course_id": "yt_dQw4w9WgXcQ",
      "course_title": "JavaScript Full Course for Beginners",
      "course_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
      "level": "Beginner",
      "average_rating": 4.8,
      "total_rating": 12400,
      "base_language": "en",
      "course_duration": "4h 32m",
      "learners_count": 380000,
      "view_count": 2100000,
      "source": "YouTube"
    }
  ]
}

Request Headers

HeaderValueRequiredDescription
Content-Typeapplication/jsonYesAll request bodies must be JSON-encoded.
Acceptapplication/jsonNoRecommended. Ensures JSON error responses.
Originhttps://yourapp.comConditionalRequired if your client has domain restrictions enabled.

Request Parameters

Parameters for POST /api/v1/skills — the primary endpoint. Other endpoints follow the same credential pattern with endpoint-specific fields documented below.

NameTypeRequiredDescription
client_idstring (UUID)RequiredYour client UUID issued from the admin panel.
api_keystringRequired32-character API key. Regeneratable from the dashboard.
skillstringRequiredSkill slug in lowercase (e.g. "javascript", "python").
levelstringOptionalFilter by difficulty: "Beginner", "Intermediate", or "Advanced".
languagestringOptionalISO language code to filter by base language (e.g. "en").

Skills API

Search courses by skill slug. Results are sourced exclusively from YouTube and limited by your plan's result_limit_percent.

POST/api/v1/skillsSearch courses by a single skill
POST/api/v1/skills/multipleSearch up to 10 skills in one call
POST/api/v1/skills/importBulk import skills (no credit deduction)

Result Caching

Skill search results are cached for 24 hours. If you need fresh data, contact support to invalidate the cache for a specific skill.

Courses

Courses are the payload returned by skill search calls — they are not exposed via a direct public endpoint. They enter the system exclusively through XLSX bulk imports managed from the admin panel.

Each course is uniquely identified by the combination of course_id and source_id, preventing duplicates across platforms.

FieldDescription
course_titleFull title of the course.
levelBeginner / Intermediate / Advanced.
average_ratingWeighted average rating (0–5).
base_languagePrimary language of the course.
course_durationHuman-readable duration string (e.g. "4h 32m").
learners_countTotal enrolled learners.
view_countTotal YouTube views (YouTube source only).

Reels API

Search short-form YouTube content by category. The category match is case-insensitive and exact (no partial matching).

POST/api/v1/reelsSearch reels by category
POST/api/v1/reels/importBulk import reels (no credit deduction)

Example Request

bash
curl -X POST https://api.WorkFence.io/api/v1/reels \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "api_key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "category": "Python"
  }'

Roles API

Search job roles by name, slug, or synonym. The autocomplete endpoint is free of charge and returns prefix-first suggestions.

POST/api/v1/rolesFull-text role search
POST/api/v1/roles/suggestAutocomplete (no credit deduction)

Autocomplete Example

bash
curl -X POST https://api.WorkFence.io/api/v1/roles/suggest \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "api_key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "q": "acc",
    "limit": 5
  }'
Response
json
{
  "success": true,
  "query": "acc",
  "results": [
    { "slug": "accountant",        "name": "Accountant",        "matched_via": "role_name" },
    { "slug": "account-manager",   "name": "Account Manager",   "matched_via": "role_name" },
    { "slug": "account-executive", "name": "Account Executive", "matched_via": "synonym"   }
  ]
}

matched_via Field

The matched_via field tells you whether the result was found via its canonical role_name or a synonym (e.g. "acnt" matching "Accountant").


Error Handling

All error responses share the same shape: a success: false boolean and a human-readable message string.

Status CodeMeaningDescription
400Bad RequestRequired fields are missing or validation failed.
401UnauthorizedMissing credentials, invalid API key, or domain mismatch.
402Payment RequiredCredit pool exhausted. Add credits to continue.
404Not FoundThe requested skill, role, or resource does not exist.
422Unprocessable EntityRequest body is valid JSON but contains semantic errors.
429Too Many RequestsPer-minute rate limit exceeded. Back off and retry.
500Internal Server ErrorUnexpected server error. Contact support if it persists.

Retry Strategy

Only retry on 500 errors with exponential back-off. Do not auto-retry 401, 402, or 429 — those require action on your side (fix credentials, add credits, or wait out the rate-limit window).

Rate Limits

Rate limiting is handled by the RateLimitPerClient middleware, which runs after auth validation. The strategy depends on whether your credit pool is non-zero.

Two-Mode Throttling

  • Credits > 0 — each request atomically decrements one credit. A 402 is returned if the pool is empty before the decrement lands.
  • Credits = 0 — a per-minute counter applies instead. Exceeding request_limit_per_minute returns 429.

The request_limit_per_minute value is inherited from your assigned plan and is visible in the admin dashboard.


Result Limiting & Caps

Every list endpoint applies a two-stage limit to control how many records are returned to each client.

Stage 1 — Percentage

text
result_count = floor(total_matching_in_db × limit_percent / 100)

Stage 2 — Hard Cap (optional)

text
final_count = min(stage_1_result, effective_cap)

Caps resolve in this order: global toggle off → no cap; client toggle explicitly off → no cap for that client; client has a specific value → use it; otherwise fall back to the global default from Settings.

Environment Variables

VariableDefaultDescription
RESULT_LIMIT_MAX100Base max for course % calculation.
REEL_LIMIT_MAX100Base max for reel % calculation.
ROLE_LIMIT_MAX100Base max for role % calculation.
RESULT_CAP_ENABLEDfalseGlobal toggle for course hard cap.
REEL_CAP_ENABLEDfalseGlobal toggle for reel hard cap.
ROLE_CAP_ENABLEDfalseGlobal toggle for role hard cap.
RESULT_CAP_DEFAULT500Default course cap when enabled.
REEL_CAP_DEFAULT200Default reel cap when enabled.
ROLE_CAP_DEFAULT100Default role cap when enabled.

Pagination

The Blog Posts endpoint supports cursor-based pagination via page and per_pagequery parameters. Skill, reel, and role endpoints return a fixed result set governed by your plan's limit percentage.

text
GET /api/v1/blog/posts?page=2&per_page=20&category=engineering
ParameterDefaultDescription
page1Page number (1-indexed).
per_page15Results per page. Maximum is 100.
qFull-text search query.
categoryFilter by category slug.

SDK Examples

The following examples demonstrate a skill search call using cURL, JavaScript (fetch), Python (requests), and PHP (cURL extension).

bash
curl -X POST https://api.WorkFence.io/api/v1/skills \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "550e8400-e29b-41d4-a716-446655440000",
    "api_key": "sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "skill": "javascript"
  }'

FAQ

Yes. Use POST /api/v1/skills/multiple and pass a comma-separated list of up to 10 slugs in the "skills" field. Results are grouped by slug in the response.


Support

For technical issues, credential problems, or billing questions, reach out via the admin panel or email [email protected]. Include your client_id and a description of the issue. For urgent production outages, mark the subject line with [URGENT].