La tua prima modifica, dall'inizio alla fine
Questa pagina è un unico script lineare, da un access token appena ottenuto a una modifica che puoi vedere nella Prevu3D Platform, e ritorno. Usa il flusso Client Credentials dall’inizio alla fine, e ogni ID necessario proviene da una risposta API precedente nello stesso script: nulla da compilare a mano.
Cosa farai
Sezione intitolata “Cosa farai”- Ottieni un access token: autenticati con Client Credentials
- Scopri la tua organizzazione: chiama
/oauth/api-infoper ottenere il tuo URL API e l’ID dell’organizzazione - Naviga fino a una divisione: il primo figlio della tua organizzazione
- Naviga fino a un sito: il primo figlio di quella divisione
- Rinomina il sito: una modifica piccola, evidente e reversibile
- Conferma nella Prevu3D Platform: il traguardo
- Annulla la ridenominazione: senza lasciare tracce
Prerequisiti
Sezione intitolata “Prerequisiti”- Completa prima la guida Flusso Client Credentials. La tua applicazione OAuth ha bisogno degli scope
read:hierarchyewrite:hierarchy, e il tuo utente di servizio ha bisogno dell’accesso ai contenuti e di un ruolo di livello modifica su almeno un sito (vedi il modello di sicurezza). - La tua organizzazione ha bisogno di almeno una divisione contenente almeno un sito. Questo percorso naviga fino al primo che trova.
Passaggio 1: ottieni un access token
Sezione intitolata “Passaggio 1: ottieni un access token”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"}Salva access_token. Se questo fallisce, consulta la guida Flusso Client Credentials.
Passaggio 2: scopri la tua organizzazione
Sezione intitolata “Passaggio 2: scopri la tua organizzazione”Endpoint: GET https://cloud-api.prevu3d.com/oauth/api-info
Intestazioni: 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"}Salva apiUrl e organization.id. Ogni richiesta successiva usa {apiUrl}.
Passaggio 3: naviga fino a una divisione
Sezione intitolata “Passaggio 3: naviga fino a una divisione”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}Prendi items[0].id come division_id.
Passaggio 4: naviga fino a un sito
Sezione intitolata “Passaggio 4: naviga fino a un sito”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}Prendi items[0].id come site_id, e conserva items[0].name ("Chicago Plant" in questo caso); ti servirà al passaggio 7.
Passaggio 5: rinomina il sito
Sezione intitolata “Passaggio 5: rinomina il sito”Rinominare un nodo è una modifica piccola, evidente e reversibile: richiede write:hierarchy, non tocca nient’altro e si annulla con un’unica chiamata.
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", "...": "..." }Passaggio 6: conferma nella Prevu3D Platform
Sezione intitolata “Passaggio 6: conferma nella Prevu3D Platform”Apri la Prevu3D Platform, vai alla divisione del passaggio 3 e apri il sito del passaggio 4.
Ora dovresti vedere il nuovo nome, Chicago Plant (test) in questo esempio, in cima alla pagina del sito e nell’elenco dei siti della divisione. Questo è il traguardo: una modifica effettuata interamente tramite l’API, visibile nel prodotto.
Passaggio 7: annulla la ridenominazione
Sezione intitolata “Passaggio 7: annulla la ridenominazione”Endpoint: PATCH {apiUrl}/v1/nodes/{site_id}
{ "name": "Chicago Plant" }Invia il nome originale del sito dal passaggio 4. La risposta, e la Prevu3D Platform, mostrano ora il sito come era prima del passaggio 5.
Provalo con Python
Sezione intitolata “Provalo con Python”Questo script esegue i passaggi da 1 a 7 in ordine. Si mette in pausa dopo la ridenominazione, così puoi verificare nella Prevu3D Platform prima di annullare la modifica.
import requestsimport base64
client_id = "your-client-id"client_secret = "your-client-secret"base_url = "https://cloud-api.prevu3d.com"
# Passaggio 1: ottenere un tokentoken_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}"}
# Passaggio 2: scoprire l'URL API e l'ID dell'organizzazioneapi_info = requests.get(f"{base_url}/oauth/api-info", headers=headers).json()api_url = api_info["apiUrl"]organization_id = api_info["organization"]["id"]
# Passaggio 3: navigare fino alla prima divisionedivisions = requests.get(f"{api_url}/v1/nodes/{organization_id}/browse", headers=headers).json()division_id = divisions["items"][0]["id"]
# Passaggio 4: navigare fino al primo sito di quella divisionesites = 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"]
# Passaggio 5: rinominare il sitonew_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"Sito {site_id} rinominato in '{new_name}'.")print("Apri il sito nella Prevu3D Platform ora. Dovresti vedere il nuovo nome.")input("Premi Invio una volta confermato, per annullare la modifica...")
# Passaggio 7: annullare la ridenominazionerequests.patch(f"{api_url}/v1/nodes/{site_id}", json={"name": original_name}, headers=headers).raise_for_status()print(f"Sito {site_id} ripristinato a '{original_name}'.")Versione pronta per la produzione
Sezione intitolata “Versione pronta per la produzione”Il percorso qui sopra è un caso ideale: un token appena ottenuto, una rete tranquilla, nulla da riprovare. Uno script che continua a girare — un’importazione in blocco, un ciclo di polling, un download di grandi dimensioni — prima o poi incontra un 429, un access token che scade a metà esecuzione, o una singola chiamata che deve essere riprovata mentre il resto del lotto prosegue. Invece di appesantire ogni esempio di questo sito con lo stesso codice difensivo, questa sezione introduce una volta sola un piccolo client riutilizzabile; gli esempi che ne hanno bisogno rimandano qui invece di ripeterlo.
RCAPIClient avvolge le chiamate requests grezze viste sopra con tre funzionalità: mette in cache l’access token e lo rinnova automaticamente — in modo proattivo prima della scadenza, e in modo reattivo su un 401 — riprova un 429 usando l’header Retry-After (vedi Limiti di frequenza) invece di indovinare un’attesa, e si arrende dopo un numero massimo di tentativi o un tempo massimo trascorso invece di riprovare all’infinito.
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)Usalo al posto di requests direttamente, ad esempio per il token e la ricerca dell’organizzazione dei Passaggi 1-2 sopra:
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() rallenta solo quando incontra un 429. Se un carico di lavoro si avvicina alla capacità di un bucket senza mai farne scattare uno (traffico sostenuto ad alto volume, ad esempio), leggi X-RateLimit-Remaining e X-RateLimit-Reset da qualsiasi risposta e rallenta prima che il bucket si svuoti — vedi Limiti di frequenza. poll() costruisce sopra request() un ciclo di polling con backoff e resa; Elaborazione e monitoraggio lo utilizza.
Cosa c’è dopo?
Sezione intitolata “Cosa c’è dopo?”Sei passato da un access token appena ottenuto a una modifica che hai potuto vedere nella Prevu3D Platform, e ritorno.
- Trovare i tuoi ID: altri modi per risolvere gli ID di organizzazione, divisione, sito e altri nodi oltre alla navigazione.
- Tipi di nodo e gerarchia: l’intero albero dei nodi che hai appena percorso, e cosa può contenere ciascun tipo di nodo.
- Introduzione: il punto di partenza organizzato per attività per tutto il resto che l’API RealityConnect può fare.