Your portal into the Jobsquare API.

Base URL https://api.jobsquare.ma /api
Auth Header key: YOUR_API_KEY

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

The 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

Every 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.

Headers
Required
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.
Error cases
  • 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

While 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.

API Handler – Centralized Request Gateway
Pattern

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.

JavaScript / TypeScript
ApiHandler
jobsquare-api.js
// 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,
};
PHP (Symfony)
Gateway
ApiHandler.php
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.yaml
services:
  App\Service\ApiHandler:
    arguments:
      $httpClient: "@http_client"
      $proxyBaseUrl: "%env(JOBSQUARE_BASE_URL)%"
      $apiKey: "%env(JOBSQUARE_API_KEY)%"
Python
ApiHandler
api_handler.py
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

Use 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.

Node.js utilities
Dictionaries & mapping
jobsquare-utils.js
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,
};
Symfony utilities
Dictionaries & mapping
JobsquareHelper.php
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] ?? "";
    }
}
Python utilities
Dictionaries & mapping
jobsquare_utils.py
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

Use 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.

Node.js mapping service
mapping-service.js
mapping-service.js
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,
};
Symfony mapping service
MappingService.php
MappingService.php
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;
    }
}
Python mapping service
mapping_service.py
mapping_service.py
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:

Node.js: create job and send to API
POST /jobs
jobs-controller.js
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,
};
Symfony: create job and send to API
jobs/addJob
JobController.php
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();
    }
}
Python: create job and send to API
jobs/addJob
jobs_controller.py
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

This 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.

Node.js: list jobs with API status
GET /jobs
jobs-controller.js
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,
};
Symfony: list jobs with API status
jobs/my-jobs
JobController.php
/**
 * @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,
    ]);
}
Python: list jobs with API status
jobs/my-jobs
jobs_controller.py
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

When 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.

Node.js helpers for add, update and delete
jobs-service.js
jobs-service.js
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,
};
Symfony helpers for add, update and delete
JobApiService.php
JobApiService.php
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);
    }
}
Python helpers for add, update and delete
jobs_service.py
jobs_service.py
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

After 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.

Node.js: list applications and update status
applications
applications-service.js
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,
};
Symfony: list applications and update status
applications/get
ApplicationApiService.php
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);
    }
}
Python: list applications and update status
applications/get
applications_service.py
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)