> For the complete documentation index, see [llms.txt](https://bluesidenl.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://bluesidenl.gitbook.io/wiki/intranet/api-entreprises.md).

# API des entreprises

Lisez les données de votre entreprise depuis vos propres outils : ventes, compte, grossiste, équipe, heures de service. Clés, sécurité et exemples.

Chaque **patron** peut créer, dans l'application **Entreprise** de la tablette, des **clés d'API** qui donnent accès aux données de son entreprise depuis ses propres outils : un tableau de bord sur votre site, un bot Discord qui annonce les ventes, un tableur Google Sheets qui calcule les primes de vos employés…

L'API est en **lecture seule** : une clé peut consulter les données, jamais les modifier. Elle ne donne accès qu'aux données de **votre** entreprise, et jamais aux informations privées des joueurs (identifiants Steam, adresses IP, numéros de carte, codes de retrait, position des véhicules).

|                      |                                                    |
| -------------------- | -------------------------------------------------- |
| **Adresse de base**  | `https://intranet.blue-side.fr/api/v1`             |
| **Méthode**          | `GET` uniquement                                   |
| **Authentification** | entête `Authorization: Bearer <votre clé>`         |
| **Format**           | JSON (UTF-8), dates ISO 8601 en UTC, montants en € |
| **Limites**          | 60 requêtes par minute et 10 000 par jour, par clé |

## 🔑 <mark style="color:blue;">Créer une clé</mark>

{% stepper %}
{% step %}

### Ouvrez la rubrique API

Sur la tablette, ouvrez l'application **Entreprise**, puis la rubrique **API** (groupe « Outils »). Elle n'apparaît que pour le patron.
{% endstep %}

{% step %}

### Créez la clé

Touchez **Nouvelle clé**, donnez-lui un nom (par exemple « Bot Discord ») et choisissez les **données accessibles**. Ne cochez que ce dont votre outil a besoin.
{% endstep %}

{% step %}

### Limitez les adresses (facultatif)

Indiquez les **adresses IP autorisées** de votre serveur (jusqu'à 10, adresses IP ou plages CIDR comme `2001:db8::/48`). La clé sera refusée depuis toute autre adresse.
{% endstep %}

{% step %}

### Copiez la clé

La clé complète (elle commence par `bsk_`) ne s'affiche **qu'une seule fois**. Copiez-la et rangez-la en lieu sûr : BlueSide n'en garde qu'une empreinte et ne peut pas vous la redonner.
{% endstep %}
{% endstepper %}

Les droits que vous pouvez donner à une clé :

| Droit                          | Donne accès à                                                  |
| ------------------------------ | -------------------------------------------------------------- |
| Solde et mouvements du compte  | `/compte`, `/compte/mouvements`                                |
| Ventes et statistiques         | `/ventes`, `/ventes/{source}/{reference}`, `/statistiques`     |
| Factures et devis              | `/factures`, `/devis`                                          |
| Commandes passées au grossiste | `/grossiste/commandes`                                         |
| Employés et grades             | `/employes`, `/grades`                                         |
| Heures de service              | `/services`, et les heures dans `/employes` et `/statistiques` |
| Fiches de paie                 | `/fiches-de-paie`                                              |
| Véhicules de l'entreprise      | `/vehicules`                                                   |

La fiche de l'entreprise (`/entreprise`) est accessible à toute clé. Une entreprise peut avoir **3 clés actives** au maximum.

## 🛡️ <mark style="color:purple;">Sécurité</mark>

* **Régénérez** une clé à tout moment (rubrique API, touchez la clé, **Régénérer la clé**) : une nouvelle valeur est créée et l'ancienne cesse **immédiatement** de fonctionner. **Révoquez-la** si vous n'en avez plus besoin.
* Une clé n'est valable que tant que **son créateur est le patron** : si l'entreprise change de patron, toutes les clés de l'ancien patron sont désactivées. Le nouveau patron peut les régénérer pour les reprendre à son compte.
* La clé se transmet **uniquement dans l'entête** `Authorization`. Une requête qui la place dans l'adresse (`?key=…`) est refusée, pour qu'elle n'apparaisse jamais dans un historique ou un journal.
* Après **10 clés invalides en 10 minutes**, une adresse IP est bloquée pendant 10 minutes.
* Le **journal des appels** de la rubrique API montre les 100 derniers appels (30 jours) : point d'accès, code de réponse, adresse IP et durée. Un appel que vous ne reconnaissez pas ? Régénérez la clé.

{% hint style="danger" %}
N'utilisez jamais votre clé dans une page web publique ou une application distribuée (JavaScript dans le navigateur, mod, application mobile) : n'importe qui pourrait la lire. Appelez l'API depuis un **serveur**, un **bot** ou un **tableur** (Google Apps Script). Ne la partagez jamais, ni sur Discord ni en capture d'écran.
{% endhint %}

## 📦 <mark style="color:orange;">Format des réponses</mark>

Toutes les réponses ont la même enveloppe. En cas de succès :

```json
{ "ok": true, "data": { ... } }
```

En cas d'erreur :

```json
{ "ok": false, "error": { "code": "forbidden", "message": "Cette clé n'a pas le droit « paie » (Fiches de paie)." } }
```

| Code HTTP | `code`         | Signification                                                                              |
| --------- | -------------- | ------------------------------------------------------------------------------------------ |
| 200       |                | Succès                                                                                     |
| 400       | `bad_request`  | Requête invalide (par exemple la clé placée dans l'adresse)                                |
| 401       | `unauthorized` | Clé manquante, invalide, révoquée ou désactivée                                            |
| 403       | `forbidden`    | Droit manquant pour ce point d'accès, adresse IP non autorisée, ou méthode autre que `GET` |
| 404       | `not_found`    | Point d'accès ou élément introuvable                                                       |
| 422       | `validation`   | Paramètre invalide (détail dans `error.fields`)                                            |
| 429       | `rate_limited` | Limite de requêtes atteinte : patientez                                                    |

Chaque réponse réussie porte ces entêtes :

| Entête                        | Contenu                                     |
| ----------------------------- | ------------------------------------------- |
| `X-RateLimit-Limit`           | Requêtes autorisées par minute (60)         |
| `X-RateLimit-Remaining`       | Requêtes restantes dans la minute en cours  |
| `X-RateLimit-Reset`           | Secondes avant la minute suivante           |
| `X-RateLimit-Daily-Remaining` | Requêtes restantes aujourd'hui (sur 10 000) |

### Listes, pages et périodes

Les listes renvoient `elements`, `limit`, `offset` et `suivant` : `suivant` vaut l'`offset` de la page suivante, ou `null` s'il n'y en a plus.

| Paramètre    | Effet                                                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `limit`      | Nombre d'éléments par page, de 1 à 100 (50 par défaut, 25 pour le grossiste)                                                    |
| `offset`     | Position de départ (0 par défaut)                                                                                               |
| `from`, `to` | Période : `2026-10-01`, `2026-10-01T18:00:00Z` ou un timestamp Unix. Une date seule couvre la journée entière (heure de Paris). |

Les données suivent le jeu avec quelques secondes de décalage. Inutile d'interroger l'API plus d'une fois par minute pour le même point d'accès.

## 📚 <mark style="color:green;">Les points d'accès</mark>

### `GET /api/v1`

La clé utilisée, ses droits, les limites et la liste des points d'accès. Pratique pour tester une clé.

### `GET /api/v1/entreprise`

```json
{
  "id": 12, "nom": "Garage Martin", "activites": ["Mécanique"],
  "description": "…", "contact": "…", "telephone": "5550123", "adresse": "12 rue des Lilas",
  "recrutement": true, "patron": "Lucas MARTIN", "effectif": 8, "en_service": 3
}
```

### `GET /api/v1/compte`

Droit : solde et mouvements. Solde actuel du compte de l'entreprise et flux des 7 et 30 derniers jours.

```json
{
  "solde": 64293.33, "solde_au": "2026-10-21T22:46:18Z",
  "sept_jours": { "entrees": 29500.0, "sorties": 2390.0, "mouvements": 11 },
  "trente_jours": { "entrees": 41200.0, "sorties": 9870.0, "mouvements": 38 }
}
```

### `GET /api/v1/compte/mouvements`

Droit : solde et mouvements. Paramètres : `from`, `to`, `sens` (`entree` ou `sortie`), `limit`, `offset`. Du plus récent au plus ancien.

```json
{
  "elements": [
    { "id": 1069, "montant": 450.0, "solde_apres": 64293.33, "libelle": "Amende réglée", "code": "BLUESIDE_INTRANET_FINE", "date": "2026-10-21T22:46:15Z" }
  ],
  "limit": 50, "offset": 0, "suivant": 50
}
```

`montant` est négatif pour une sortie.

### `GET /api/v1/ventes`

Droit : ventes. Toutes les ventes **encaissées** par l'entreprise, sous une forme commune, de la plus récente à la plus ancienne. Paramètres : `from`, `to`, `source`, `limit`, `offset`. La réponse contient aussi `total` (nombre et montant de toute la sélection, pas seulement de la page).

| `source`    | Ventes concernées                                      |
| ----------- | ------------------------------------------------------ |
| `facture`   | Factures émises et payées                              |
| `blue_eats` | Commandes Blue Eats (restaurants)                      |
| `grossiste` | Commandes reçues, si votre entreprise est le grossiste |
| `taxi`      | Courses de taxi payées                                 |
| `bus`       | Titres de transport vendus                             |

```json
{
  "elements": [
    { "source": "blue_eats", "reference": "MCB-2026-000412", "libelle": "Commande n°412", "montant": 86.5, "statut": "completed", "date": "2026-10-21T19:02:11Z" }
  ],
  "limit": 50, "offset": 0, "suivant": null,
  "total": { "ventes": 1, "montant": 86.5 }
}
```

Une commande Blue Eats ou grossiste annulée après paiement reste dans la liste avec le statut `cancelled` (elle a été remboursée au client).

### `GET /api/v1/ventes/{source}/{reference}`

Droit : ventes. Le détail d'une vente : lignes de la facture, produits et quantités d'une commande, kilomètres et durée d'une course…

### `GET /api/v1/statistiques`

Droit : ventes. Paramètre `jours` : `7`, `30` (par défaut) ou `90`. Chiffre d'affaires total, par source et jour par jour (heure de Paris). Avec le droit « Heures de service », ajoute `heures_de_service` (total de l'équipe) ; avec le droit « Solde et mouvements », ajoute les entrées et sorties du compte.

```json
{
  "jours": 7, "chiffre_affaires": 12450.0, "ventes": 96,
  "par_source": { "blue_eats": { "ventes": 90, "montant": 9850.0 }, "facture": { "ventes": 6, "montant": 2600.0 } },
  "par_jour": [ { "jour": "2026-10-20", "montant": 1830.0 } ],
  "heures_de_service": 79.3,
  "compte": { "entrees": 29500.0, "sorties": 2390.0 }
}
```

### `GET /api/v1/factures` et `GET /api/v1/devis`

Droit : factures et devis. Factures : paramètre `sens` (`emises` par défaut, ou `recues` pour celles adressées à votre entreprise), `statut` (`unpaid`, `processing`, `paid`, `refused`, `cancelled`), `from`, `to`. Chaque facture donne l'émetteur, le client, les lignes, le total, le statut et ses dates. Devis : paramètre `statut` (`draft`, `sent`, `accepted`, `refused`, `converted`, `cancelled`), avec la référence de la facture créée à partir du devis.

### `GET /api/v1/grossiste/commandes`

Droit : commandes au grossiste. Les commandes passées **par votre entreprise** chez le grossiste. Paramètres : `statut`, `from`, `to`, `limit` (25 par défaut), `offset`.

```json
{
  "elements": [
    {
      "grossiste": "Blue Grossiste", "passee_par": "Lucas MARTIN",
      "reference": "GRO-2026-000088", "numero": 88, "mode": "delivery",
      "statut": "completed", "statut_libelle": "Livrée",
      "sous_total": 4200.0, "remise": { "pourcentage": 10.0, "montant": 420.0 }, "frais_livraison": 150.0, "total": 3930.0,
      "prevue_le": "2026-10-21T18:00:00Z", "payee_le": "2026-10-21T15:12:40Z", "livree_le": "2026-10-21T18:22:03Z",
      "manquant": null, "remboursement": null,
      "lignes": [ { "produit": "Pain", "categorie": "food", "unites_par_lot": 20, "prix_du_lot": 210.0, "lots": 20, "lots_prepares": 20 } ]
    }
  ],
  "limit": 25, "offset": 0, "suivant": null
}
```

Statuts : `paid` (reçue), `confirmed` (validée), `preparing`, `ready`, `delivering`, `completed` (livrée), `cancelled`, ainsi que `paying`, `payment_failed` et `payment_review` pendant le paiement.

### `GET /api/v1/employes`

Droit : employés. Chaque membre de l'entreprise avec son grade, s'il est en ville, en service, et sa dernière connexion. Avec le droit « Heures de service », chaque employé porte aussi `service` : secondes de service **aujourd'hui** (depuis minuit), cette **semaine** (depuis lundi), sur **7 jours** et ce **mois**, et `en_service_depuis`.

```json
{
  "elements": [
    {
      "id": 384, "prenom": "Tony", "nom": "DAVID", "patron": false,
      "grade": { "id": 80, "nom": "Capitaine", "couleur": null, "salaire": 0.0 },
      "en_ligne": true, "en_service": true, "derniere_connexion": "2026-10-21T11:05:51Z",
      "service": { "aujourdhui": 5400, "semaine": 14746, "sept_jours": 14746, "mois": 14746, "en_service_depuis": "2026-10-21T20:45:16Z" }
    }
  ]
}
```

L'`id` d'un employé est celui de son personnage : il sert de filtre `employe` dans `/services` et `/fiches-de-paie`.

### `GET /api/v1/grades`

Droit : employés. Les grades de l'entreprise avec leur salaire et leur effectif.

### `GET /api/v1/services`

Droit : heures de service. Chaque prise de service (début, fin, durée en secondes), de la plus récente à la plus ancienne. Paramètres : `employe`, `from`, `to`, `limit`, `offset`. Un service en cours a `fin` à `null` et `en_cours` à `true`.

### `GET /api/v1/fiches-de-paie`

Droit : fiches de paie. Chaque paye versée : minutes travaillées, salaire (taux et montant), prime de l'entreprise (taux, montant, statut `versee`, `impayee` ou `aucune`) et total. Paramètres : `employe`, `from`, `to`, `limit`, `offset`.

### `GET /api/v1/vehicules`

Droit : véhicules. Modèle, plaque, rangé ou sorti, couleur, carburant (%) et kilométrage. La position des véhicules n'est jamais transmise.

## 💻 <mark style="color:red;">Exemples</mark>

Dans tous les exemples, remplacez `bsk_…` par votre clé, idéalement lue depuis une variable d'environnement.

{% tabs %}
{% tab title="Terminal (curl)" %}

```bash
curl -H "Authorization: Bearer bsk_VOTRE_CLE" \
  "https://intranet.blue-side.fr/api/v1/ventes?from=2026-10-01&limit=20"
```

{% endtab %}

{% tab title="JavaScript (Node.js)" %}

```js
const API = 'https://intranet.blue-side.fr/api/v1'
const KEY = process.env.BLUESIDE_API_KEY

async function bluesideApi(path) {
  const res = await fetch(API + path, { headers: { Authorization: `Bearer ${KEY}` } })
  const body = await res.json()
  if (!body.ok) throw new Error(`${res.status} ${body.error.code} : ${body.error.message}`)
  return body.data
}

const compte = await bluesideApi('/compte')
console.log(`Solde : ${compte.solde} €`)

// Toutes les ventes d'octobre, page par page.
let offset = 0
const ventes = []
while (offset !== null) {
  const page = await bluesideApi(`/ventes?from=2026-10-01&to=2026-10-31&limit=100&offset=${offset}`)
  ventes.push(...page.elements)
  offset = page.suivant
}
console.log(`${ventes.length} ventes`)
```

{% endtab %}

{% tab title="Python" %}

```python
import os, requests

API = "https://intranet.blue-side.fr/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['BLUESIDE_API_KEY']}"}

def bluesideApi(path, **params):
    r = requests.get(API + path, headers=HEADERS, params=params, timeout=15)
    body = r.json()
    if not body["ok"]:
        raise RuntimeError(f"{r.status_code} {body['error']['message']}")
    return body["data"]

# Heures de service de la semaine, de l'employé le plus présent au moins présent.
equipe = bluesideApi("/employes")["elements"]
for e in sorted(equipe, key=lambda e: e["service"]["semaine"], reverse=True):
    print(f"{e['prenom']} {e['nom']} : {e['service']['semaine'] / 3600:.1f} h")
```

{% endtab %}

{% tab title="Google Sheets" %}
Dans votre tableur : **Extensions**, **Apps Script**, collez ce code, puis enregistrez la clé dans **Paramètres du projet**, **Propriétés du script** (nom `BLUESIDE_API_KEY`). Lancez `importerVentes` : la feuille « Ventes » se remplit. Ajoutez un déclencheur horaire pour la tenir à jour.

```js
function bluesideApi(path) {
  const key = PropertiesService.getScriptProperties().getProperty('BLUESIDE_API_KEY')
  const res = UrlFetchApp.fetch('https://intranet.blue-side.fr/api/v1' + path, {
    headers: { Authorization: 'Bearer ' + key },
    muteHttpExceptions: true,
  })
  const body = JSON.parse(res.getContentText())
  if (!body.ok) throw new Error(body.error.message)
  return body.data
}

function importerVentes() {
  const data = bluesideApi('/ventes?limit=100')
  const sheet = SpreadsheetApp.getActive().getSheetByName('Ventes') || SpreadsheetApp.getActive().insertSheet('Ventes')
  sheet.clearContents()
  sheet.appendRow(['Date', 'Source', 'Référence', 'Libellé', 'Montant', 'Statut'])
  data.elements.forEach((v) => sheet.appendRow([new Date(v.date), v.source, v.reference, v.libelle, v.montant, v.statut]))
}
```

{% endtab %}

{% tab title="Bot Discord (discord.js)" %}
Annonce chaque nouvelle vente dans un salon, en interrogeant l'API une fois par minute.

```js
const { Client, GatewayIntentBits } = require('discord.js')
const client = new Client({ intents: [GatewayIntentBits.Guilds] })
const SALON = 'ID_DU_SALON'
let derniere = null

async function bluesideApi(path) {
  const res = await fetch('https://intranet.blue-side.fr/api/v1' + path, {
    headers: { Authorization: `Bearer ${process.env.BLUESIDE_API_KEY}` },
  })
  const body = await res.json()
  if (!body.ok) throw new Error(body.error.message)
  return body.data
}

client.once('ready', () => {
  setInterval(async () => {
    try {
      const { elements } = await bluesideApi('/ventes?limit=10')
      const nouvelles = derniere ? elements.filter((v) => v.date > derniere) : []
      if (elements.length) derniere = elements[0].date
      const salon = await client.channels.fetch(SALON)
      for (const v of nouvelles.reverse()) await salon.send(`Nouvelle vente : ${v.libelle}, ${v.montant} €`)
    } catch (e) { console.error(e.message) }
  }, 60_000)
})

client.login(process.env.DISCORD_TOKEN)
```

{% endtab %}
{% endtabs %}

## ✅ <mark style="color:blue;">Bonnes pratiques</mark>

* Une clé **par outil**, avec seulement les droits nécessaires : si un outil est compromis, vous ne révoquez que sa clé.
* Gardez vos clés dans des **variables d'environnement** ou les propriétés du script, jamais dans un fichier partagé ou un dépôt public.
* Respectez les limites : gardez en mémoire les réponses qui changent peu (grades, véhicules) et surveillez `X-RateLimit-Remaining`. En cas de réponse `429`, attendez la minute suivante.
* Pour récupérer un historique complet, parcourez les pages avec `suivant` et une période `from` / `to`, plutôt que de tout redemander à chaque fois.
* Consultez de temps en temps le **journal des appels** de la rubrique API.

{% hint style="info" %}
Une question, une donnée qui vous manque pour votre outil ? Ouvrez un ticket sur le Discord : <https://discord.gg/blueside>.
{% endhint %}

Voir aussi : [L'application Entreprise](/wiki/intranet/entreprise.md) · [Revolut Business](/wiki/economie/revolut-business.md) · [Grossiste](/wiki/intranet/grossiste.md) · [Prendre son service & être payé](/wiki/metiers/travailler.md).
