Search Skills
curl --request GET \
--url https://api.example.com/v1/api/skills/search \
--header 'x-api-key: <api-key>'import requests
url = "https://api.example.com/v1/api/skills/search"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.example.com/v1/api/skills/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/api/skills/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/api/skills/search"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/v1/api/skills/search")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/api/skills/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": [
{
"skill_id": "sk-001",
"Skill": "Docker",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
},
{
"skill_id": "sk-042",
"Skill": "Docker Compose",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
}
],
"page": 1,
"limit": 10,
"count": 2
}
{
"error": "Keyword must be at least 3 characters long."
}
{
"error": "Page must be an integer between 1 and 5."
}
{
"error": "Invalid search pattern. Please use alphanumeric characters."
}
{
"error": "Search rate limit exceeded. Please try again later."
}
Skills
Search Skills
Search the skill catalogue by keyword. No auth needed.
GET
/
api
/
skills
/
search
Search Skills
curl --request GET \
--url https://api.example.com/v1/api/skills/search \
--header 'x-api-key: <api-key>'import requests
url = "https://api.example.com/v1/api/skills/search"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.example.com/v1/api/skills/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/v1/api/skills/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/v1/api/skills/search"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.example.com/v1/api/skills/search")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/v1/api/skills/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": [
{
"skill_id": "sk-001",
"Skill": "Docker",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
},
{
"skill_id": "sk-042",
"Skill": "Docker Compose",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
}
],
"page": 1,
"limit": 10,
"count": 2
}
{
"error": "Keyword must be at least 3 characters long."
}
{
"error": "Page must be an integer between 1 and 5."
}
{
"error": "Invalid search pattern. Please use alphanumeric characters."
}
{
"error": "Search rate limit exceeded. Please try again later."
}
This is probably the endpoint you’ll use most. Type a keyword, get back a list of matching skills. Simple.
Results are paginated — you get up to 10 per page, and you can go up to page 5. If you need to go deeper than that, you might want to use a more specific keyword instead.
Query Parameters
string
required
What you’re searching for. Has to be at least 3 characters — single letters and two-character queries are rejected. Also can’t be entirely made up of special characters like
*** or ???.integer
default:"1"
Which page you want. Must be between 1 and 5. Defaults to 1 if you leave it out.
Response
array
integer
The page you’re on
integer
How many results per page (always 10)
integer
How many results came back on this page
Examples
curl "http://localhost:5000/api/skills/search?keyword=docker&page=1"
const res = await fetch(
"http://localhost:5000/api/skills/search?keyword=docker&page=1"
);
const { data, count } = await res.json();
import requests
res = requests.get(
"http://localhost:5000/api/skills/search",
params={"keyword": "docker", "page": 1}
)
print(res.json())
{
"data": [
{
"skill_id": "sk-001",
"Skill": "Docker",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
},
{
"skill_id": "sk-042",
"Skill": "Docker Compose",
"sub_skill_id": "sub-001",
"master_skill_id": "ms-001"
}
],
"page": 1,
"limit": 10,
"count": 2
}
{
"error": "Keyword must be at least 3 characters long."
}
{
"error": "Page must be an integer between 1 and 5."
}
{
"error": "Invalid search pattern. Please use alphanumeric characters."
}
{
"error": "Search rate limit exceeded. Please try again later."
}
A note on security
This endpoint is public, so it has a few extra protections layered on:- Rate limit — 15 requests per minute per IP. That’s enough for normal use.
- Regex sanitization — your keyword is escaped before it hits the database query. This prevents ReDoS attacks.
- Scraping detection — if an IP makes more than 30 requests in a 5-minute window, it gets blocked. The threshold is in
middleware/detectScraping.jsif you need to tune it.

