API GeoPulse — référence v1

Base : https://geo-pulse.app/api/v1

L'API donne accès en lecture seule à ce que GeoPulse produit chaque jour : données de marché de 50 actifs (niveau pour 20, variation seule pour 30), événements géopolitiques scorés par IA, signaux ouverts, indice composite et corrélations. Aucun endpoint n'écrit, ne modifie un compte ni n'expose de donnée personnelle.

Elle est ouverte sur demande, sans prix : les clés sont délivrées à la demande — contactez-nous en décrivant votre usage. Les limites décrites ci-dessous valent par compte.


Authentification

Chaque requête porte votre clé, au choix :

curl -H "X-API-Key: gp_live_votrecle" https://geo-pulse.app/api/v1/prices
curl -H "Authorization: Bearer gp_live_votrecle" https://geo-pulse.app/api/v1/prices

Les clés se créent et se révoquent depuis votre compte → onglet API. Une clé n'est affichée qu'une seule fois, à sa création : nous n'en stockons que l'empreinte (SHA-256), et nous sommes donc incapables de vous la rappeler. Perdue, elle se remplace — révoquez-la et créez-en une autre.

Jusqu'à 5 clés actives simultanément : une par environnement, ce qui permet d'en révoquer une sans interrompre les autres. Les limites ci-dessous sont celles du compte : toutes ses clés les partagent.


Limites

Les limites portent sur le compte, pas sur la clé : toutes vos clés partagent le même débit et le même quota, et la consommation d'une clé révoquée dans la journée reste comptée — révoquer puis recréer une clé ne remet rien à zéro.

valeur
Débit 30 requêtes/minute en continu, rafales de 10 au plus
Quota 10 000 requêtes/jour, remis à zéro à 00:00 UTC

Le débit suit un seau à jetons : le seau contient au plus 10 jetons, chaque requête en consomme un, et il s'en reconstitue 30 par minute, à intervalles réguliers. Après un temps de repos, vous pouvez donc envoyer 10 requêtes d'affilée ; ensuite, 30 par minute au plus, régulièrement espacées.

Chaque réponse porte l'état de vos compteurs :

X-RateLimit-Limit: 10000            X-RateLimit-Limit-Minute: 30
X-RateLimit-Remaining: 9987            X-RateLimit-Remaining-Minute: 9

X-RateLimit-Remaining est le quota journalier restant du compte ; X-RateLimit-Remaining-Minute est le nombre de requêtes que vous pouvez envoyer tout de suite — les jetons entiers restant dans le seau, au plus 10.

