API de lecture du portefeuille externe
Authentifiez, signez et lisez la composition cible des stratégies, leur historique optionnel de prix de marché, les métadonnées des clients liés, les instantanés de portefeuille et les demandes d'invitation partenaires.
L'API externe de portefeuille expose la composition cible actuelle des stratégies, les données de portefeuille et les demandes d'invitation partenaires (entrée en relation investisseurs) via des points de terminaison protégés par HMAC, destinés aux acteurs externes qui utilisent des identifiants d'équipe activés. Un jeu de clés API est limité à une seule équipe et peut lire tous les comptes clients liés à cette équipe. Le point de terminaison des stratégies renvoie les trois mêmes compositions de base globales éligibles à chaque équipe activée. L'envoi d'invitations est une capacité distincte qu'Iceblock active par équipe.
Remplacez les identifiants de clé et les identifiants de compte des exemples par les identifiants délivrés à votre équipe.
Sommaire
- Démarrage rapide
- Activation de l'équipe et statut de la clé
- Environnements
- Authentification
- Conventions
- GET /strategies
- GET /clients
- POST /portfolio/latest
- POST /portfolio/history
- Invitations
- Erreurs
- Limites de débit et identifiants
- Conseils d'intégration
Démarrage rapide
- Demandez à un propriétaire d'équipe d'ouvrir Accès API (
/home/{team-slug}/external-api) et de vérifier Accès API externe de l'équipe. Si le statut indique Activation requise, contactez Iceblock pour demander l'activation de cette équipe. Créer une clé API n'active pas l'accès API. - Créez une clé et copiez immédiatement l'identifiant public et le secret HMAC unique. Avant d'envoyer des requêtes, confirmez que l'accès équipe est Activé et que le Statut de la clé est Active.
- Signez chaque requête avec HMAC-SHA256 (voir Authentification).
- Appelez
GET /clientspour découvrir les valeursaccount_uidliées. - Appelez
POST /portfolio/latestavec ces identifiants pour l'instantané courant. - Utilisez
POST /portfolio/historypour les reprises d'historique. Conservez les plages dans le plafond d'historique de l'équipe (366 jours UTC inclus par défaut). - Appelez
GET /strategiessans corps ni requête pour afficher les allocations cibles actuelles Fundamental, Dynamic et Innovation. Ajoutezinclude=token_price_history,fromettopour lire les prix de marché USD disponibles pour ces allocations. Les identifiants de comptes clients ne sont pas nécessaires pour cet appel. - Si Iceblock a activé l'envoi d'invitations pour votre équipe, appelez
POST /invitationspour soumettre un prospect, puis interrogezGET /invitationsouGET /invitations/{id}pour le statut. Cette API n'envoie pas d'e-mail au prospect et n'émet pas de webhook.
Chaque réponse inclut x-request-id. Transmettez cette valeur lorsque vous ouvrez un ticket de support.
Activation de l'équipe et statut de la clé
Iceblock active l'accès API externe séparément pour chaque équipe partenaire et chaque environnement. Créer, faire tourner ou réactiver une clé ne change pas cette activation. Des clés peuvent être créées avant l'activation ; le statut d'équipe reste visible pendant la rotation et dans la boîte de dialogue du secret unique.
| Accès API externe de l'équipe | Statut de la clé | Accès |
|---|---|---|
| Activation requise (y compris paramètres d'équipe manquants) | Active, Disabled ou Revoked | Les requêtes sont bloquées jusqu'à l'activation de l'équipe par Iceblock. |
| Enabled | Active | La clé peut authentifier les requêtes, sous réserve de la signature, de l'autorisation des clients liés, de la disponibilité globale et des limites configurées. |
| Enabled | Disabled | Cette clé est temporairement bloquée ; un propriétaire d'équipe peut la réactiver. |
| Enabled | Revoked | Cette clé est définitivement bloquée ; créez une clé de remplacement. |
Créer une clé API n'active pas l'accès API. Contactez Iceblock pour demander l'activation de cette équipe. Actualisez la page Accès API après confirmation d'Iceblock pour voir le statut enregistré. L'activation ne réactive pas les clés désactivées et ne restaure pas les clés révoquées.
Environnements
| Environnement | URL de base |
|---|---|
| Staging | https://www.staging.app.ice-block.net/api/v1/b2b |
| Production | https://app.iceblockinvestments.com/api/v1/b2b |
Toutes les requêtes doivent utiliser HTTPS. Chemins relatifs à l'URL de base :
GET /clientsGET /strategiesPOST /portfolio/latestPOST /portfolio/historyPOST /invitationsGET /invitationsGET /invitations/{id}
Authentification
Chaque requête est authentifiée par une signature HMAC-SHA256 portant sur la méthode, le chemin, la chaîne de requête canonique, le hachage du corps et l'horodatage.
| En-tête | Obligatoire | Description |
|---|---|---|
x-iceblock-key | Oui | Identifiant public de la clé API délivré à votre équipe. |
x-timestamp | Oui | Horodatage Unix en secondes (UTC). Sert à la protection contre le rejeu. |
x-signature | Oui | sha256=<hmac-encodé-en-base64> calculé à partir de la chaîne à signer. |
Chaîne à signer
METHOD PATHNAME CANONICAL_QUERY BODY_SHA256 TIMESTAMP
METHOD: méthode HTTP en majuscules (GET,POST).PATHNAME: le chemin exact de la requête, par exemple/api/v1/b2b/portfolio/latest.CANONICAL_QUERY: la chaîne de requête normalisée, ou la chaîne vide s'il n'y a aucun paramètre.BODY_SHA256: SHA-256 hexadécimal en minuscules du corps brut. PourGET, hachez la chaîne vide.TIMESTAMP: le même horodatage Unix que celui envoyé dansx-timestamp.
Règles de requête canonique
- Analysez chaque paramètre de requête depuis l'URL.
- Triez les paramètres par clé croissante.
- Si une clé apparaît plusieurs fois, triez par valeur croissante.
- Encodez les clés et valeurs avec
encodeURIComponent. - Joignez chaque paire en
<key>=<value>et concaténez-les avec&. - Utilisez la chaîne vide lorsqu'il n'y a aucun paramètre.
Protection contre le rejeu
- L'écart d'horloge autorisé est de
±300secondes. - Les requêtes hors de cette fenêtre renvoient
403avectimestamp_out_of_window. - Un en-tête
x-timestampmanquant renvoie400avecmalformed_headers. Un horodatage présent mais mal formé renvoie400avecmalformed_timestamp.
Exemple de signature (Node.js)
const crypto = require('node:crypto');
function canonicalQuery(searchParams) {
return Array.from(searchParams.entries())
.sort(([keyA, valueA], [keyB, valueB]) => {
if (keyA === keyB) {
return valueA.localeCompare(valueB);
}
return keyA.localeCompare(keyB);
})
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
)
.join('&');
}
function sha256Hex(input) {
return crypto.createHash('sha256').update(input).digest('hex');
}
function signRequest({ method, pathname, searchParams, body, timestamp, secret }) {
const rawBody = body ?? '';
const stringToSign = [
method.toUpperCase(),
pathname,
canonicalQuery(searchParams),
sha256Hex(rawBody),
String(timestamp),
].join('\n');
const hmac = crypto
.createHmac('sha256', secret)
.update(stringToSign)
.digest('base64');
return `sha256=${hmac}`;
}
Notes d'implémentation
- Pour
GET, signez avecbody = ''. - Pour
POST, signez la chaîne JSON brute exacte qui sera envoyée. Ne reformatez pas les espaces et ne réordonnez pas les clés JSON entre la signature et l'envoi. - Si votre client HTTP sérialise le JSON pour vous, hachez la chaîne sérialisée, pas un objet en mémoire.
- Les écarts de signature viennent presque toujours du hachage d'un objet parsé au lieu du corps brut, d'un ordre de query string différent entre signature et envoi, ou d'un chemin signé différent de celui demandé.
Conventions
- Les montants monétaires et les quantités d'actifs sont renvoyés en chaînes pour préserver la précision décimale. Ne les analysez pas comme des flottants IEEE pour la comptabilité.
- Les UUID sont des chaînes ; les horodatages sont ISO 8601 UTC ; les dates d'historique sont
YYYY-MM-DD. - Une clé API couvre tous les comptes clients liés à votre équipe. Un
account_uidqui existe mais n'est pas lié à votre équipe renvoienot_authorized. account_uidsaccepte jusqu'à 200 UUID par requête ; les doublons sont ignorés après la première occurrence.- Les corps de requête doivent rester sous 64 Kio.
- Chaque réponse sérialisée est limitée à 4 000 000 octets. Le serveur ne découpe jamais un résultat client ou portefeuille sur plusieurs pages.
- Une exposition d'emprunt utilise un
value_usdnégatif.is_stablecoin = trueidentifie les positions en stablecoins. - Dans l'historique, chaque entrée quotidienne est le dernier instantané à la fin de ce jour UTC.
is_carry_forward = truesignifie qu'un instantané antérieur a été réutilisé. - Les réponses de succès et d'erreur définissent
cache-control: no-storeet incluentx-request-id.
Pagination et compatibilité
La pagination de clients/latest/history est optionnelle. Les requêtes existantes sans page_size conservent la forme de réponse complète d'origine lorsque la réponse tient dans 4 000 000 octets. Sinon, l'API renvoie 422 pagination_required ; réessayez avec page_size. GET /strategies n'accepte aucun paramètre de pagination et renvoie une composition complète ou 422 response_item_too_large. GET /invitations pagine toujours : page_size vaut 25 par défaut et accepte 1–100.
Lorsque page_size est présent, la réponse inclut aussi :
{
"pagination": {
"page_size": 100,
"returned_count": 100,
"has_more": true,
"next_cursor": "opaque-cursor"
}
}
Soumettez next_cursor comme cursor avec la même route et les mêmes filtres pour récupérer la page suivante. Un curseur est opaque et lié à l'identifiant, à la route et à la portée normalisée. Modifier la liste de comptes, les filtres, la plage de dates, include_positions, include, defi_earnings_period ou page_size l'invalide. Les requêtes avec curseur doivent inclure page_size.
Signez chaque page comme une nouvelle requête HMAC avec sa query string ou son corps JSON exact. Chaque page consomme une unité de quota. La rotation de clé révoque immédiatement l'ancienne clé : les curseurs de cette clé ne peuvent pas être poursuivis avec la clé de remplacement ; recommencez la pagination.
GET /strategies
Renvoie les allocations cibles actuelles de Fundamental, Dynamic et Innovation via l'identifiant d'équipe activé. Ce point de terminaison partagé de composition de stratégies v1 renvoie les trois mêmes définitions cibles globales à chaque équipe activée. Les allocations cibles décrivent la composition prévue. Les avoirs clients réels restent exposés par les points de terminaison d'instantané de portefeuille, dans le périmètre d'accès de votre équipe.
Requête et disponibilité
Envoyez un GET authentifié avec un corps vide et sans query string. La clé existante, les paramètres globaux/équipe, la fenêtre de rejeu et les quotas Redis s'appliquent. Chaque appel consomme une unité de quota. L’historique optionnel des prix utilise les paramètres décrits ci-dessous ; les versions, autres filtres, page_size et curseurs renvoient 400 invalid_query ; un corps non vide renvoie 400 invalid_body.
Omettez Transfer-Encoding. Si Content-Length est fourni, il doit valoir zéro. Un cadrage de corps positif ou mal formé renvoie 400 invalid_body ; une Content-Length déclarée au-delà du plafond de 64 Kio conserve 422 body_too_large.
La réponse contient exactement trois stratégies de base globales actives, dans cet ordre : Fundamental (fundamental), Dynamic (dynamic), Innovation (innovation). Les définitions stablecoin, personnalisées, archivées, détenues par une équipe ou privées sont exclues. Une composition requise manquante ou invalide renvoie 503 strategy_composition_unavailable avec l'enveloppe d'erreur habituelle et le message sûr Current target strategy composition is unavailable. Les échecs de base ou de transport conservent 500 internal_error. Aucun succès partiel ni composition de repli n'est renvoyé.
Les réponses de succès et d'erreur utilisent Cache-Control: no-store et x-request-id. La composition complète est atomique et limitée à 4 000 000 octets sérialisés ; une composition trop volumineuse renvoie 422 response_item_too_large. Ce point de terminaison n'a pas de pagination.
Exemple GET signé (Node.js)
Enregistrez cet exemple autonome sous strategies.cjs, configurez EXTERNAL_PORTFOLIO_BASE_URL (l'origine de l'application), EXTERNAL_PORTFOLIO_API_KEY et EXTERNAL_PORTFOLIO_HMAC_SECRET via votre environnement sécurisé, puis exécutez node strategies.cjs. Il signe la méthode/le chemin exacts, une query canonique vide et le SHA-256 du corps vide. Il n'envoie aucun corps et n'affiche pas d'identifiants.
const { createHash, createHmac } = require('node:crypto');
function signedStrategiesRequest({ baseUrl, apiKey, secret }) {
const url = new URL('/api/v1/b2b/strategies', baseUrl);
const timestamp = String(Math.floor(Date.now() / 1000));
const emptyBodyHash = createHash('sha256').update('').digest('hex');
// The empty third line is the canonical query; GET sends no body.
const stringToSign = ['GET', url.pathname, '', emptyBodyHash, timestamp].join('\n');
const signature = createHmac('sha256', secret).update(stringToSign).digest('base64');
return {
url: url.href,
options: {
method: 'GET',
headers: {
'x-iceblock-key': apiKey,
'x-timestamp': timestamp,
'x-signature': 'sha256=' + signature,
},
},
};
}
async function readStrategies() {
const baseUrl = process.env.EXTERNAL_PORTFOLIO_BASE_URL;
const apiKey = process.env.EXTERNAL_PORTFOLIO_API_KEY;
const secret = process.env.EXTERNAL_PORTFOLIO_HMAC_SECRET;
if (!baseUrl || !apiKey || !secret) throw new Error('Configure the three external API environment variables');
const request = signedStrategiesRequest({ baseUrl, apiKey, secret });
const response = await fetch(request.url, request.options);
if (!response.ok) {
throw new Error('HTTP ' + response.status + '; request id ' + response.headers.get('x-request-id'));
}
console.log(JSON.stringify(await response.json(), null, 2));
}
readStrategies().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
Exemple de réponse synthétique
Ces UUID, pondérations et nombres d'actifs sont illustratifs. Lisez la réponse courante pour vos graphiques ; le serveur n'a pas de nombre d'actifs fixe ni de pondérations par défaut.
{
"generated_at": "2026-10-09T06:30:00.000Z",
"data": [
{
"strategy_uid": "30000000-0000-4000-8000-000000000002",
"slug": "fundamental",
"name": "Fundamental",
"allocations": [
{
"asset_uid": "20000000-0000-4000-8000-000000000001",
"symbol": "BTC",
"name": "Bitcoin",
"allocation_bps": 7500,
"allocation_percent": "75.00"
},
{
"asset_uid": "20000000-0000-4000-8000-000000000002",
"symbol": "ETH",
"name": "Ethereum",
"allocation_bps": 2500,
"allocation_percent": "25.00"
},
{
"asset_uid": "20000000-0000-4000-8000-000000000004",
"symbol": "SOL",
"name": "Solana",
"allocation_bps": 0,
"allocation_percent": "0.00"
}
]
},
{
"strategy_uid": "30000000-0000-4000-8000-000000000003",
"slug": "dynamic",
"name": "Dynamic",
"allocations": [
{
"asset_uid": "20000000-0000-4000-8000-000000000001",
"symbol": "BTC",
"name": "Bitcoin",
"allocation_bps": 5000,
"allocation_percent": "50.00"
},
{
"asset_uid": "20000000-0000-4000-8000-000000000002",
"symbol": "ETH",
"name": "Ethereum",
"allocation_bps": 5000,
"allocation_percent": "50.00"
}
]
},
{
"strategy_uid": "30000000-0000-4000-8000-000000000004",
"slug": "innovation",
"name": "Innovation",
"allocations": [
{
"asset_uid": "20000000-0000-4000-8000-000000000004",
"symbol": "SOL",
"name": "Solana",
"allocation_bps": 10000,
"allocation_percent": "100.00"
}
]
}
]
}
Champs de réponse et ordre
| Champ | Type | Signification |
|---|---|---|
generated_at | string (ISO8601) | Instant UTC de génération de la réponse, échantillonné après la lecture. Ce n'est ni l'heure de modification de la composition ni une version. |
data | array | Exactement les trois stratégies dans l'ordre ci-dessus. |
strategy_uid | string (uuid) | portfolio_strategies.id IceBlock. |
slug, name | string | Slug autorisé et nom d'affichage courant. |
allocations | array | Jeu cible courant complet et non vide, y compris les lignes à poids zéro. |
allocations[].asset_uid | string (uuid) | portfolio_strategy_assets.id IceBlock ; ni un id CMC ni un id de compte client. |
allocations[].symbol, allocations[].name | string | Symbole de jeton exact en majuscules et dénomination du catalogue. |
allocations[].allocation_bps | integer | De 0 à 10000 ; chaque stratégie totalise exactement 10000 bps. |
allocations[].allocation_percent | string | Pourcentage dérivé exactement des bps avec deux décimales : 1 → "0.01", 2500 → "25.00", 10000 → "100.00". |
Les allocations sont ordonnées par points de base décroissants, puis symbole croissant, puis UID d'actif croissant, via une comparaison déterministe de points de code. Une ligne à 0 bps reste présente et signifie une exposition cible nulle. Un même UID d'actif conserve un seul symbole et une seule dénomination dans une réponse ; une référence de catalogue ambiguë ou invalide rend la composition indisponible.
Chaque requête lit une composition validée cohérente. Un remplacement d'allocation en cours produit l'ensemble précédent ou suivant complet ; la première lecture après validation reflète les pondérations et actifs ajoutés ou retirés. generated_at est échantillonné pour chaque réponse, même si les allocations restent identiques ; les valeurs peuvent coïncider. Sans l’option token_price_history, la réponse contient uniquement la composition cible actuelle. L’option ajoute les prix de marché disponibles pour les allocations actuelles ; elle ne reconstitue ni les compositions passées, ni les performances, ni les données clients.
Historique optionnel des prix de marché des tokens
GET /api/v1/b2b/strategies?include=token_price_history&from=YYYY-MM-DD&to=YYYY-MM-DD&resolution=auto ajoute un historique de prix de marché USD aux allocations actuelles Fundamental, Dynamic et Innovation. Les dates UTC from et to sont obligatoires et inclusives. resolution accepte auto (par défaut), daily ou weekly. Sans paramètre, la réponse de composition existante reste identique.
Utilisez une clé d'équipe active avec l'accès API de l'équipe activé. Créer une clé n'active pas cet accès. La signature HMAC porte sur la méthode, le chemin, tous les paramètres de requête, le hash SHA-256 du corps vide et l'horodatage. Les règles de révocation, fenêtre de rejeu de 300 secondes, quotas et Cache-Control: no-store s'appliquent. Chaque lecture consomme une requête. Pour les autres routes et l'activation, consultez le guide External Portfolio Read API.
Le champ frère token_price_history contient requested, effective_to, currency: "USD", resolution, interval_minutes, cadence_tolerance_seconds et tokens. Ce dernier est indexé par l'asset_uid des allocations, avec une seule entrée par token actuel, y compris les allocations à zéro. La lecture ne dépend pas des positions d'un client et ne reconstitue pas les compositions historiques. Les correspondances entre UUID et catalogues sont persistées et vérifiées ; un nouveau token non référencé reste visible avec le statut indisponible.
| Nombre de dates UTC inclusives | Intervalle automatique |
|---|---|
| Jusqu'à 7 | 240 minutes |
| Jusqu'à 30 | 720 minutes |
| Jusqu'à 366, année normale ou bissextile incluse | 4320 minutes |
| Au-delà | 10080 minutes, une semaine exacte |
daily utilise 1440 minutes et weekly 10080 minutes. La grille commence à minuit UTC au début demandé et inclut la borne finale effective. Sans observation admissible antérieure à ce début, le premier instant observé dans la période est inclus comme ouverture et les points précédents sont omis. Si un prix antérieur sert d'ancre d'ouverture, les points suivent la grille et la borne finale : la première observation dans la période n'est pas automatiquement ajoutée comme point supplémentaire. Les observations admissibles entre deux points restent comptées dans la couverture. Pour aujourd'hui, la fin effective s'arrête à l'heure de la requête. Une date de fin future est refusée.
Chaque point contient price_usd sous forme de chaîne décimale exacte, at, observed_at, carried_forward, source (custody ou cefi), source_asset_id, snapshot_uid et lineage (legacy ou cutover). Ne convertissez pas les prix en nombres flottants. La dernière observation admissible antérieure ou égale au point est utilisée ; à instant strictement égal, custody précède CeFi. observed_at est l'instant effectif du snapshot persisté, pas l'heure native d'un tick de marché. Il n'y a ni interpolation, ni ajout de zéro, ni utilisation d'une observation future. Les observations reportées sont explicitement marquées.
Chaque token comporte status, reasons, coverage et points. La couverture expose les première/dernière observations, observation_count (nombre d'instants effectifs distincts, ancre d'ouverture incluse), max_gap_seconds, première/dernière dates tracées et carried_forward_points. Un intervalle grossier ou des points reportés ne prouvent pas une année complète d'observations.
| Statut | Signification |
|---|---|
available | Données vérifiées satisfaisant les critères d'ouverture, de fraîcheur et d'écart ; des points peuvent être reportés. |
partial | Données utilisables avec late_opening, stale_final et/ou sparse_observations. |
empty | Catalogues vérifiés sans observation admissible jusqu'à la fin effective ; motif no_observations. |
unavailable | mapping_missing, mapping_conflict, ambiguous_observations, invalid_observation, source_work_limit ou source_unavailable ; aucun point inventé. |
late_opening signale une première observation postérieure au début demandé. stale_final signale une dernière observation précédant la fin effective de plus de 172800 secondes, et sparse_observations un écart interne supérieur à cette valeur. Cette tolérance de qualité de 48 heures n'est pas une garantie de fréquence du fournisseur. L'historique peut commencer tard et présenter des écarts importants ; aucune année entièrement observée n'est promise. Une lecture de source en échec produit unavailable, distinct d'une lecture vérifiée sans résultat (empty).
La limite de l'équipe est de 366 dates inclusives par défaut, au maximum 3660. Une date mal formée produit 400 invalid_query ; une période inversée ou une fin future produit 422 invalid_date_range avant toute erreur de dépassement de durée. Une période passée valide dépassant la limite produit 422 history_range_too_large. Une réponse comporte au maximum 128 tokens, 12000 points potentiels et 4 000 000 octets sérialisés. 422 history_point_limit_exceeded ou 422 response_item_too_large demande une résolution hebdomadaire ou des périodes adjacentes plus courtes : par exemple janvier–juin jusqu'au 2025-06-30, puis à partir du 2025-07-01. Signez chaque URL séparément et comparez les UUID d'allocation avant de combiner les résultats : chaque requête lit la composition actuelle indépendamment. Cette route n'accepte ni curseur ni pagination ; un paramètre non reconnu produit 400 invalid_query.
Exemples signés : courte période, mois, année
Enregistrez le code suivant dans token-price-history.cjs. Configurez EXTERNAL_PORTFOLIO_BASE_URL (origine de l'application), EXTERNAL_PORTFOLIO_API_KEY et EXTERNAL_PORTFOLIO_HMAC_SECRET dans l'environnement sécurisé, puis exécutez node token-price-history.cjs. Il signe trois lectures fraîches et affiche les résultats réellement renvoyés, sans données de marché fabriquées. Pour une résolution imposée, passez resolution: 'daily' ou 'weekly' à signedTokenHistoryRequest et signez la nouvelle URL.
const { createHash, createHmac } = require('node:crypto');
function signedTokenHistoryRequest({
baseUrl,
apiKey,
secret,
from,
to,
resolution = 'auto',
}) {
const url = new URL('/api/v1/b2b/strategies', baseUrl);
url.search = new URLSearchParams({
include: 'token_price_history',
from,
to,
resolution,
}).toString();
const canonicalQuery = [...url.searchParams.entries()]
.sort(([a, av], [b, bv]) => a.localeCompare(b) || av.localeCompare(bv))
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
)
.join('&');
const timestamp = String(Math.floor(Date.now() / 1000));
const stringToSign = [
'GET',
url.pathname,
canonicalQuery,
createHash('sha256').update('').digest('hex'),
timestamp,
].join('\n');
return {
url: url.href,
method: 'GET',
headers: {
'x-iceblock-key': apiKey,
'x-timestamp': timestamp,
'x-signature': `sha256=${createHmac('sha256', secret).update(stringToSign).digest('base64')}`,
},
};
}
async function readTokenHistories() {
const baseUrl = process.env.EXTERNAL_PORTFOLIO_BASE_URL;
const apiKey = process.env.EXTERNAL_PORTFOLIO_API_KEY;
const secret = process.env.EXTERNAL_PORTFOLIO_HMAC_SECRET;
if (!baseUrl || !apiKey || !secret)
throw new Error('Secure API configuration is required');
const origin = new URL(baseUrl);
if (
origin.protocol !== 'https:' &&
!['localhost', '127.0.0.1'].includes(origin.hostname)
) {
throw new Error('HTTPS is required');
}
const today = new Date();
const to = today.toISOString().slice(0, 10);
const midnight = Date.parse(`${to}T00:00:00.000Z`);
// Inclusive UTC dates: 7, 30, and 366 days. No fabricated market observations.
for (const [label, daysBack] of [
['short', 6],
['month', 29],
['year', 365],
]) {
const from = new Date(midnight - daysBack * 86400000)
.toISOString()
.slice(0, 10);
const request = signedTokenHistoryRequest({
baseUrl,
apiKey,
secret,
from,
to,
});
const response = await fetch(request.url, {
method: request.method,
headers: request.headers,
});
if (!response.ok)
throw new Error(
`HTTP ${response.status}; request id ${response.headers.get('x-request-id') ?? 'unavailable'}`,
);
const body = await response.json();
console.log(
JSON.stringify({
window: label,
token_price_history: body.token_price_history,
}),
);
}
}
module.exports = { signedTokenHistoryRequest, readTokenHistories };
if (require.main === module) {
readTokenHistories().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
}
GET /clients
Renvoie chaque compte client lié à votre équipe, avec les métadonnées de contact et de conservation uniquement. Il ne renvoie ni soldes ni avoirs.
Paramètres de requête
| Paramètre | Type | Obligatoire | Notes |
|---|---|---|---|
updated_since | string (ISO8601) | Non | Ne renvoie que les clients dont les métadonnées exposées ont changé à cette date ou après. |
include | string | Non | Valeurs séparées par des virgules parmi contact,wallets,cefi_accounts. Les trois par défaut. |
page_size | integer | Non | Active la pagination. De 1 à 500 clients par page. |
cursor | string | Non | next_cursor opaque de la page précédente. Exige page_size. |
Exemple de requête
GET /api/v1/b2b/clients?include=contact,wallets&page_size=100 HTTP/1.1 x-iceblock-key: your_key_id x-timestamp: 1775030400 x-signature: sha256=...
Exemple de réponse
{
"generated_at": "2026-04-01T09:30:00Z",
"data": [
{
"account_uid": "11111111-1111-4111-8111-111111111111",
"full_name": "Jane Doe",
"email": "jane@example.com",
"linked_at": "2026-03-01T10:00:00Z",
"wallets": [
{
"address": "0xabc123...",
"blockchain_namespace": "eip155:1"
}
],
"cefi_accounts": [
{
"platform": "binanceInternational"
}
]
}
],
"pagination": {
"page_size": 100,
"returned_count": 1,
"has_more": false,
"next_cursor": null
}
}
Champs de réponse
| Champ | Type | Description |
|---|---|---|
generated_at | string (ISO8601) | Horodatage de génération côté serveur. |
account_uid | string (uuid) | Identifiant du compte client IceBlock. |
full_name | string | Nom d'affichage du client. Présent lorsque include contient contact. |
email | string | Adresse e-mail du client. Présente lorsque include contient contact. |
linked_at | string (ISO8601) | Horodatage de liaison du client à votre équipe. |
wallets[].address | string | Adresse de portefeuille on-chain. Présente lorsque include contient wallets. |
wallets[].blockchain_namespace | string | Espace de noms CAIP-2, par exemple eip155:1. |
cefi_accounts[].platform | string | Identifiant de plateforme CeFi. Présent lorsque include contient cefi_accounts. |
POST /portfolio/latest
Renvoie le dernier instantané de portefeuille disponible pour chaque compte client demandé.
Corps de requête
{
"account_uids": [
"11111111-1111-4111-8111-111111111111",
"22222222-2222-4222-8222-222222222222"
],
"include_positions": true,
"include": ["defi_earnings"],
"defi_earnings_period": "1m"
}
| Champ | Type | Obligatoire | Notes |
|---|---|---|---|
account_uids | string[] | Oui | Jusqu'à 200 UUID. |
include_positions | boolean | Non | Vaut true par défaut. Lorsque false, l'instantané omet positions. |
include | string[] | Non | Active des sections supplémentaires. La valeur prise en charge est defi_earnings. |
defi_earnings_period | 1w | 1m | 1y | all | Non | Vaut 1m par défaut. Valide uniquement si include contient defi_earnings. |
page_size | integer | Non | Active la pagination. De 1 à 200 résultats de compte par page. |
cursor | string | Non | next_cursor opaque de la page précédente. Exige page_size. |
Exemple de réponse
{
"generated_at": "2026-04-01T09:30:00Z",
"results": [
{
"account_uid": "11111111-1111-4111-8111-111111111111",
"status": "ok",
"snapshot": {
"snapshot_at": "2026-03-31T23:55:00Z",
"total_mandate_value_usd": "125000.45",
"total_crypto_value_usd": "95000.45",
"total_stablecoin_value_usd": "30000.00",
"positions": [
{
"asset_symbol": "BTC",
"asset_name": "Bitcoin",
"asset_sector": "payment",
"is_stablecoin": false,
"position_source": "cefi",
"position_kind": "spot",
"quantity": "1.25000000",
"price_usd": "68000.00",
"value_usd": "85000.00",
"platform": "binanceInternational"
}
]
},
"defi_earnings": {
"status": "ok",
"period": "1m",
"period_start": "2026-03-02",
"period_end": "2026-03-31",
"earned_value_usd": "25.00",
"dietz_return": "0.002506265664160401",
"currently_supplied": [
{
"asset_ticker": "USDC",
"quantity": "10000",
"value_usd": "10000.00",
"apy": "0.04",
"apy_observed_at": "2026-03-31T23:55:00Z"
}
],
"calculation_version": "aave-usdc-eurc-v3",
"source_cutoff_at": "2026-03-31T23:55:00Z",
"disclosures": ["gas_excluded", "variable_rate", "limited_history"]
}
},
{
"account_uid": "22222222-2222-4222-8222-222222222222",
"status": "no_snapshot"
}
]
}
Statuts par compte
| Statut | Signification |
|---|---|
ok | Instantané renvoyé. |
not_found | account_uid n'existe pas. |
not_authorized | account_uid existe mais n'est pas lié à votre équipe. |
no_snapshot | account_uid est lié mais aucun instantané n'est encore disponible. |
Lorsque include contient defi_earnings et que les revenus DeFi sont activés pour l'environnement, un résultat autorisé inclut aussi defi_earnings, indépendamment de la disponibilité de l'instantané. Son status est ok, empty ou unavailable. Le champ est entièrement omis lorsque la fonctionnalité est désactivée ; l'API ne renvoie pas de statut hidden. Les revenus et le rendement ne sont présents que pour ok ; des positions actuellement fournies peuvent rester présentes pour unavailable, avec value_usd à null lorsqu'aucune valorisation fiable n'existe. Les motifs de qualité internes ne sont pas exposés.
Champs des revenus DeFi
| Champ | Type | Description |
|---|---|---|
status | string | ok, empty ou unavailable. |
period | string | Période demandée 1w, 1m, 1y ou all. |
period_start / period_end | string | null | Bornes UTC inclusives du calcul lorsqu'elles sont disponibles. |
earned_value_usd | string | null | Revenus USD pour un résultat ok. |
dietz_return | string | null | Rendement Dietz modifié, en ratio décimal. |
currently_supplied | array | Fournitures Aave v3 USDC/EURC éligibles, avec quantité, valeur USD optionnelle, APY variable et instant d'observation. |
calculation_version | string | Version du contrat de calcul figé. |
source_cutoff_at | string | null | Plus ancienne coupure de source représentée par le résultat. |
disclosures | string[] | Peut inclure gas_excluded, variable_rate et limited_history. |
Champs d'instantané
| Champ | Type | Description |
|---|---|---|
snapshot_at | string (ISO8601) | Horodatage de l'instantané sous-jacent. |
total_mandate_value_usd | string | Valeur totale du portefeuille à l'instantané. |
total_crypto_value_usd | string | Part hors stablecoins de la valeur du portefeuille. |
total_stablecoin_value_usd | string | Part stablecoins de la valeur du portefeuille. |
positions | array | Avoirs et expositions DeFi normalisés. Présent lorsque include_positions = true. |
Objet position
| Champ | Type | Description |
|---|---|---|
asset_symbol | string | Ticker de l'actif, par exemple BTC, ETH, USDC. |
asset_name | string | Nom lisible de l'actif. |
asset_sector | string | null | Secteur de l'actif lorsqu'il est disponible. |
is_stablecoin | boolean | true lorsque l'actif est un stablecoin. |
position_source | string | wallet, cefi ou defi. |
position_kind | string | spot, supply ou borrow. |
quantity | string | Quantité de la position. |
price_usd | string | null | Prix de valorisation USD de l'instantané lorsqu'il est disponible. |
value_usd | string | Valorisation USD de l'instantané. Négatif pour les emprunts. |
wallet_address | string | Présent pour les positions wallet et DeFi. |
blockchain_namespace | string | Présent pour les positions wallet et DeFi. |
platform | string | Présent pour les positions CeFi. |
protocol | string | Présent pour les positions DeFi. |
POST /portfolio/history
Renvoie l'historique quotidien de portefeuille pour un lot de comptes clients sur une plage de dates UTC demandée.
Corps de requête
{
"account_uids": ["11111111-1111-4111-8111-111111111111"],
"from": "2026-01-01",
"to": "2026-01-31",
"include_positions": false,
"include": ["defi_earnings"],
"defi_earnings_period": "1m"
}
| Champ | Type | Obligatoire | Notes |
|---|---|---|---|
account_uids | string[] | Oui | Jusqu'à 200 UUID. |
from | string (YYYY-MM-DD) | Oui | Date de début inclusive (UTC). |
to | string (YYYY-MM-DD) | Oui | Date de fin inclusive (UTC). Doit être supérieure ou égale à from. |
include_positions | boolean | Non | Vaut true par défaut. Lorsque false, chaque instantané quotidien omet positions. |
include | string[] | Non | Active des sections supplémentaires. La valeur prise en charge est defi_earnings. |
defi_earnings_period | 1w | 1m | 1y | all | Non | Vaut 1m par défaut. Valide uniquement si include contient defi_earnings. |
page_size | integer | Non | Active la pagination. De 1 à 200 résultats de compte par page. |
cursor | string | Non | next_cursor opaque de la page précédente. Exige page_size. |
La plage inclusive (to - from + 1 jours) ne doit pas dépasser le plafond d'historique de l'équipe. Le plafond par défaut est de 366 jours et est configurable par équipe par les administrateurs IceBlock. Découpez les reprises plus longues en plusieurs appels.
Exemple de réponse
{
"generated_at": "2026-04-01T09:30:00Z",
"results": [
{
"account_uid": "11111111-1111-4111-8111-111111111111",
"status": "ok",
"snapshots": [
{
"date": "2026-03-30",
"snapshot_at": "2026-03-30T23:54:00Z",
"is_carry_forward": false,
"total_mandate_value_usd": "124100.10",
"total_crypto_value_usd": "94100.10",
"total_stablecoin_value_usd": "30000.00",
"positions": []
},
{
"date": "2026-03-31",
"snapshot_at": "2026-03-30T23:54:00Z",
"is_carry_forward": true,
"total_mandate_value_usd": "124100.10",
"total_crypto_value_usd": "94100.10",
"total_stablecoin_value_usd": "30000.00",
"positions": []
}
],
"defi_earnings": {
"status": "ok",
"period": "1m",
"period_start": "2026-03-02",
"period_end": "2026-03-31",
"earned_value_usd": "25.00",
"dietz_return": "0.002506265664160401",
"currently_supplied": [],
"calculation_version": "aave-usdc-eurc-v3",
"source_cutoff_at": "2026-03-31T23:55:00Z",
"disclosures": ["gas_excluded", "variable_rate"]
}
}
]
}
Comportement de l'historique
- La granularité est quotidienne en v1.
- Dès qu'un compte a au moins un instantané à une date demandée ou avant, la réponse inclut une entrée par jour UTC jusqu'à
to. - Si aucun instantané n'existe encore pour un compte lié dans la plage demandée, ce compte renvoie
status = "no_snapshot". - Les valeurs de
statuspar compte correspondent àPOST /portfolio/latest. - Lorsque
include_positions = true,positionsutilise la même forme d'objet quePOST /portfolio/latest. - Lorsque
includecontientdefi_earnings, le résumé de revenus matérialisé courant est joint à chaque résultat de compte autorisé ; ce n'est pas une série quotidienne de revenus.
Invitations
Les routes d'invitation partenaires soumettent et suivent des demandes d'entrée en relation investisseurs. Iceblock active l'envoi d'invitations séparément pour chaque équipe (invitation_capability_enabled). Créer une clé API externe n'active pas les invitations. Contactez Iceblock pour demander cette capacité.
POST /invitations exige cette capacité. GET /invitations et GET /invitations/{id} ne l'exigent pas : un identifiant d'équipe actif peut interroger les demandes existantes même si Iceblock désactive ensuite l'envoi. Désactiver la capacité bloque les créations et les rejeux idempotents (403 invitation_capability_disabled).
Cette API n'a pas de webhook, pas d'expiration automatique et n'envoie pas d'e-mail au prospect. Les partenaires ne peuvent pas changer le statut via cette API. Après le rattachement d'un compte investisseur personnel par Iceblock, les statuts suivants sont calculés automatiquement à partir de l'état actuel du KYC, du coffre de conservation, du financement et du premier snapshot de portefeuille. Les partenaires interrogent les routes GET.
Les équipes liées peuvent consulter ces mêmes demandes en lecture seule dans Demandes d’entrée en relation investisseurs (/home/{team-slug}/investor-requests) : l'équipe d'origine et chaque équipe listée dans additional_team_account_ids. Les données personnelles du prospect ne sont pas renvoyées par l'API et n'apparaissent pas dans cette vue d'équipe.
POST /invitations
Crée une demande d'entrée en relation, ou renvoie la demande ouverte existante lorsque la même charge utile canonique est renvoyée — y compris avec une nouvelle idempotency_key.
- La query string doit être vide (
400 invalid_query). Content-Typedoit êtreapplication/json(400 invalid_body).- Signez le corps JSON brut exact. L'en-tête
Idempotency-Keyn'est pas utilisé ; envoyezidempotency_keydans le corps. - Les nouvelles demandes renvoient 201. Le renvoi d'une demande ouverte existante renvoie 200. La forme du corps de réponse est la même.
Corps de requête
{
"idempotency_key": "partner-req-01HXEXAMPLEKEY",
"external_client_reference": "crm-client-1001",
"prospect": {
"first_name": "Alex",
"last_name": "Martin",
"email": "alex.martin@example.com"
},
"additional_team_account_ids": [
"33333333-3333-4333-8333-333333333333"
]
}
| Champ | Type | Obligatoire | Notes |
|---|---|---|---|
idempotency_key | string | Oui | ^[A-Za-z0-9._:-]{8,128}$. Sensible à la casse. Non trimé. |
external_client_reference | string | Oui | Votre référence client. Normalisée NFC et trimée ; 1–128 caractères ; pas de caractères de contrôle. |
prospect | object | Oui | Objet strict. Uniquement first_name, last_name et email. |
prospect.first_name | string | Oui | Normalisé NFC et trimé ; 1–100 caractères ; pas de caractères de contrôle. |
prospect.last_name | string | Oui | Mêmes règles que first_name. |
prospect.email | string | Oui | Trimé et mis en minuscules ; 320 caractères maximum. Doit être un e-mail valide et ne doit pas commencer par ., contenir .. ni utiliser .@. |
additional_team_account_ids | string[] | Non | Jusqu'à 20 UUID d'équipes. Les valeurs sont mises en minuscules, les doublons sont retirés, et les identifiants restants sont triés. La réponse utilise cette liste normalisée. Ne doit pas inclure l'équipe appelante. Chaque cible doit déjà être autorisée à recevoir des invitations de votre équipe ; sinon 422 association_unavailable. |
Les champs inconnus sont rejetés (400 invalid_body).
Idempotence
| Résultat | HTTP | error.code |
|---|---|---|
| Première création acceptée | 201 | — |
Même idempotency_key et même empreinte canonique | 200 | — |
Nouvelle idempotency_key avec la même empreinte canonique qu'une demande déjà ouverte | 200 | — |
Même idempotency_key, empreinte différente | 409 | idempotency_conflict |
Demande ouverte avec le même external_client_reference et une empreinte différente | 409 | reference_conflict |
| Capacité désactivée, y compris en rejeu | 403 | invitation_capability_disabled |
L'empreinte canonique utilise un ordre de clés fixe : external_client_reference, prospect et additional_team_account_ids.
Exemple de réponse
{
"generated_at": "2026-10-21T09:30:00.000Z",
"data": {
"id": "44444444-4444-4444-8444-444444444444",
"external_client_reference": "crm-client-1001",
"additional_team_account_ids": [
"33333333-3333-4333-8333-333333333333"
],
"status": "received",
"created_at": "2026-10-21T09:30:00.000Z",
"updated_at": "2026-10-21T09:30:00.000Z",
"completed_at": null,
"closed_reason": null,
"result": null
}
}
Les champs du prospect sont acceptés à la création et ne sont pas renvoyés.
GET /invitations
Liste les demandes d'invitation créées par votre équipe. Les lectures rejettent un corps non vide (400 invalid_body).
| Paramètre | Type | Obligatoire | Notes |
|---|---|---|---|
status | string | Non | Un des statuts publics. |
external_client_reference | string | Non | Même normalisation qu'à la création. |
associated_team_account_id | string (uuid) | Non | Filtre les demandes dont additional_team_account_ids contient cette équipe. |
page_size | integer | Non | Vaut 25 par défaut. De 1 à 100. |
cursor | string | Non | next_cursor opaque de la page précédente. |
Des paramètres inconnus, dupliqués ou invalides renvoient 400 invalid_query. Un curseur mal formé ou expiré renvoie 400 invalid_cursor.
Les résultats sont du plus récent au plus ancien (created_at décroissant, puis id décroissant). La réponse inclut toujours pagination. Le curseur est lié à la clé API qui l'a créé : la rotation de clé l'invalide. Modifier status, external_client_reference, associated_team_account_id ou page_size l'invalide également.
GET /api/v1/b2b/invitations?status=in_progress&page_size=25 HTTP/1.1 x-iceblock-key: your_key_id x-timestamp: 1775030400 x-signature: sha256=...
{
"generated_at": "2026-10-21T09:35:00.000Z",
"data": [
{
"id": "44444444-4444-4444-8444-444444444444",
"external_client_reference": "crm-client-1001",
"additional_team_account_ids": [
"33333333-3333-4333-8333-333333333333"
],
"status": "in_progress",
"created_at": "2026-10-21T09:30:00.000Z",
"updated_at": "2026-10-21T09:34:00.000Z",
"completed_at": null,
"closed_reason": null,
"result": {
"account_uid": "11111111-1111-4111-8111-111111111111"
}
}
],
"pagination": {
"page_size": 25,
"returned_count": 1,
"has_more": false,
"next_cursor": null
}
}
GET /invitations/{id}
Renvoie une demande appartenant à votre équipe. Le {id} du chemin doit être un UUID. Les identifiants inconnus, mal formés et les demandes créées par une autre équipe renvoient tous 404 request_not_found. Les paramètres de requête ne sont pas acceptés.
GET /api/v1/b2b/invitations/44444444-4444-4444-8444-444444444444 HTTP/1.1 x-iceblock-key: your_key_id x-timestamp: 1775030400 x-signature: sha256=...
Le corps de succès est { generated_at, data } avec le même objet de demande que la création et la liste.
Statuts d'invitation
Valeurs publiques de status, dans l'ordre du cycle de vie après une création acceptée :
received → in_progress → awaiting_funding → awaiting_snapshot → ready_for_completion → completed
Une demande non rattachée peut quitter ce parcours en closed. closed_reason vaut alors cancelled ou rejected. Après le rattachement d'un compte investisseur personnel par Iceblock, les statuts suivants sont calculés automatiquement à partir des faits courants de ce compte. Si ces faits régressent ou deviennent inconnus, le statut revient à l'étape antérieure correspondante. completed_at est renseigné lorsque la demande passe à completed, et vaut null sinon.
| Statut | Signification | Ce qui place une demande ici |
|---|---|---|
received | Iceblock a accepté la demande. | POST /invitations partenaire. Valeur par défaut à la création. |
in_progress | L'examen a commencé, ou un compte investisseur personnel est rattaché et les faits d'entrée en relation requis ne sont pas tous vérifiés. | Iceblock commence l'examen, ou Iceblock rattache un compte investisseur personnel alors que le KYC, le coffre de conservation ou une autre preuve d'identité / d'entrée en relation applicable est manquante, en échec ou inconnue. |
awaiting_funding | Les faits KYC et de coffre de conservation applicables sont vérifiés. Le financement investisseur est encore en attente. | Calculé automatiquement après rattachement lorsque le KYC et le coffre de conservation courant sont vérifiés et que la source de financement autoritative indique que le financement est encore en attente. |
awaiting_snapshot | Le financement est vérifié. Le premier snapshot de portefeuille qualifiant est encore en attente. | Calculé automatiquement lorsque le financement est vérifié et que la preuve de snapshot courant autoritative reste en attente. |
ready_for_completion | Traitement dérivé transitoire : tous les faits courants requis du compte sont vérifiés et la demande avance vers completed. | Calculé automatiquement. Ce n'est pas une étape d'approbation manuelle. |
completed | L'entrée en relation est complète sur les faits courants vérifiés. | Calculé automatiquement lorsque le KYC, le coffre de conservation, le financement et le premier snapshot de portefeuille qualifiant sont tous vérifiés. Renseigne completed_at. |
closed | Iceblock a clôturé la demande avant achèvement. | Les opérateurs Iceblock clôturent une demande non rattachée avec closed_reason cancelled ou rejected. La clôture n'est plus disponible après rattachement du compte. |
Cycle de vie
- Le
POST /invitationspartenaire créereceived. - Iceblock commence l'examen →
in_progress(demande encore non rattachée). - Iceblock rattache un compte investisseur personnel →
in_progresstant que le KYC, le coffre de conservation ou une autre preuve d'entrée en relation applicable n'est pas résolue. - Le KYC et le coffre de conservation courant sont vérifiés, et le financement est encore en attente →
awaiting_funding. - Le financement est vérifié, et le premier snapshot de portefeuille qualifiant est encore en attente →
awaiting_snapshot. - Tous les faits courants requis du compte sont vérifiés → bref
ready_for_completion, puiscompletedaveccompleted_atrenseigné. - Avant rattachement uniquement : Iceblock clôture la demande →
closedaveccancelledourejected. - Après rattachement, une régression du KYC, du coffre, du financement ou du snapshot ramène la demande à l'étape antérieure correspondante.
- Les partenaires ne peuvent pas changer le statut via cette API. Il n'y a ni délai d'expiration ni rappel. Interrogez GET.
Champs de réponse
| Champ | Type | Description |
|---|---|---|
id | string (uuid) | Identifiant Iceblock de la demande d'invitation. |
external_client_reference | string | La référence que vous avez soumise. |
additional_team_account_ids | string[] | Identifiants d'équipes associées supplémentaires, en minuscules et triés. L'équipe appelante n'est jamais incluse. |
status | string | Un des statuts publics ci-dessus. |
created_at / updated_at | string (ISO8601) | Horodatages de la demande. |
completed_at | string (ISO8601) | null | Renseigné lorsque status est completed. null pour tous les autres statuts. |
closed_reason | cancelled | rejected | null | Renseigné uniquement lorsque status est closed. |
result | { account_uid } | null | Présent après qu'Iceblock a rattaché un compte investisseur personnel lisible. |
Lorsque result.account_uid est présent, utilisez cet UUID avec GET /clients et les routes de portefeuille. C'est le même account_uid que ces routes acceptent déjà. Il est omis tant que le compte n'existe pas, n'est pas rattaché, ou que votre équipe n'est pas autorisée à le lire.
Erreurs d'invitation
| Statut | error.code | Raison |
|---|---|---|
400 | invalid_json | Corps JSON mal formé à la création. |
400 | invalid_body | Charge utile de création invalide, type de contenu JSON manquant à la création, ou corps sur un GET. |
400 | invalid_query | Query de liste ou de détail non prise en charge ou invalide. |
400 | invalid_cursor | Curseur d'invitation mal formé, expiré ou hors portée. |
403 | invitation_capability_disabled | L'envoi d'invitations est désactivé pour l'équipe. |
403 | team_feature_disabled | L'accès API externe n'est pas disponible pour la création. |
404 | request_not_found | Identifiant inconnu, mal formé ou non détenu. |
409 | idempotency_conflict | idempotency_key est lié à une entrée différente. |
409 | reference_conflict | external_client_reference est lié à une entrée ouverte différente. |
422 | association_unavailable | Une équipe listée est l'appelant, n'est pas autorisée, ou ne peut pas recevoir la demande. |
Les erreurs d'authentification, de fenêtre de rejeu et de limite de débit sont les mêmes que pour les autres routes B2B.
Erreurs
| Statut | Codes typiques | Raison |
|---|---|---|
400 Bad Request | malformed_headers, malformed_timestamp, malformed_signature, malformed_account_uid, invalid_json, invalid_body, invalid_query, invalid_cursor | En-têtes manquants, JSON mal formé, UUID invalide, horodatage/date/query invalides, création d'invitation non JSON, corps sur un GET d'invitation, ou curseur mal formé / hors portée signée. |
401 Unauthorized | invalid_key, invalid_signature | Clé API inconnue ou signature incorrecte. |
403 Forbidden | timestamp_out_of_window, credential_disabled, credential_revoked, team_feature_disabled, global_feature_disabled, invitation_capability_disabled | Fenêtre de rejeu dépassée, identifiants désactivés/révoqués, fonctionnalité désactivée, ou envoi d'invitations désactivé. |
404 Not Found | request_not_found | Identifiant d'invitation inconnu, mal formé ou non détenu par l'équipe appelante. |
409 Conflict | idempotency_conflict, reference_conflict | Clé d'idempotence ou référence client ouverte liée à une entrée différente. |
422 Unprocessable Entity | invalid_date_range, too_many_account_uids, history_range_too_large, history_point_limit_exceeded, body_too_large, pagination_required, response_item_too_large, association_unavailable | Requête sémantiquement invalide, réponse non paginée de plus de 4 000 000 octets, résultat atomique trop volumineux, ou association d'invitation non autorisée. |
429 Too Many Requests | rate_limit_exceeded, daily_limit_exceeded, rate_limit_unavailable | Limite par clé dépassée, plafond quotidien dépassé, ou limiteur temporairement indisponible. Respectez Retry-After. |
500 Internal Server Error | internal_error, vault_secret_unavailable | Échec serveur inattendu. |
503 Service Unavailable | strategy_composition_unavailable | Une composition cible courante requise est manquante, vide, archivée ou invalide. Aucune composition partielle n'est renvoyée. |
Toutes les erreurs utilisent la même forme :
{
"error": {
"code": "invalid_signature",
"message": "Signature mismatch"
}
}
Limites de débit et identifiants
- Les requêtes sont limitées par clé (300 requêtes/minute par défaut, configurable par équipe). Un plafond quotidien optionnel (UTC) s'applique lorsque les administrateurs IceBlock le configurent. Lorsqu'une limite est dépassée — ou si le limiteur est brièvement indisponible — l'API renvoie
429avec un en-têteRetry-After. - Chaque page est une requête authentifiée et consomme une unité de quota minute et, le cas échéant, quotidienne.
- Les propriétaires d'équipe et super-admins gèrent les clés sur la page Accès API de l'équipe (et sur la fiche admin du compte pour les super-admins).
- Les clés API peuvent être créées, renouvelées, désactivées, révoquées et réactivées depuis cette page.
- Le secret HMAC n'est affiché qu'une seule fois, à la création ou à la rotation. Stockez-le de façon sécurisée et faites-le tourner s'il est exposé. La rotation exige une confirmation, émet une clé de remplacement et révoque immédiatement la précédente pour qu'un seul identifiant actif reste.
Métriques d'usage et traces d'audit (gestionnaires d'équipe)
- Les métriques d'usage apparaissent par clé sur la carte des identifiants : dernière utilisation, nombre de requêtes du jour, et nombre sur 30 jours. Ce sont des instantanés Redis transitoires avec un TTL d'inactivité de 30 jours ;
Neveret0signifient qu'aucun instantané n'est actuellement conservé. - Les traces d'audit enregistrent uniquement les événements de cycle de vie des identifiants et paramètres (
credential.created,credential.rotated,settings.updated, la désactivation d'urgence super-admin, et similaires). L'e-mail de l'opérateur est stocké de façon dénormalisée à l'écriture de l'événement.
L'activité par requête (route, statut HTTP, latence) n'est pas stockée dans le journal d'audit ni dans des compteurs de base permanents — elle est capturée dans les journaux serveur pour le débogage. Aucun secret ni corps de requête n'est enregistré.
Conseils d'intégration
- Utilisez
GET /strategiespour les graphiques de composition cible. Conservez les UID d'actifs et les lignes à poids zéro, et utilisez les valeurs exactes en bps ou en pourcentage à deux décimales. L’optiontoken_price_historyajoute les prix de marché disponibles pour les tokens actuels, sans reconstituer les compositions historiques. - Utilisez
GET /clientspour actualiser lesaccount_uiddisponibles et les métadonnées de conservation. - Utilisez
POST /invitationsseulement après qu'Iceblock a activé l'envoi d'invitations pour votre équipe. Conservezidetexternal_client_reference, puis interrogez GET. Lorsqueresult.account_uidapparaît, traitez-le comme un client nouvellement lié pourGET /clientset les routes de portefeuille. - Utilisez
POST /portfolio/latestpour la synchronisation quotidienne normale. Ajoutezinclude: ["defi_earnings"]lorsque vous avez besoin du résumé de prêt matérialisé courant. - Utilisez
POST /portfolio/historypour les reprises et le rapprochement ; définissezinclude_positionsexplicitement pour alléger les charges utiles. - Pour les grands ensembles, définissez
page_size, suiveznext_cursorjusqu'à ce quehas_moresoitfalse, et signez chaque page indépendamment sans changer la portée d'origine. - Traitez chaque résultat client ou compte de façon atomique. Pour un
response_item_too_largede portefeuille, réessayez sans positions ou avec une plage d'historique plus courte. Une composition de stratégies trop volumineuse nécessite le support IceBlock. - Gérez toujours les statuts par compte dans les réponses de portefeuille. N'inférez pas l'autorisation à partir de la seule liste de clients.
- Incluez
x-request-iddes réponses en échec lorsque vous contactez le support IceBlock.