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.
Ce que vous allez faire
Section intitulée « Ce que vous allez faire »- Obtenir un jeton d’accès : s’authentifier avec Client Credentials
- Découvrir votre organisation : appeler
/oauth/api-infopour obtenir votre URL d’API et l’ID de votre organisation - Parcourir jusqu’à une division : le premier enfant de votre organisation
- Parcourir jusqu’à un site : le premier enfant de cette division
- Renommer le site : un changement petit, évident et réversible
- Confirmer dans la Prevu3D Platform : la ligne d’arrivée
- Annuler le renommage : ne laisser aucune trace
Prérequis
Section intitulée « Prérequis »- Terminez d’abord le guide Flux Client Credentials. Votre application OAuth a besoin des scopes
read:hierarchyetwrite: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é.
Étape 1 : Obtenir un jeton d’accès
Section intitulée « Étape 1 : Obtenir un jeton d’accès »Endpoint : POST https://cloud-api.prevu3d.com/oauth/token
POST https://cloud-api.prevu3d.com/oauth/token HTTP/1.1Host: cloud-api.prevu3d.comAuthorization: Basic eW91ci1jbGllbnQtaWQ6eW91ci1jbGllbnQtc2VjcmV0Content-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.
Étape 2 : Découvrir votre organisation
Section intitulée « Étape 2 : Découvrir votre organisation »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}.
Étape 3 : Parcourir jusqu’à une division
Section intitulée « Étape 3 : Parcourir jusqu’à une division »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.
Étape 4 : Parcourir jusqu’à un site
Section intitulée « Étape 4 : Parcourir jusqu’à un site »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.
Étape 5 : Renommer le site
Section intitulée « Étape 5 : Renommer le site »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.1Authorization: Bearer <access_token>Content-Type: application/json
{ "name": "Chicago Plant (test)" }{ "id": "8a1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e", "name": "Chicago Plant (test)", "type": "Site", "...": "..." }Étape 6 : Confirmer dans la Prevu3D Platform
Section intitulée « Étape 6 : Confirmer dans la Prevu3D Platform »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.
Étape 7 : Annuler le renommage
Section intitulée « Étape 7 : Annuler le renommage »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.
Essayez avec Python
Section intitulée « Essayez avec Python »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 requestsimport base64
client_id = "your-client-id"client_secret = "your-client-secret"base_url = "https://cloud-api.prevu3d.com"
# Étape 1 : obtenir un jetontoken_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'organisationapi_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 divisiondivisions = 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 divisionsites = 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 sitenew_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 renommagerequests.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}'.")Version prête pour la production
Section intitulée « Version prête pour la production »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 timeimport base64import 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.
Et ensuite ?
Section intitulée « Et ensuite ? »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.