Un dépassement de débit renvoie 429 avec un en-tête Retry-After en secondes (le temps qu'un jeton se reconstitue) — et ne consomme pas de quota journalier : une rafale mal réglée ne peut pas vider votre quota sans vous avoir rien rendu. Un dépassement de quota renvoie également 429, jusqu'à la remise à zéro.

À quelle fréquence appeler ?

Nos données ne changent pas plus vite que nous ne les collectons. Appeler au-delà de ces cadences renvoie la même réponse :

donnée rafraîchissement
Cryptos (Kraken) 30 s — variation seule
Actifs Yahoo à collecte rapide 60 s
Actifs observés 15 min
Taux BCE et Fed (8 actifs) relevé quotidien, vérifié toutes les heures
Actifs IEX (10) dernier échange de la veille, publié chaque matin (J+1)
Événements 5 min
Signaux toutes les 2 h, plus dès réception d'un événement de sévérité ≥ 7 (les articles NewsData arrivent ~12 h après publication)
Indice composite toutes les heures
Corrélations toutes les 15 min

Un client qui suit chaque donnée à sa cadence — /prices toutes les 30 s, /events et /signals toutes les 5 min, /correlations toutes les 15 min, /index toutes les heures — consomme environ 3 600 requêtes par jour, sous le quota de 10 000. Les webhooks (en fin de page) évitent d'interroger /signals et /index en boucle.


Endpoints

GET / — documentation exécutable

Public, sans clé. Renvoie la liste des endpoints, les limites en vigueur et les cadences de rafraîchissement. Utile pour vérifier une version sans lire cette page.

GET /assets — registre des actifs

{ "count": 50,
  "assets": [ { "symbol": "XAU", "name": "Or", "category": "commodity", "unit": "$",
                "tier": "signals", "covered_by_rules": true, "family": null } ] }

tier dit la cadence de collecte : signals (collecte rapide, panier de l'indice composite) ou observed (collecte 15 min). covered_by_rules dit, lui, lesquels peuvent produire un signal — et depuis le 24/08/2026 les deux ne se confondent plus : un actif observed peut porter une règle sans entrer dans l'indice. ⚠️ Lire covered_by_rules, jamais tier, pour savoir d'où un signal peut venir.

display (depuis le 27/08/2026) dit ce qui est licencié pour l'affichage : full (prix complet), derived — pour ces actifs, la source du niveau de prix n'est pas licenciée pour la redistribution, donc seuls les dérivés irréversibles sont servis (variation 24 h, direction, RSI — ni niveau, ni historique de niveaux) —, none (depuis le 30/08/2026 : aucune donnée de marché servie, l'actif est absent de /prices et son historique répond 403), ou iex (depuis le 31/08/2026 : les prix servis sont le relevé quotidien « IEX Delayed » — dernier échange de la séance régulière enregistré sur la bourse IEX, publié le lendemain (J+1), horodaté au dernier échange réel ; ce n'est pas la clôture officielle. Data provided for free by IEX — cette attribution accompagne toute redistribution de ces valeurs).

Les cryptos (source Kraken) sont déclarées derived. Leur niveau s'affiche sur geo-pulse.app, mais leur fournisseur n'en accepte que l'affichage sur le site : il n'est servi ni par l'API (variation seule, historique en 403, price_at_signal à null), ni par les webhooks, ni par aucun export.

ecb_cross_rate (depuis le 27/09/2026) vaut true pour les paires que nous calculons à partir des taux de référence de la BCE (USD/JPY, GBP/USD, USD/CHF, USD/INR : la BCE ne publie que des taux contre l'euro). Ce ne sont pas des taux publiés par la BCE ; ils sont fournis à titre d'information — la BCE déconseille de les utiliser pour des transactions.

attributions (depuis le 26/09/2026) : chaque réponse réussie et chaque webhook portent la liste des mentions que les licences de nos sources imposent — source, notice, url. Les taux de change sont les taux de référence de la BCE, disponibles gratuitement sur le site de la Banque centrale européenne ; la mention BCE porte aussi cross_rates — la liste des taux croisés calculés et la phrase qui les déclare. Si vous redistribuez ces valeurs, ces mentions les accompagnent.

GET /prices — variation 24 h de tous les actifs, niveau quand il est publiable

Renvoie variation 24 h et horodatage pour les 50 actifs, et le prix pour les 20 dont le niveau est publiable. Réponse mise en cache 10 secondes (donc toujours plus fraîche que la collecte). ⚠️ Pour un actif display: "derived", l'objet ne porte pas de champ price — seulement change24h, timestamp et derived: true. Pour un actif display: "iex", l'objet porte iex: true : prix et variation sont ceux du relevé quotidien IEX (J+1), la variation est séance sur séance. Un taux croisé calculé depuis la BCE porte ecb_cross_rate: true. refresh_rates donne la cadence de collecte de chaque canal de prix.

GET /prices/:asset — historique d'un actif

Paramètre days (1-365, défaut 30). ⚠️ Pour un actif display: "derived" ou display: "none", la route répond 403 price_display_restricted : un historique de niveaux permettrait de reconstituer la série source. Pour un actif display: "iex", l'historique servi est celui des relevés quotidiens IEX (source: "iex" dans la réponse — série démarrée le 24/08/2026, elle s'allonge chaque nuit).

Une seule source par série (depuis le 27/09/2026) : l'historique ne contient que les cotations de la source dont l'actif est licencié — indiquée par source (ecb, fed…). Les taux BCE et Fed ont remplacé une autre source le 27/08/2026 : leur historique commence à cette date, même si vous demandez 365 jours.

Résolution (champ resolution) : jusqu'à 7 jours, toutes les cotations (raw) ; jusqu'à 90 jours, une par heure (hourly) ; au-delà, une par jour UTC (daily). Chaque point est une cotation réelle — la dernière de son heure ou de son jour, avec son propre horodatage ; aucune valeur n'est moyennée ni interpolée, et une période sans cotation reste un trou.

curl -H "X-API-Key: $KEY" "https://geo-pulse.app/api/v1/prices/EUR?days=90"

Un symbole inconnu renvoie 404 avec la liste des symboles connus — pas besoin d'aller la chercher ailleurs.

GET /events — événements géopolitiques scorés

Paramètre limit (1-100, défaut 20). Chaque événement porte son titre, le lien vers l'article (url), son score de sévérité (0-10, issu du scoring IA), son niveau, sa catégorie, sa région, sa source et sa date de publication. Le texte de l'article n'est pas servi : depuis le 27/09/2026, summary vaut toujours null — ce texte appartient à son éditeur, suivez le lien.

GET /signals — signaux ouverts

Les décisions non encore résolues : règle, actif, direction, magnitude attendue, horizon, confiance, prix à l'émission, date de résolution prévue. Une ligne par décision : une décision suivie sur plusieurs horizons n'est listée qu'une fois, par son horizon principal (depuis le 27/09/2026). price_at_signal vaut null pour un actif display: "derived" — le niveau de ces actifs n'est pas licencié pour la redistribution.

Ce sont des signaux algorithmiques, pas des conseils en investissement. La méthode, les abstentions et le track record scellé sont publics sur geo-pulse.app/scoreboard.

GET /index — indice composite GeoPulse

Score 0-100 et ses cinq sous-scores (géopolitique, sentiment, stress, momentum, volatilité). Paramètre history (1-500) pour la série.

{ "index": { "score": 62, "classification": "elevated",
             "components": { "geo": 71, "sentiment": 48, "stress": 55,
                             "momentum": 75, "volatility": 60 },
             "component_sources": { "sentiment": {
               "source": "Alternative.me",
               "notice": "Crypto Fear & Greed Index courtesy of Alternative.me",
               "url": "https://alternative.me/crypto/fear-and-greed-index/" } },
             "calculated_at": 1784800000000 } }

Le sous-score sentiment est le Crypto Fear & Greed Index d'Alternative.me : sa licence exige que l'attribution figure à côté de la valeur. Elle est donc posée contre le champ (component_sources.sentiment) et reprise dans attributions — si vous affichez cette valeur, affichez la mention avec elle.

GET /correlations — corrélations entre actifs

Pearson sur 30 jours glissants, recalculé toutes les 15 minutes. computed_at date le calcul servi. Si aucun calcul n'existe encore, la réponse porte status: "unavailable" et une liste vide — jamais des valeurs par défaut.

GET /usage — votre consommation

Paramètre days (1-90, défaut 30). Renvoie la consommation du jour et l'historique quotidien du compte — toutes ses clés, révoquées comprises, puisque c'est ce total que le quota compare — avec les limites en vigueur (rate_per_minute, rate_burst, daily_quota).


Codes de réponse

code signification que faire
200 Succès —
401 api_key_required Aucune clé fournie Ajoutez l'en-tête
401 api_key_invalid Clé inconnue Vérifiez la clé ; elle a peut-être été régénérée
401 api_key_revoked Clé révoquée Créez-en une nouvelle depuis votre compte
403 api_plan_required Le compte n'a plus l'accès API Contactez-nous
404 unknown_asset Symbole inconnu La réponse liste les symboles valides
429 rate_limit_exceeded Débit dépassé Attendez Retry-After secondes
429 quota_exceeded Quota journalier atteint Attendez 00:00 UTC

Le tier du compte est relu à chaque requête : un retrait de l'accès coupe l'accès immédiatement, sans qu'il faille révoquer les clés une par une. Il en va de même pour les webhooks : un compte qui quitte le plan API cesse aussitôt d'en recevoir, et les envois en attente sont annulés.


Exemple complet

export KEY="gp_live_votrecle"
BASE="https://geo-pulse.app/api/v1"

# Indice composite et ses composantes
curl -s -H "X-API-Key: $KEY" "$BASE/index" | jq '.index'

# Les 5 événements les plus récents, avec leur score de sévérité
curl -s -H "X-API-Key: $KEY" "$BASE/events?limit=5" | jq '.events[] | {title, score}'

# Historique de l'EUR/USD (taux de référence BCE) sur 90 jours, une cotation par heure
curl -s -H "X-API-Key: $KEY" "$BASE/prices/EUR?days=90" | jq '{source, resolution, count}'

# Consommation restante
curl -sD- -o/dev/null -H "X-API-Key: $KEY" "$BASE/prices" | grep -i ratelimit

Appels depuis un navigateur (CORS)

L'API se consomme depuis un serveur. Une requête portant un en-tête Origin d'un autre domaine que geo-pulse.app reçoit 403 cors_origin_forbidden, sans en-tête CORS : une clé d'API placée dans le JavaScript d'une page serait lisible par chacun de ses visiteurs, qui consommeraient votre quota. Pour afficher nos données dans un navigateur, appelez l'API depuis votre backend et servez-en le résultat.


Stabilité et versionnage

La v1 est additive : de nouveaux champs peuvent apparaître, aucun champ existant ne sera retiré ni renommé sans une v2. Écrivez vos clients en ignorant les champs inconnus.

Les réponses sont normalisées et ne reflètent pas le schéma interne de la base : nos tables peuvent évoluer sans casser votre intégration.

Nous ne publions pas d'engagement de disponibilité (SLA). L'infrastructure est un serveur unique ; annoncer 99,9 % serait un chiffre que nous ne pouvons pas encore tenir.


Webhooks

Plutôt que d'interroger l'API en boucle, faites-vous prévenir. Les endpoints se configurent depuis votre compte → onglet API → Webhooks : ajout (URL et événements choisis), suppression, renouvellement du secret, réactivation, historique des livraisons.

Événements disponibles (4) :

événement quand il part
signal.created une décision vient d'être émise. Les observations (signaux hors statistiques, jamais publiés) et les horizons secondaires d'une même décision ne partent pas
signal.resolved une décision arrive à échéance et reçoit son verdict. Une échéance sans mouvement significatif (« expirée ») n'est pas un verdict et ne part pas
event.high_severity un événement géopolitique de sévérité ≥ 7 sur 10 vient d'être reçu — le seuil qui déclenche aussi une génération de signaux
index.updated l'indice composite vient d'être recalculé (toutes les heures)

Ce que vous recevez

POST https://votre-domaine/hook
Content-Type: application/json
X-GeoPulse-Event: signal.created
X-GeoPulse-Timestamp: 1784800000
X-GeoPulse-Signature: sha256=<hmac>
X-GeoPulse-Delivery: 4213

{ "type": "signal.created", "created_at": 1784800000000, "data": { … }, "attributions": [ … ] }

attributions est la même liste que dans les réponses de l'API : les mentions que les licences de nos sources imposent.

La charge, événement par événement

data ne porte que les champs ci-dessous — les mêmes noms que dans l'API. Aucun niveau de prix ne sort pour un actif dont le niveau n'est pas licencié (display autre que "full" dans GET /assets) : le champ vaut alors null.

signal.created

{ "id": 812, "rule": "rule_name", "asset": "XAU", "direction": "up",
  "magnitude_pct": 1.8, "timeframe_days": 5, "confidence": 61,
  "price_at_signal": null, "triggered_at": 1784800000000,
  "resolve_at": 1785232000000, "status": "pending" }

price_at_signal est le niveau à l'émission, seulement pour un actif display: "full". resolve_at est l'échéance planifiée (triggered_at + timeframe_days).

signal.resolved — les champs de signal.created, plus le verdict :

{ "id": 812, "rule": "rule_name", "asset": "XAU", "direction": "up",
  "magnitude_pct": 1.8, "timeframe_days": 5, "confidence": 61,
  "price_at_signal": null, "triggered_at": 1784800000000,
  "resolve_at": 1785232000000, "status": "resolved",
  "resolved_at": 1785232900000, "was_correct": true, "actual_direction": "up",
  "actual_pct_change": 2.4, "price_at_resolution": null }

actual_pct_change est la variation constatée, en %, de l'émission à la résolution.

event.high_severity

{ "id": 50231, "title": "…", "url": "https://…", "severity": 8, "category": "conflict" }

Le titre et le lien vers l'article — jamais son texte, qui appartient à son éditeur. severity est le score à la réception ; il peut être affiné ensuite par le scoring IA, et GET /events sert le score à jour.

index.updated — les champs de GET /index :

{ "score": 62, "classification": "elevated",
  "components": { "geo": 71, "sentiment": 48, "stress": 55,
                  "momentum": 75, "volatility": 60 },
  "component_sources": { "sentiment": { "source": "Alternative.me", "notice": "…", "url": "…" } },
  "calculated_at": 1784800000000 }

La mention d'Alternative.me voyage contre components.sentiment et dans attributions : si vous affichez cette valeur, affichez la mention avec elle.

Vérifier la signature — à faire systématiquement

La signature est un HMAC-SHA256 de <timestamp>.<corps exact reçu>, avec le secret affiché à la création de l'endpoint.

import crypto from 'node:crypto';

function verify(rawBody, headers, secret) {
  const ts = headers['x-geopulse-timestamp'];
  const expected = crypto.createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex');
  const got = (headers['x-geopulse-signature'] || '').replace('sha256=', '');
  // Comparaison à temps constant : une comparaison naïve fuit la signature.
  const ok = got.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
  // L'horodatage fait partie de la signature : refusez ce qui est trop vieux,
  // sinon un appel intercepté peut être rejoué indéfiniment.
  return ok && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
}

⚠️ Signez le corps brut, avant tout JSON.parse. Une re-sérialisation change les espaces et invalide la signature.

Livraison et nouvelles tentatives

Une réponse 2xx vaut accusé de réception. Tout le reste — code d'erreur, redirection, délai dépassé (10 s), connexion refusée — déclenche une nouvelle tentative, chacune après un délai croissant : 1 min, 5 min, 15 min, 1 h, 6 h, 24 h. Un événement est donc tenté 7 fois sur environ 31 h, puis abandonné.

La livraison est au moins une fois : rarement (un redémarrage de notre serveur pendant l'envoi), un même envoi peut arriver deux fois — dédoublonnez sur X-GeoPulse-Delivery, qui reste le même d'une tentative à l'autre. L'ordre d'arrivée n'est pas garanti : triez sur created_at.

Après 10 événements consécutifs abandonnés — chacun après toutes ses tentatives, sans aucune livraison réussie entre deux — l'endpoint est désactivé : il cesse de recevoir, et ses envois encore en attente sont annulés. Une panne de quelques heures ne désactive donc rien. Le journal reste consultable ; réactivez l'endpoint depuis votre compte après correction — seuls les nouveaux événements lui seront envoyés.

Répondez vite (< 10 s) : mettez le traitement en file de votre côté plutôt que de le faire pendant la requête.

Contraintes

HTTPS obligatoire, adresses internes refusées, redirections non suivies, 5 endpoints actifs maximum. Le secret n'est affiché qu'à la création — il se régénère, il ne se relit pas ; dès qu'il est renouvelé, les envois suivants, tentatives en cours comprises, sont signés avec le nouveau. Le journal des livraisons est conservé 30 jours.

Les webhooks sont réservés au plan API : un compte qui le quitte cesse aussitôt d'en recevoir, et ses envois en attente sont annulés.