Aller au contenu

Votre premier changement, de bout en bout

Cette page est un script linéaire unique, d’un jeton d’accès tout neuf jusqu’à un changement que vous pouvez voir dans la Prevu3D Platform, puis retour en arrière. Elle utilise le flux Client Credentials de bout en bout, et chaque ID dont elle a besoin provient d’une réponse d’API obtenue plus tôt dans le même script : rien à remplir à la main.


  1. Obtenir un jeton d’accès : s’authentifier avec Client Credentials
  2. Découvrir votre organisation : appeler /oauth/api-info pour obtenir votre URL d’API et l’ID de votre organisation
  3. Parcourir jusqu’à une division : le premier enfant de votre organisation
  4. Parcourir jusqu’à un site : le premier enfant de cette division
  5. Renommer le site : un changement petit, évident et réversible
  6. Confirmer dans la Prevu3D Platform : la ligne d’arrivée
  7. Annuler le renommage : ne laisser aucune trace
  • Terminez d’abord le guide Flux Client Credentials. Votre application OAuth a besoin des scopes read:hierarchy et write:hierarchy, et votre utilisateur de service a besoin d’un accès au contenu et d’un rôle de niveau édition sur au moins un site (voir le modèle de sécurité).
  • Votre organisation a besoin d’au moins une division contenant au moins un site. Ce parcours parcourt jusqu’au premier trouvé.

Endpoint : POST https://cloud-api.prevu3d.com/oauth/token

POST https://cloud-api.prevu3d.com/oauth/token HTTP/1.1
Host: cloud-api.prevu3d.com
Authorization: Basic eW91ci1jbGllbnQtaWQ6eW91ci1jbGllbnQtc2VjcmV0
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
{
"hasError": false,
"access_token": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3599,
"token_type": "bearer"
}

Conservez access_token. Consultez le guide Flux Client Credentials en cas d’échec.

Endpoint : GET https://cloud-api.prevu3d.com/oauth/api-info

En-têtes : Authorization: Bearer <access_token>

{
"organization": {
"id": "217ebd23-ec54-4af0-a6d6-4a441a6d1966",
"name": "Test Organization"
},
"scopes": ["read:hierarchy", "write:hierarchy"],
"apiUrl": "https://api-ue1.prevu3d.com/realityconnect-api"
}

Conservez apiUrl et organization.id. Chaque requête ci-dessous utilise {apiUrl}.

Endpoint : GET {apiUrl}/v1/nodes/{organization_id}/browse

{
"node": { "id": "217ebd23-ec54-4af0-a6d6-4a441a6d1966", "type": "Organization", "...": "..." },
"items": [
{ "id": "3d3b6f2a-9e0a-4b0a-8b0a-1c2d3e4f5a6b", "name": "North America Operations", "type": "Division", "...": "..." }
],
"total": 1
}

Prenez items[0].id comme division_id.

Endpoint : GET {apiUrl}/v1/nodes/{division_id}/browse

{
"node": { "id": "3d3b6f2a-9e0a-4b0a-8b0a-1c2d3e4f5a6b", "type": "Division", "...": "..." },
"items": [
{ "id": "8a1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e", "name": "Chicago Plant", "type": "Site", "...": "..." }
],
"total": 1
}

Prenez items[0].id comme site_id, et conservez items[0].name ("Chicago Plant" ici) ; vous en aurez besoin à l’étape 7.

Renommer un nœud est un changement petit, évident et réversible : il nécessite write:hierarchy, ne touche rien d’autre, et se défait en un seul appel.

Endpoint : PATCH {apiUrl}/v1/nodes/{site_id}

PATCH https://api-ue1.prevu3d.com/realityconnect-api/v1/nodes/8a1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
{ "name": "Chicago Plant (test)" }
{ "id": "8a1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e", "name": "Chicago Plant (test)", "type": "Site", "...": "..." }

Ouvrez la Prevu3D Platform, accédez à la division de l’étape 3, puis ouvrez le site de l’étape 4.

Vous devriez maintenant voir le nouveau nom, Chicago Plant (test) dans cet exemple, en haut de la page du site et dans la liste des sites de la division. C’est la ligne d’arrivée : un changement effectué entièrement via l’API, visible dans le produit.

Endpoint : PATCH {apiUrl}/v1/nodes/{site_id}

{ "name": "Chicago Plant" }

Envoyez le nom d’origine du site depuis l’étape 4. La réponse, et la Prevu3D Platform, montrent maintenant le site tel qu’il était avant l’étape 5.

Ce script exécute les étapes 1 à 7 dans l’ordre. Il marque une pause après le renommage pour que vous puissiez vérifier dans la Prevu3D Platform avant d’annuler le changement.

