Your portal into the Jobsquare API.
This documentation is designed to help you integrate with the Jobsquare website API from your own platform. It provides comprehensive guidance on handling various requests, such as updating your applicants status, adding job offers, and more…
Cette documentation est conçue pour vous aider à intégrer l’API du site Jobsquare depuis votre propre plateforme. Elle fournit un guide complet pour gérer les différentes requêtes : mise à jour du statut des candidats, ajout d’offres d’emploi, et plus encore.
Overview
Concept / ConceptThe Jobsquare API lets you publish and manage job offers on Jobsquare directly from your own platform. When you want to create or edit a job on your platform, you can use our API, which let you manage the job in Jobsquare website without accessing your Jobsquare account or leaving your platform.
L’API Jobsquare vous permet de publier et de gérer des offres d’emploi sur Jobsquare directement depuis votre propre plateforme. Lorsque vous créez ou modifiez une offre, vous pouvez utiliser notre API pour gérer le poste sur le site Jobsquare sans vous connecter à votre compte Jobsquare ni quitter votre interface.
The typical flow is:
Le déroulé classique est le suivant :
- 1. The employer fills a job form on your platform and enables the option to publish the offer on Jobsquare (for example via a checkbox).
- 2. Your backend validates the data and maps your internal fields to the corresponding Jobsquare fields.
- 3. Your backend calls the Jobsquare API with the mapped payload and the job is created or updated on Jobsquare.
- 4. You store the returned listing_sid in your database so you can update or deactivate the job later.
Authentication
API keys / Clés APIEvery request is authenticated using an API key associated with a Jobsquare employer account. The API key is sent in the HTTP header key.
Chaque requête est authentifiée via une clé API associée à un compte employeur Jobsquare. La clé API est envoyée dans l’en-tête HTTP key.
| Header | Description |
|---|---|
| key | API key of the employer. Identifies which account the jobs belong to. |
| Content-Type | application/json for all endpoints with a body. |
- 400 Missing API key header
- 403 Invalid API key
- 500 Internal server error
Make sure each employer has its own API key and store it securely in your configuration.
Assurez-vous que chaque employeur possède sa propre clé API et stockez-la de manière sécurisée dans votre configuration.
Recommended Implementation Pattern
Gateway / PasserelleWhile you can make requests manually, we recommend creating a centralized API handler for consistency, error handling and maintainability.
Vous pouvez appeler l’API manuellement, mais nous recommandons de créer un gestionnaire d’API centralisé pour garantir la cohérence, une meilleure gestion des erreurs et une maintenance plus simple.
This section presents the general structure of the ApiHandler file that contains reusable functions built to standardize and manage all HTTP communication between your application and the external Jobsquare API.
Cette section présente la structure générale du fichier ApiHandler qui regroupe des fonctions réutilisables permettant de standardiser et de gérer toutes les communications HTTP entre votre application et l’API externe de Jobsquare.
// install required packages
// npm install axios dotenv
const axios = require("axios");
require("dotenv").config();
const API_KEY = process.env.JOBSQUARE_API_KEY;
const BASE_URL = process.env.JOBSQUARE_BASE_URL;
if (!API_KEY || !BASE_URL) {
console.error("ERROR: Missing JOBSQUARE_API_KEY or JOBSQUARE_BASE_URL in .env file");
process.exit(1);
}
/**
* Make API request to Jobsquare
* @param {string} endpoint - API path like "jobs/addJob"
* @param {string} method - "GET", "POST", "PUT", "DELETE"
* @param {object} data - Data to send (optional)
* @param {boolean} isBinary - Set to true for PDF/download responses
*/
async function makeApiRequest(endpoint, method = "GET", data = null, isBinary = false) {
const url = `${BASE_URL}/${endpoint}`;
const options = {
method,
url,
headers: {
Accept: isBinary ? "*/*" : "application/json",
key: API_KEY,
Connection: "keep-alive",
},
responseType: isBinary ? "stream" : "json",
httpVersion: "1.1",
};
if (data) {
options.data = data;
}
try {
const response = await axios(options);
return {
success: true,
raw: response.data,
};
} catch (error) {
throw new Error(`API request failed: ${error.message}`);
}
}
/**
* Download resume as PDF
* @param {string} applicationResume - Resume ID/token
* @returns {Stream} PDF file stream
*/
async function downloadResume(applicationResume) {
const url = `${BASE_URL}/download-resume`;
try {
const response = await axios({
method: "POST",
url,
headers: {
Accept: "*/*",
"Content-Type": "application/json",
key: API_KEY,
},
data: {
applicationResume,
},
responseType: "stream",
});
return response.data;
} catch (error) {
throw new Error(`Resume download failed: ${error.message}`);
}
}
module.exports = {
makeApiRequest,
downloadResume,
};
use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ApiHandler
{
private HttpClientInterface $httpClient;
private string $proxyBaseUrl;
private string $apiKey;
public function __construct(HttpClientInterface $httpClient, string $proxyBaseUrl, string $apiKey)
{
$this->httpClient = $httpClient;
$this->proxyBaseUrl = rtrim($proxyBaseUrl, "/");
$this->apiKey = $apiKey;
}
public function makeApiRequest(string $endpoint, string $method, array $data = null, bool $isBinary = false): array
{
$url = $this->proxyBaseUrl . "/" . ltrim($endpoint, "/");
$options = [
"headers" => [
"Accept" => $isBinary ? "*/*" : "application/json",
"Connection" => "keep-alive",
"key" => $this->apiKey,
],
"http_version" => "1.1",
];
if ($data) {
if ($method === "PUT" && $endpoint === "resume") {
$options["body"] = http_build_query($data);
} else {
$options["json"] = $data;
}
}
$response = $this->httpClient->request($method, $url, $options);
$statusCode = $response->getStatusCode();
$content = $response->getContent(false);
$decoded = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) {
return $decoded;
}
return [
"success" => false,
"message" => "Non-JSON response (HTTP $statusCode): " . substr($content, 0, 300),
"raw" => $content,
];
}
public function downloadResumeDirectly(string $applicationResume): StreamedResponse
{
$url = $this->proxyBaseUrl . "/download-resume";
$options = [
"headers" => [
"Accept" => "*/*",
"Content-Type" => "application/json",
"key" => $this->apiKey,
],
"json" => ["applicationResume" => $applicationResume],
"buffer" => false,
];
$response = $this->httpClient->request("POST", $url, $options);
return new StreamedResponse(function () use ($response) {
foreach ($this->httpClient->stream($response) as $chunk) {
echo $chunk->getContent();
flush();
}
}, $response->getStatusCode(), $response->getHeaders(false));
}
}
services:
App\Service\ApiHandler:
arguments:
$httpClient: "@http_client"
$proxyBaseUrl: "%env(JOBSQUARE_BASE_URL)%"
$apiKey: "%env(JOBSQUARE_API_KEY)%"
import os
import requests
BASE_URL = os.getenv("JOBSQUARE_BASE_URL")
API_KEY = os.getenv("JOBSQUARE_API_KEY")
def make_api_request(endpoint, method="GET", data=None, is_binary=False):
url = BASE_URL.rstrip("/") + "/" + endpoint.lstrip("/")
headers = {
"Accept": "*/*" if is_binary else "application/json",
"key": API_KEY,
"Connection": "keep-alive",
}
kwargs = {"headers": headers, "timeout": 20}
if data is not None:
kwargs["json"] = data
if is_binary:
kwargs["stream"] = True
resp = requests.request(method, url, **kwargs)
if is_binary:
return resp.iter_content(chunk_size=8192)
return resp.json()
def download_resume(application_resume):
url = BASE_URL.rstrip("/") + "/download-resume"
headers = {
"Accept": "*/*",
"Content-Type": "application/json",
"key": API_KEY,
}
resp = requests.post(
url,
json={"applicationResume": application_resume},
headers=headers,
stream=True,
timeout=20,
)
return resp.iter_content(chunk_size=8192)
Use the handler across any platform. Keep base URL and key in environment, standardize headers and return shapes.
Utilities
Helpers / AidesUse helpers on top of the ApiHandler to fetch Jobsquare dictionaries, map your own domain entities to Jobsquare ids, and handle product info, application settings and listing status.
Utilisez des fonctions utilitaires au-dessus de l’ApiHandler pour récupérer les dictionnaires Jobsquare, faire la correspondance entre vos entités métier et les identifiants Jobsquare, et gérer les informations produit, les paramètres d’application et le statut des annonces.
const { makeApiRequest } = require("./jobsquare-api");
async function getContracts() {
try {
const res = await makeApiRequest("contracts", "GET");
return res.raw.contracts || [];
} catch (error) {
console.error("Error fetching contracts from Jobsquare:", error.message);
throw error;
}
}
async function getValues(valueKey) {
try {
const res = await makeApiRequest("values", "POST", { value: valueKey });
return res.raw.function_result || [];
} catch (error) {
console.error("Error fetching values from Jobsquare:", error.message);
throw error;
}
}
function mapField(mappingConfig, localId, type) {
if (!localId) {
return getDefaultValueForType(type);
}
const mappings = mappingConfig[type] || [];
if (!mappings.length) {
return getDefaultValueForType(type);
}
for (const mapping of mappings) {
const sourceCriteria = mapping.sourceCriteria;
const targetCriteria = mapping.targetCriteria;
const targetArray =
typeof targetCriteria === "string" ? JSON.parse(targetCriteria) : targetCriteria;
for (const target of targetArray || []) {
if (target && typeof target === "object" && "id" in target && target.id == localId) {
const sourceArray =
typeof sourceCriteria === "string" ? JSON.parse(sourceCriteria) : sourceCriteria;
return sourceArray && sourceArray.id ? sourceArray.id : getDefaultValueForType(type);
}
}
}
return getDefaultValueForType(type);
}
function getDefaultValueForType(type) {
const defaults = {
JobCategory: "1046",
EmploymentType: "1047",
id_Job_Experience: "1045",
id_Job_Niveaudtude: "1048",
id_Job_Rmunrationpropose: "1049",
id_Job_Langue: "1050",
id_Job_Genre: "1023",
WorkType: "2215",
};
return defaults[type] ?? "";
}
module.exports = {
getContracts,
getValues,
mapField,
getDefaultValueForType,
};
class JobsquareHelper
{
private ApiHandler $apiHandler;
private MappingConfigurationRepository $mappingConfigRepo;
public function __construct(ApiHandler $apiHandler, MappingConfigurationRepository $mappingConfigRepo)
{
$this->apiHandler = $apiHandler;
$this->mappingConfigRepo = $mappingConfigRepo;
}
public function getContracts(): array
{
try {
$response = $this->apiHandler->makeApiRequest("contracts", "GET");
return $response["contracts"] ?? [];
} catch (\Throwable $e) {
return [];
}
}
public function getValues(string $valueKey): array
{
try {
$response = $this->apiHandler->makeApiRequest("values", "POST", ["value" => $valueKey]);
return $response["function_result"] ?? [];
} catch (\Throwable $e) {
return [];
}
}
public function mapField(?object $entity, string $type)
{
if (!$entity) {
return $this->getDefaultValueForType($type);
}
$localId = $entity->getId();
$mappings = $this->mappingConfigRepo->findBy(["entityType" => $type]);
if (empty($mappings)) {
return $this->getDefaultValueForType($type);
}
foreach ($mappings as $mapping) {
$sourceCriteria = $mapping->getSourceCriteria();
$targetCriteria = $mapping->getTargetCriteria();
$targetArray = is_string($targetCriteria) ? json_decode($targetCriteria, true) : $targetCriteria;
foreach ($targetArray as $target) {
if (isset($target["id"]) && $target["id"] == $localId) {
$sourceArray = is_string($sourceCriteria) ? json_decode($sourceCriteria, true) : $sourceCriteria;
return $sourceArray["id"];
}
}
}
return $this->getDefaultValueForType($type);
}
private function getDefaultValueForType(string $type): string
{
$defaults = [
"JobCategory" => "1046",
"EmploymentType" => "1047",
"id_Job_Experience" => "1045",
"id_Job_Niveaudtude" => "1048",
"id_Job_Rmunrationpropose" => "1049",
"id_Job_Langue" => "1050",
"id_Job_Genre" => "1023",
"WorkType" => "2215",
];
return $defaults[$type] ?? "";
}
}
import json
from api_handler import make_api_request
def get_contracts():
try:
res = make_api_request("contracts", "GET")
return res.get("contracts", [])
except Exception:
return []
def get_values(value_key: str):
try:
res = make_api_request("values", "POST", {"value": value_key})
return res.get("function_result", [])
except Exception:
return []
def map_field(mapping_config: dict, local_id: int, type_: str) -> str:
if not local_id:
return get_default_value_for_type(type_)
mappings = mapping_config.get(type_, [])
if not mappings:
return get_default_value_for_type(type_)
for m in mappings:
source_criteria = m.get("sourceCriteria")
target_criteria = m.get("targetCriteria")
target_array = (
json.loads(target_criteria)
if isinstance(target_criteria, str)
else target_criteria
) or []
for target in target_array:
if isinstance(target, dict) and target.get("id") == local_id:
source_array = (
json.loads(source_criteria)
if isinstance(source_criteria, str)
else source_criteria
) or {}
return str(source_array.get("id", get_default_value_for_type(type_)))
return get_default_value_for_type(type_)
def get_default_value_for_type(type_: str) -> str:
defaults = {
"JobCategory": "1046",
"EmploymentType": "1047",
"id_Job_Experience": "1045",
"id_Job_Niveaudtude": "1048",
"id_Job_Rmunrationpropose": "1049",
"id_Job_Langue": "1050",
"id_Job_Genre": "1023",
"WorkType": "2215",
}
return defaults.get(type_, "")
Data Mapping Overview: Connecting API Data with Your Local Entities
Field alignment / Alignement des champsUse a dedicated mapping service to translate Jobsquare dictionary values to your own entities. The service reads /api/values results and produces mapping rows you can store in your database.
Utilisez un service de mapping dédié pour traduire les valeurs de dictionnaire de Jobsquare vers vos propres entités. Ce service lit les résultats de /api/values et produit des lignes de correspondance que vous pouvez stocker dans votre base de données.
const { makeApiRequest } = require("./jobsquare-api");
const API_FIELDS = {
Category: "Category",
EducationLevel: "Education Level",
ContractType: "Contract Type",
Experience: "Experience",
Language: "Language",
Gender: "Gender",
Location: "Location",
WorkType: "WorkType"
};
const TARGET_CLASS_MAP = {
Category: "App\\Entity\\Category",
EducationLevel: "App\\Entity\\Education",
ContractType: "App\\Entity\\ContractType",
Experience: "App\\Entity\\Experience",
Language: "App\\Entity\\Language",
Gender: "App\\Entity\\Gender",
Location: "App\\Entity\\Region",
WorkType: "App\\Entity\\Region"
};
async function processMappings(formData) {
const mappings = [];
for (const [field, fieldMappings] of Object.entries(formData)) {
if (!API_FIELDS[field]) continue;
const apiData = await fetchApiValues(field);
const apiValues = apiData.values;
const apiIdMap = apiData.ids;
for (const [apiIndex, localIds] of Object.entries(fieldMappings)) {
const apiValue = apiValues[apiIndex];
if (!apiValue) continue;
const sourceCriteria = {
id: apiIdMap[apiValue] || null,
value: apiValue,
};
const localIdsArray = Array.isArray(localIds) ? localIds : [localIds];
const targetCriteria = await resolveLocalEntities(field, localIdsArray);
if (targetCriteria.length > 0) {
mappings.push({
entityType: field,
sourceCriteria,
targetCriteria,
targetEntityClass: TARGET_CLASS_MAP[field] || "UnknownClass",
});
}
}
}
return mappings;
}
async function fetchApiValues(field) {
try {
const response = await makeApiRequest("values", "GET", { value: field });
if (response.success && response.raw.function_result?.length > 0) {
const firstItem = response.raw.function_result[0];
const valueKey = firstItem.value ? "value" : "name";
return {
values: response.raw.function_result.map(
(item) => item[valueKey] || item.name
),
ids: response.raw.function_result.reduce((acc, item) => {
const key = item[valueKey] || item.name;
acc[key] = item.sid;
return acc;
}, {}),
};
}
return { values: [], ids: {} };
} catch (error) {
console.error("Error fetching API values for", field, error);
return { values: [], ids: {} };
}
}
async function resolveLocalEntities(field, localIds) {
const localOptions = await getLocalOptions(field);
return localIds
.map((id) => {
const entity = localOptions.find((opt) => opt.id == id);
return entity
? {
id: entity.id,
value: entity.name,
}
: null;
})
.filter(Boolean);
}
async function getLocalOptions(field) {
return [];
}
async function loadExistingMappings() {
return [];
}
async function saveMappings(mappings) {
console.log("Saving mappings", mappings.length);
}
async function clearMappings() {
console.log("Clearing all mappings");
}
module.exports = {
API_FIELDS,
TARGET_CLASS_MAP,
processMappings,
fetchApiValues,
resolveLocalEntities,
getLocalOptions,
loadExistingMappings,
saveMappings,
clearMappings,
};
class MappingService
{
private ApiHandler $apiHandler;
private EntityManagerInterface $entityManager;
private const API_FIELDS = [
"Category" => "Category",
"EducationLevel" => "Education Level",
"ContractType" => "Contract Type",
"Experience" => "Experience",
"Language" => "Language",
"Gender" => "Gender",
"Location" => "Location",
"WorkType" => "WorkType"
];
private const TARGET_CLASS_MAP = [
"Category" => \App\Entity\Category::class,
"EducationLevel" => \App\Entity\Education::class,
"ContractType" => \App\Entity\ContractType::class,
"Experience" => \App\Entity\Experience::class,
"Language" => \App\Entity\Language::class,
"Gender" => \App\Entity\Gender::class,
"Location" => \App\Entity\Region::class,
"WorkType" => \App\Entity\Region::class,
];
public function __construct(ApiHandler $apiHandler, EntityManagerInterface $entityManager)
{
$this->apiHandler = $apiHandler;
$this->entityManager = $entityManager;
}
public function processMappings(array $formData): array
{
$mappings = [];
foreach ($formData as $field => $fieldMappings) {
if (!isset(self::API_FIELDS[$field])) {
continue;
}
["values" => $apiValues, "ids" => $apiIdMap] = $this->fetchApiValues($field);
foreach ($fieldMappings as $apiIndex => $localIds) {
$apiValue = $apiValues[$apiIndex] ?? null;
if (!$apiValue) {
continue;
}
$sourceCriteria = [
"id" => $apiIdMap[$apiValue] ?? null,
"value" => $apiValue,
];
$localIdsArray = is_array($localIds) ? $localIds : [$localIds];
$targetCriteria = $this->resolveLocalEntities($field, $localIdsArray);
if (!empty($targetCriteria)) {
$mappings[] = [
"entityType" => $field,
"sourceCriteria" => $sourceCriteria,
"targetCriteria" => $targetCriteria,
"targetEntityClass" => self::TARGET_CLASS_MAP[$field] ?? "UnknownClass",
];
}
}
}
return $mappings;
}
private function fetchApiValues(string $field): array
{
$response = $this->apiHandler->makeApiRequest("values", "GET", ["value" => $field]);
if (!empty($response["success"]) && !empty($response["function_result"])) {
$items = $response["function_result"];
$first = $items[0];
$valueKey = isset($first["value"]) ? "value" : "name";
$values = array_map(fn ($item) => $item[$valueKey] ?? $item["name"], $items);
$ids = [];
foreach ($items as $item) {
$key = $item[$valueKey] ?? $item["name"];
$ids[$key] = $item["sid"] ?? null;
}
return ["values" => $values, "ids" => $ids];
}
return ["values" => [], "ids" => []];
}
private function resolveLocalEntities(string $field, array $localIds): array
{
$class = self::TARGET_CLASS_MAP[$field] ?? null;
if (!$class) {
return [];
}
$repo = $this->entityManager->getRepository($class);
$entities = $repo->findBy(["id" => $localIds]);
$result = [];
foreach ($entities as $entity) {
$result[] = [
"id" => $entity->getId(),
"value" => (string) $entity,
];
}
return $result;
}
}
from typing import Dict, List, Any, Tuple
from api_handler import make_api_request
API_FIELDS: Dict[str, str] = {
"Category": "Category",
"EducationLevel": "Education Level",
"ContractType": "Contract Type",
"Experience": "Experience",
"Language": "Language",
"Gender": "Gender",
"Location": "Location",
"WorkType": "WorkType"
}
TARGET_CLASS_MAP: Dict[str, str] = {
"Category": "app.models.Category",
"EducationLevel": "app.models.Education",
"ContractType": "app.models.ContractType",
"Experience": "app.models.Experience",
"Language": "app.models.Language",
"Gender": "app.models.Gender",
"Location": "app.models.Region",
"WorkType": "app.models.WorkType"
}
async def process_mappings(form_data: Dict[str, Any]) -> List[Dict[str, Any]]:
mappings: List[Dict[str, Any]] = []
for field, field_mappings in form_data.items():
if field not in API_FIELDS:
continue
api_values, api_ids = await fetch_api_values(field)
for api_index, local_ids in field_mappings.items():
api_index_int = int(api_index)
if api_index_int >= len(api_values):
continue
api_value = api_values[api_index_int]
source_criteria = {
"id": api_ids.get(api_value),
"value": api_value,
}
local_ids_list = local_ids if isinstance(local_ids, list) else [local_ids]
target_criteria = await resolve_local_entities(field, local_ids_list)
if target_criteria:
mappings.append(
{
"entityType": field,
"sourceCriteria": source_criteria,
"targetCriteria": target_criteria,
"targetEntityClass": TARGET_CLASS_MAP.get(field, "UnknownClass"),
}
)
return mappings
async def fetch_api_values(field: str) -> Tuple[List[str], Dict[str, Any]]:
try:
response = await make_api_request("values", "GET", {"value": field})
data = response.get("raw", {})
items = data.get("function_result", [])
if not items:
return [], {}
first = items[0]
value_key = "value" if "value" in first else "name"
values = [item.get(value_key) or item.get("name") for item in items]
ids: Dict[str, Any] = {}
for item in items:
key = item.get(value_key) or item.get("name")
ids[key] = item.get("sid")
return values, ids
except Exception:
return [], {}
async def resolve_local_entities(field: str, local_ids: List[Any]) -> List[Dict[str, Any]]:
local_options = await get_local_options(field)
result: List[Dict[str, Any]] = []
for id_ in local_ids:
entity = next((opt for opt in local_options if opt.get("id") == id_), None)
if entity:
result.append(
{
"id": entity.get("id"),
"value": entity.get("name"),
}
)
return result
async def get_local_options(field: str) -> List[Dict[str, Any]]:
return []
async def load_existing_mappings() -> List[Dict[str, Any]]:
return []
async def save_mappings(mappings: List[Dict[str, Any]]) -> None:
print(f"Saving {len(mappings)} mappings")
async def clear_mappings() -> None:
print("Clearing all mappings")
To create a job and send it to Jobsquare, build a payload with your mapped fields and call the add job endpoint:
const { makeApiRequest } = require("./jobsquare-api");
async function createJob(req, res, next) {
try {
const job = await loadLocalJob(req.body);
const mappedData = {
contract_id: job.contractId,
id_Job_Vacancies: job.vacancies || 0,
Title: job.title,
JobCategory: job.jobCategoryId,
EmploymentType: job.employmentTypeId,
JobDescription: job.description,
JobRequirements: job.requirements,
id_Job_Experience: job.experienceId,
id_Job_Niveaudtude: job.educationId,
id_Job_Rmunrationpropose: job.salaryRangeId,
id_Job_Langue: (job.languageIds || []).join(","),
WorkType: (job.workModeIds || []).join(","),
Location_ville: job.cityId,
Location_State: job.stateName,
Location_gouvernorat: job.stateId,
GooglePlace: job.country || "Maroc",
email: job.email,
url: job.url,
id_Job_MotsCls: job.keywords,
};
const apiResponse = await makeApiRequest("jobs/addJob", "POST", mappedData);
const apiJobId = apiResponse.raw?.function_result?.id;
if (apiJobId) {
await markJobAsPosted(job.id, apiJobId);
}
res.json({ success: true, apiJobId });
} catch (error) {
next(error);
}
}
async function loadLocalJob(payload) {
return payload;
}
async function markJobAsPosted(jobId, apiJobId) {}
module.exports = {
createJob,
};
public function newJob(Request $request, EntityManagerInterface $em)
{
$form = $this->createForm(JobFormType::class, null, [
"allow_extra_fields" => true,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var Job $job */
$job = $form->getData();
$addToApi = $request->request->get("switch") === "private"
|| $request->request->get("switch") === "1";
$selectedContract = $request->request->get("contract");
$email = $request->request->get("email");
$web = $request->request->get("web");
$vacant = $request->request->get("vacant");
$etude = $request->request->get("netude");
$money = $request->request->get("nmoney");
$langue = (array) $request->request->get("nlangue", []);
$genre = $request->request->get("nGenre");
$postulerMethod = $request->request->get("postuler-name");
$emailValue = $postulerMethod === "email-postuler" ? $email : "contact@artusmaroc.ma";
$webValue = $postulerMethod === "web-postuler" ? $web : "";
$enterpriseName = "Entreprise name";
$description = $job->getDescription() ?? "";
$requirements = $job->getResume() ?? "";
$defaultKeywords = array_filter(array_map("trim", explode(" ", "$enterpriseName $description $requirements")));
$idJobMotsCls = implode(",", array_slice($defaultKeywords, 0, 5));
if ($addToApi) {
if (!$selectedContract) {
$this->addFlash("error", "Please select a contract type.");
return $this->redirectToRoute("app_admin_jobs");
}
$mappedData = [
"contract_id" => $selectedContract,
"id_Job_Vacancies" => $vacant ?? 0,
"Title" => $job->getTitle(),
"JobCategory" => $this->mapField($job->getSecteur(), "JobCategory") ?? "",
"EmploymentType" => $this->mapField($job->getTypecontrat(), "EmploymentType") ?? "",
"JobDescription" => $job->getDescription(),
"JobRequirements" => $job->getResume(),
"id_Job_Experience" => $this->mapField($job->getExperience(), "id_Job_Experience") ?? "",
"id_Job_Niveaudtude" => $etude ?: null,
"id_Job_Rmunrationpropose" => $money ?: null,
"id_Job_Langue" => $langue ? implode(",", $langue) : null,
"WorkType" => $workMode ? implode(",", $workMode) : null,
"Location_ville" => $job->getVille() ?? "",
"Location_State" => $job->getGov()->getName(),
"Location_gouvernorat" => $this->mapField($job->getGov(), "Location_State"),
"GooglePlace" => "Maroc",
"email" => $emailValue,
"url" => $webValue,
"id_Job_MotsCls" => $idJobMotsCls,
];
$apiJobData = $this->apiHandler->makeApiRequest("jobs/addJob", "POST", $mappedData);
if (!empty($apiJobData["function_result"]["id"])) {
$job->setIsPostedApi(true);
$job->setApiJobId($apiJobData["function_result"]["id"]);
}
}
$em->persist($job);
$em->flush();
}
}
from typing import Dict, Any
from api_handler import make_api_request
async def create_job(request) -> Dict[str, Any]:
body = await request.json()
job = await load_local_job(body)
mapped_data = {
"contract_id": job.get("contract_id"),
"id_Job_Vacancies": job.get("vacancies", 0),
"Title": job.get("title"),
"JobCategory": job.get("jobCategoryId"),
"EmploymentType": job.get("employmentTypeId"),
"JobDescription": job.get("description"),
"JobRequirements": job.get("requirements"),
"id_Job_Experience": job.get("experienceId"),
"id_Job_Niveaudtude": job.get("educationId"),
"id_Job_Rmunrationpropose": job.get("salaryRangeId"),
"id_Job_Langue": ",".join(job.get("languageIds", [])),
"WorkType": ",".join(job.get("workModeIds", [])),
"Location_ville": job.get("cityId"),
"Location_State": job.get("stateName"),
"Location_gouvernorat": job.get("stateId"),
"GooglePlace": job.get("country", "Maroc"),
"email": job.get("email"),
"url": job.get("url"),
"id_Job_MotsCls": job.get("keywords"),
}
response = await make_api_request("jobs/addJob", "POST", mapped_data)
data = response.get("raw", {}).get("function_result", {})
api_job_id = data.get("id")
if api_job_id:
await mark_job_as_posted(job["id"], api_job_id)
return {"success": True, "apiJobId": api_job_id}
async def load_local_job(payload: Dict[str, Any]) -> Dict[str, Any]:
return payload
async def mark_job_as_posted(job_id: int, api_job_id: Any) -> None:
pass
Keep a mapping table in your code that translates your local enums and entities to Jobsquare IDs. Store the resulting IDs in the payload.
Job collection
Listings / OffresThis collection centralizes everything related to job listings and job offers in the API. It helps you list, create, update and hide jobs while keeping your own database as the source of truth.
Cette collection regroupe tout ce qui concerne les annonces et offres d’emploi dans l’API. Elle vous aide à lister, créer, mettre à jour et masquer les offres tout en gardant votre propre base de données comme source de vérité.
To track synchronization with Jobsquare, keep two extra attributes on your job entity:
Pour suivre la synchronisation avec Jobsquare, conservez deux attributs supplémentaires sur votre entité « job » :
- isPostedApi boolean flag indicating if the job was sent to the API.
- apiJobId numeric id returned by the API for this job.
Use these attributes to decide when to call add, update or delete endpoints and to display the correct status in your backoffice.
const { makeApiRequest } = require("./jobsquare-api");
async function listJobsWithStatus(req, res, next) {
try {
const apiResponse = await makeApiRequest("/jobs", "POST");
const apiJobs = apiResponse.raw.function_result || [];
const localJobs = await loadLocalJobs();
const jobStatuses = {};
for (const job of localJobs) {
let status = "désactivé";
if (job.isPostedApi) {
const match = apiJobs.find((j) => j.id === job.apiJobId);
if (match) {
if (match.active === 1) {
status = "activé";
} else if (match.active === 2) {
status = "posté, en attente d'acceptation";
} else if (match.active === 0) {
status = "posté, caché";
}
}
} else {
status = "non posté";
}
jobStatuses[job.id] = status;
}
res.json({ jobs: localJobs, jobStatuses });
} catch (error) {
next(error);
}
}
async function loadLocalJobs() {
return [];
}
module.exports = {
listJobsWithStatus,
};
/**
* @Route("a/admin/jobs", name="app_admin_jobs")
*/
public function jobs(Request $request, JobRepository $jobRepository)
{
$myJobsResponse = $this->apiHandler->makeApiRequest("jobs/my-jobs", "GET");
$jobs = $jobRepository->findAll();
$jobStatuses = [];
$myJobs = $myJobsResponse["function_result"] ?? [];
foreach ($jobs as $job) {
$status = "désactivé";
if ($job->getIsPostedApi()) {
foreach ($myJobs as $data) {
if ($job->getApiJobId() === ($data["id"] ?? null)) {
if (($data["active"] ?? null) === 1) {
$status = "activé";
} elseif (($data["active"] ?? null) === 2) {
$status = "posté, en attente d'acceptation";
} elseif (($data["active"] ?? null) === 0) {
$status = "posté, caché";
}
}
}
} else {
$status = "non posté";
}
$jobStatuses[$job->getId()] = $status;
}
return $this->render("admin/job/listing.html.twig", [
"jobs" => $jobs,
"jobStatuses" => $jobStatuses,
]);
}
from typing import Dict, Any, List
from api_handler import make_api_request
async def list_jobs_with_status(request) -> Dict[str, Any]:
api_response = await make_api_request("jobs/my-jobs", "GET")
api_jobs: List[Dict[str, Any]] = api_response.get("raw", {}).get("function_result", [])
local_jobs = await load_local_jobs()
job_statuses: Dict[int, str] = {}
for job in local_jobs:
status = "désactivé"
if job.get("isPostedApi"):
match = next((j for j in api_jobs if j.get("id") == job.get("apiJobId")), None)
if match:
active = match.get("active")
if active == 1:
status = "activé"
elif active == 2:
status = "posté, en attente d'acceptation"
elif active == 0:
status = "posté, caché"
else:
status = "non posté"
job_statuses[job["id"]] = status
return {"jobs": local_jobs, "jobStatuses": job_statuses}
async def load_local_jobs() -> List[Dict[str, Any]]:
return []
Use the same isPostedApi and apiJobId fields when calling create, update and delete endpoints so your local jobs always reflect the remote state.
Job payload
Create and update / Créer et mettre à jourWhen you create a job with POST /api/jobs/addJob, you send a JSON payload containing the main job fields plus Jobsquare-specific dictionary IDs.
Lorsque vous créez une offre avec POST /api/jobs/addJob, vous envoyez un payload JSON qui contient les champs principaux du poste ainsi que les identifiants spécifiques aux dictionnaires Jobsquare.
| Field | Type | Description |
|---|---|---|
| contract_idrequired | number | Contract id returned by GET /api/contracts. |
| id_Job_Vacanciesrequired | number | Number of open positions. |
| Titlerequired | string | Job title. |
| JobCategoryrequired | number | Category id from values with "JobCategory". |
| EmploymentTyperequired | number | Contract type id from values with "EmploymentType". |
| JobDescriptionoptional | string | Full job description. |
| JobRequirementsoptional | string | Candidate requirements. |
| id_Job_Experiencerequired | number | Experience id from values with "id_Job_Experience". |
| id_Job_Niveaudtuderequired | number | Education level id, from values with "id_Job_Niveaudtude". |
| id_Job_Rmunrationproposerequired | number | Salary range id, from values with "id_Job_Rmunrationpropose". |
| id_Job_Languerequired | string | Comma-separated language ids, from values with "id_Job_Langue". |
| id_Job_Genrerequired | number | Gender id, from values with "id_Job_Genre". |
| WorkTypeoptional | string | Work mode. Comma-separated ids, from values with "WorkType" — on-site 2215, hybrid 2216, remote 2217. |
| Location_villerequired | number | City id from Jobsquare cities table. |
| Location_gouvernoratrequired | number | State id from Jobsquare states table. |
| GooglePlacerequired | string | Country name, usually "Maroc". |
| emailrequired | string | Email where applications are sent when applying by email. |
| urloptional | string | External URL where candidates apply if you prefer redirect. |
| id_Job_MotsClsoptional | string | Comma-separated keywords to improve search. |
On update with PUT /api/update-listings, include listing_sid in the body and only the fields you want to modify.
Once your payload is mapped, use small helper functions in your backend to add, update and delete jobs on Jobsquare. Each helper takes a payload that already follows this structure, so you can adapt it to your own entities or DTOs.
const { makeApiRequest } = require("./jobsquare-api");
const REQUIRED_FIELDS = [
"JobCategory",
"EmploymentType",
"id_Job_Experience",
"Location_State",
"id_Job_Niveaudtude",
"id_Job_Rmunrationpropose",
"id_Job_Langue",
"Location_ville",
"id_Job_Genre",
"WorkType",
"id_Job_Vacancies",
];
function validateJobPayload(payload) {
const missing = [];
for (const field of REQUIRED_FIELDS) {
const value = payload[field];
if (value === undefined || value === null || (typeof value === "string" && value.trim() === "")) {
missing.push(field);
}
}
if (missing.length) {
throw new Error("Required fields missing or empty: " + missing.join(", "));
}
}
async function addJob(payload) {
validateJobPayload(payload);
const response = await makeApiRequest("jobs/addJob", "POST", payload);
return response.raw?.function_result ?? response;
}
async function updateJob(listingSid, changes) {
const body = { listing_sid: listingSid, ...changes };
const response = await makeApiRequest("update-listings", "PUT", body);
return response.raw?.function_result ?? response;
}
async function deleteJob(listingSid) {
const body = { listing_sid: listingSid };
const response = await makeApiRequest("jobs/deleteJob", "PUT", body);
return response.raw?.function_result ?? response;
}
module.exports = {
addJob,
updateJob,
deleteJob,
};
class JobApiService
{
private const REQUIRED_FIELDS = [
"JobCategory",
"EmploymentType",
"id_Job_Experience",
"Location_State",
"id_Job_Niveaudtude",
"id_Job_Rmunrationpropose",
"id_Job_Langue",
"Location_ville",
"id_Job_Genre",
"WorkType",
"id_Job_Vacancies",
];
private ApiHandler $apiHandler;
public function __construct(ApiHandler $apiHandler)
{
$this->apiHandler = $apiHandler;
}
private function validateJobPayload(array $payload): void
{
$missing = [];
foreach (self::REQUIRED_FIELDS as $field) {
$value = $payload[$field] ?? null;
if ($value === null || (is_string($value) && trim($value) === "")) {
$missing[] = $field;
}
}
if (!empty($missing)) {
throw new \RuntimeException("Required fields missing or empty: " . implode(", ", $missing));
}
}
public function addJob(array $payload): array
{
$this->validateJobPayload($payload);
return $this->apiHandler->makeApiRequest("jobs/addJob", "POST", $payload);
}
public function updateJob(string $listingSid, array $changes): array
{
$body = array_merge(["listing_sid" => $listingSid], $changes);
return $this->apiHandler->makeApiRequest("update-listings", "PUT", $body);
}
public function deleteJob(string $listingSid): array
{
$body = ["listing_sid" => $listingSid];
return $this->apiHandler->makeApiRequest("jobs/deleteJob", "PUT", $body);
}
}
from typing import Dict, Any, List
from api_handler import make_api_request
REQUIRED_FIELDS: List[str] = [
"JobCategory",
"EmploymentType",
"id_Job_Experience",
"Location_State",
"id_Job_Niveaudtude",
"id_Job_Rmunrationpropose",
"id_Job_Langue",
"Location_ville",
"id_Job_Genre",
"WorkType",
"id_Job_Vacancies",
]
def validate_job_payload(payload: Dict[str, Any]) -> None:
missing: List[str] = []
for field in REQUIRED_FIELDS:
value = payload.get(field)
if value is None or (isinstance(value, str) and value.strip() == ""):
missing.append(field)
if missing:
raise RuntimeError("Required fields missing or empty: " + ", ".join(missing))
async def add_job(payload: Dict[str, Any]) -> Dict[str, Any]:
validate_job_payload(payload)
response = await make_api_request("jobs/addJob", "POST", payload)
return response
async def update_job(listing_sid: str, changes: Dict[str, Any]) -> Dict[str, Any]:
body: Dict[str, Any] = {"listing_sid": listing_sid}
body.update(changes)
response = await make_api_request("update-listings", "PUT", body)
return response
async def delete_job(listing_sid: str) -> Dict[str, Any]:
body = {"listing_sid": listing_sid}
response = await make_api_request("jobs/deleteJob", "PUT", body)
return response
Application collection
Candidates / CandidaturesAfter your jobs are synchronized with Jobsquare, you can query application data and download resumes through your own backend. Use your existing application controllers to fetch applications and call the download-resume endpoint when you need the candidate's CV as a PDF.
Une fois vos offres synchronisées avec Jobsquare, vous pouvez interroger les candidatures et télécharger les CV directement depuis votre backend. Utilisez vos contrôleurs d’application existants pour récupérer les candidatures et appelez l’endpoint download-resume lorsque vous avez besoin du CV du candidat au format PDF.
The application collection is always filtered by the authenticated employer key, so make sure you pass the correct key header when listing applications or downloading resumes.
La collection de candidatures est toujours filtrée par la clé employeur authentifiée, veillez donc à transmettre le bon header key lors du listing des candidatures ou du téléchargement des CV.
const apiHandler = require("./jobsquare-api");
async function getApplicationsForJob(listingId) {
const response = await apiHandler.makeApiRequest("applications", "GET", {
listing_id: listingId,
});
return response.raw?.function_result || [];
}
// Statuts disponibles : ['Nouveau', 'Interview', 'S├®lectionn├®', 'Disqualifi├®']
// Toute autre valeur entraînera une erreur
async function updateApplicationStatus(appId, status) {
const body = {
status,
app_id: appId,
};
return apiHandler.makeApiRequest("/update-status", "PUT", body);
}
async function downloadResume(req, res, next) {
try {
const { applicationResume } = req.params;
return apiHandler.downloadResumeDirectly(applicationResume, res);
} catch (error) {
next(error);
}
}
module.exports = {
getApplicationsForJob,
updateApplicationStatus,
downloadResume,
};
class ApplicationApiService
{
private ApiHandler $apiHandler;
public function __construct(ApiHandler $apiHandler)
{
$this->apiHandler = $apiHandler;
}
public function getApplicationsForJob(string $listingId): array
{
$body = ["listing_id" => $listingId];
return $this->apiHandler->makeApiRequest("applications", "GET", $body);
}
public function updateStatus(string $appId, string $status): array
{
$body = [
"status" => $status,
"app_id" => $appId,
];
return $this->apiHandler->makeApiRequest("applications/updateStatus", "PUT", $body);
}
public function downloadResume(string $applicationResume): \Symfony\Component\HttpFoundation\StreamedResponse
{
return $this->apiHandler->downloadResumeDirectly($applicationResume);
}
}
from typing import Dict, Any, List
from api_handler import make_api_request, download_resume
async def get_applications_for_job(listing_id: str) -> List[Dict[str, Any]]:
response = await make_api_request("applications", "GET", {"listing_id": listing_id})
return response.get("raw", {}).get("function_result", [])
async def update_application_status(app_id: str, status: str) -> Dict[str, Any]:
body = {"status": status, "app_id": app_id}
response = await make_api_request("applications/updateStatus", "PUT", body)
return response
def stream_resume(application_resume: str):
return download_resume(application_resume)