Tu primer cambio, de principio a fin
Esta página es un único script lineal, desde un token de acceso recién obtenido hasta un cambio que puedes ver en la Prevu3D Platform, y de vuelta. Usa el flujo Client Credentials de principio a fin, y cada ID que necesita proviene de una respuesta de la API anterior en el mismo script: nada que rellenar a mano.
Lo que harás
Sección titulada «Lo que harás»- Obtén un token de acceso: autentícate con Client Credentials
- Descubre tu organización: llama a
/oauth/api-infopara obtener tu URL de API y el ID de tu organización - Navega hasta una división: el primer hijo de tu organización
- Navega hasta un sitio: el primer hijo de esa división
- Renombra el sitio: un cambio pequeño, evidente y reversible
- Confírmalo en la Prevu3D Platform: la meta
- Deshaz el cambio de nombre: no dejes rastros
Requisitos previos
Sección titulada «Requisitos previos»- Completa primero la guía Flujo de Client Credentials. Tu aplicación OAuth necesita los scopes
read:hierarchyywrite:hierarchy, y tu usuario de servicio necesita acceso al contenido y un rol de nivel edición en al menos un sitio (consulta el modelo de seguridad). - Tu organización necesita al menos una división que contenga al menos un sitio. Este recorrido navega hasta el primero que encuentre.
Paso 1: obtén un token de acceso
Sección titulada «Paso 1: obtén un token de acceso»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"}Guarda access_token. Si esto falla, consulta la guía Flujo de Client Credentials.
Paso 2: descubre tu organización
Sección titulada «Paso 2: descubre tu organización»Endpoint: GET https://cloud-api.prevu3d.com/oauth/api-info
Cabeceras: 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"}Guarda apiUrl y organization.id. Cada solicitud siguiente usa {apiUrl}.
Paso 3: navega hasta una división
Sección titulada «Paso 3: navega hasta una división»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}Toma items[0].id como division_id.
Paso 4: navega hasta un sitio
Sección titulada «Paso 4: navega hasta un sitio»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}Toma items[0].id como site_id, y guarda items[0].name ("Chicago Plant" en este caso); lo necesitarás en el paso 7.
Paso 5: renombra el sitio
Sección titulada «Paso 5: renombra el sitio»Renombrar un nodo es un cambio pequeño, evidente y reversible: necesita write:hierarchy, no afecta nada más y se revierte con una sola llamada.
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", "...": "..." }Paso 6: confírmalo en la Prevu3D Platform
Sección titulada «Paso 6: confírmalo en la Prevu3D Platform»Abre la Prevu3D Platform, navega hasta la división del paso 3 y abre el sitio del paso 4.
Ahora deberías ver el nuevo nombre, Chicago Plant (test) en este ejemplo, en la parte superior de la página del sitio y en la lista de sitios de la división. Esa es la meta: un cambio realizado enteramente a través de la API, visible en el producto.
Paso 7: deshaz el cambio de nombre
Sección titulada «Paso 7: deshaz el cambio de nombre»Endpoint: PATCH {apiUrl}/v1/nodes/{site_id}
{ "name": "Chicago Plant" }Envía el nombre original del sitio del paso 4. La respuesta, y la Prevu3D Platform, muestran ahora el sitio tal como estaba antes del paso 5.
Pruébalo con Python
Sección titulada «Pruébalo con Python»Este script ejecuta los pasos 1 a 7 en orden. Se detiene después de renombrar para que puedas comprobarlo en la Prevu3D Platform antes de deshacer el cambio.
import requestsimport base64
client_id = "your-client-id"client_secret = "your-client-secret"base_url = "https://cloud-api.prevu3d.com"
# Paso 1: obtener 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}"}
# Paso 2: descubrir la URL de la API y el ID de la organizaciónapi_info = requests.get(f"{base_url}/oauth/api-info", headers=headers).json()api_url = api_info["apiUrl"]organization_id = api_info["organization"]["id"]
# Paso 3: navegar hasta la primera divisióndivisions = requests.get(f"{api_url}/v1/nodes/{organization_id}/browse", headers=headers).json()division_id = divisions["items"][0]["id"]
# Paso 4: navegar hasta el primer sitio de esa divisiónsites = 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"]
# Paso 5: renombrar el sitionew_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"Sitio {site_id} renombrado a '{new_name}'.")print("Abre el sitio en la Prevu3D Platform ahora. Deberías ver el nuevo nombre.")input("Presiona Enter una vez confirmado, para deshacer el cambio...")
# Paso 7: deshacer el cambio de nombrerequests.patch(f"{api_url}/v1/nodes/{site_id}", json={"name": original_name}, headers=headers).raise_for_status()print(f"Sitio {site_id} restaurado a '{original_name}'.")Versión lista para producción
Sección titulada «Versión lista para producción»El recorrido anterior es un camino feliz: un token recién obtenido, una red tranquila, nada que reintentar. Un script que sigue ejecutándose — una importación masiva, un bucle de sondeo, una descarga grande — termina encontrando un 429, un token de acceso que expira a mitad de la ejecución, o una sola llamada que necesita reintentarse mientras el resto del lote continúa. En lugar de sobrecargar cada ejemplo de este sitio con el mismo código defensivo, esta sección introduce una sola vez un pequeño cliente reutilizable; los ejemplos que lo necesitan enlazan aquí en vez de repetirlo.
RCAPIClient envuelve las llamadas requests sin procesar de arriba con tres cosas: almacena en caché el token de acceso y lo renueva automáticamente — de forma proactiva antes de que expire, y de forma reactiva ante un 401 —, reintenta un 429 usando el encabezado Retry-After (consulta Límites de tasa) en lugar de adivinar una espera, y se rinde tras un número máximo de intentos o un tiempo máximo transcurrido en lugar de reintentar para siempre.
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)Úsalo en lugar de requests directamente, por ejemplo para el token y la búsqueda de organización de los pasos 1 y 2 anteriores:
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() solo aplica espera cuando ve un 429. Si una carga de trabajo se acerca a la capacidad de un bucket sin llegar a provocar uno (tráfico sostenido de alto volumen, por ejemplo), lee X-RateLimit-Remaining y X-RateLimit-Reset de cualquier respuesta y reduce la velocidad antes de que el bucket se vacíe — consulta Límites de tasa. poll() construye un bucle de sondeo con espera y rendición sobre request(); Procesamiento y seguimiento lo utiliza.
¿Qué sigue?
Sección titulada «¿Qué sigue?»Has pasado de un token de acceso recién obtenido a un cambio que pudiste ver en la Prevu3D Platform, y de vuelta.
- Cómo encontrar tus ID: más formas de resolver los ID de organización, división, sitio y otros nodos, más allá de navegar.
- Tipos de nodo y jerarquía: el árbol de nodos completo que acabas de recorrer, y qué puede contener cada tipo de nodo.
- Introducción: el punto de partida organizado por tareas para todo lo demás que puede hacer la API de RealityConnect.