import requests
import base64
client_id = "your-client-id"
client_secret = "your-client-secret"
base_url = "https://cloud-api.prevu3d.com"
# Étape 1 : obtenir un jeton
token_response = requests.post(
f"{base_url}/oauth/token",
data={"grant_type": "client_credentials"},
headers={
"Authorization": f'Basic {base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()}',
"Content-Type": "application/x-www-form-urlencoded",
},
)
token_response.raise_for_status()
access_token = token_response.json()["access_token"]
headers = {"Authorization": f"Bearer {access_token}"}
# Étape 2 : découvrir l'URL de l'API et l'ID de l'organisation
api_info = requests.get(f"{base_url}/oauth/api-info", headers=headers).json()
api_url = api_info["apiUrl"]
organization_id = api_info["organization"]["id"]
# Étape 3 : parcourir jusqu'à la première division
divisions = requests.get(f"{api_url}/v1/nodes/{organization_id}/browse", headers=headers).json()
division_id = divisions["items"][0]["id"]
# Étape 4 : parcourir jusqu'au premier site de cette division
sites = requests.get(f"{api_url}/v1/nodes/{division_id}/browse", headers=headers).json()
site = sites["items"][0]
site_id = site["id"]
original_name = site["name"]
# Étape 5 : renommer le site
new_name = f"{original_name} (test)"
requests.patch(f"{api_url}/v1/nodes/{site_id}", json={"name": new_name}, headers=headers).raise_for_status()
print(f"Site {site_id} renommé en '{new_name}'.")
print("Ouvrez le site dans la Prevu3D Platform maintenant. Vous devriez voir le nouveau nom.")
input("Appuyez sur Entrée une fois confirmé, pour annuler le changement...")
# Étape 7 : annuler le renommage
requests.patch(f"{api_url}/v1/nodes/{site_id}", json={"name": original_name}, headers=headers).raise_for_status()
print(f"Site {site_id} restauré en '{original_name}'.")

Le parcours ci-dessus est un chemin heureux : un jeton tout neuf, un réseau calme, rien à réessayer. Un script qui continue de tourner — un import en masse, une boucle de sondage, un téléchargement volumineux — finit par rencontrer un 429, un jeton d’accès qui expire en cours d’exécution, ou un appel isolé qui a besoin d’être réessayé pendant que le reste du lot continue. Plutôt que d’alourdir chaque exemple de ce site avec le même code défensif, cette section introduit une bonne fois pour toutes un petit client réutilisable ; les exemples qui en ont besoin renvoient ici plutôt que de le répéter.

RCAPIClient enveloppe les appels requests bruts ci-dessus avec trois choses : il met en cache le jeton d’accès et le rafraîchit automatiquement — de manière proactive avant son expiration, et de manière réactive sur un 401 — il réessaie un 429 en utilisant l’en-tête Retry-After (voir Limites de débit) plutôt que de deviner un délai, et il abandonne après un nombre maximal de tentatives ou une durée maximale écoulée plutôt que de réessayer indéfiniment.

import time
import base64
import requests
class RCAPIClient:
"""Token cache + refresh, retry with backoff, and a give-up condition.
Wrap the raw `requests` calls above with this once your integration runs
long enough to hit a rate limit or outlive an access token -- most bulk
imports, polling loops, and long-running downloads do.
"""
def __init__(self, client_id, client_secret, cloud_api_base="https://cloud-api.prevu3d.com"):
self.client_id = client_id
self.client_secret = client_secret
self.cloud_api_base = cloud_api_base
self._access_token = None
self._expires_at = 0 # epoch seconds
def _fetch_token(self):
credentials = base64.b64encode(f"{self.client_id}:{self.client_secret}".encode()).decode()
response = requests.post(
f"{self.cloud_api_base}/oauth/token",
data={"grant_type": "client_credentials"},
headers={"Authorization": f"Basic {credentials}"},
)
response.raise_for_status()
body = response.json()
self._access_token = body["access_token"]
# Refresh a little before the real expiry, not exactly at it.
self._expires_at = time.time() + body["expires_in"] - 60
def _token(self):
if self._access_token is None or time.time() >= self._expires_at:
self._fetch_token()
return self._access_token
def request(self, method, url, max_attempts=6, max_elapsed=300, **kwargs):
"""`requests.request()` plus token refresh, 429 backoff, and a give-up condition."""
start = time.time()
headers = kwargs.pop("headers", {})
for attempt in range(1, max_attempts + 1):
headers["Authorization"] = f"Bearer {self._token()}"
response = requests.request(method, url, headers=headers, **kwargs)
if response.status_code == 401 and attempt == 1:
self._access_token = None # token was rejected outright; force one refresh
continue
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 1))
if attempt == max_attempts or time.time() - start + retry_after > max_elapsed:
response.raise_for_status()
time.sleep(retry_after)
continue
return response
return response
def poll(self, url, is_done, interval=15, max_elapsed=3600):
"""GET `url` through this client until `is_done(body)` or `max_elapsed` seconds pass."""
start = time.time()
while True:
response = self.request("GET", url)
response.raise_for_status()
body = response.json()
if is_done(body):
return body
if time.time() - start >= max_elapsed:
raise TimeoutError(f"Gave up polling {url} after {max_elapsed}s")
time.sleep(interval)

Utilisez-le à la place de requests directement, par exemple pour le jeton et la recherche d’organisation des étapes 1 et 2 ci-dessus :

client = RCAPIClient(client_id="your-client-id", client_secret="your-client-secret")
api_info = client.request("GET", "https://cloud-api.prevu3d.com/oauth/api-info").json()

request() ne temporise qu’à partir du moment où il rencontre un 429. Si une charge de travail s’approche de la capacité d’un bucket sans en déclencher un (un trafic soutenu à volume élevé, par exemple), lisez X-RateLimit-Remaining et X-RateLimit-Reset sur n’importe quelle réponse et ralentissez avant que le bucket ne se vide — voir Limites de débit. poll() construit une boucle de sondage avec temporisation et abandon au-dessus de request() ; Traitement et suivi l’utilise.

Vous êtes passé d’un jeton d’accès tout neuf à un changement visible dans la Prevu3D Platform, puis retour en arrière.

  • Trouver vos ID : d’autres façons de résoudre les ID d’organisation, de division, de site et d’autres nœuds au-delà du parcours.
  • Types de nœuds et hiérarchie : l’arbre de nœuds complet que vous venez de parcourir, et ce que chaque type de nœud peut contenir.
  • Introduction : le point de départ organisé par tâche pour tout ce que l’API RealityConnect peut faire d’autre.