Ir al contenido

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.


  1. Obtén un token de acceso: autentícate con Client Credentials
  2. Descubre tu organización: llama a /oauth/api-info para obtener tu URL de API y el ID de tu organización
  3. Navega hasta una división: el primer hijo de tu organización
  4. Navega hasta un sitio: el primer hijo de esa división
  5. Renombra el sitio: un cambio pequeño, evidente y reversible
  6. Confírmalo en la Prevu3D Platform: la meta
  7. Deshaz el cambio de nombre: no dejes rastros
  • Completa primero la guía Flujo de Client Credentials. Tu aplicación OAuth necesita los scopes read:hierarchy y write: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.

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"
}

Guarda access_token. Si esto falla, consulta la guía Flujo de Client Credentials.

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}.

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.

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.

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.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", "...": "..." }

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.

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.

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 requests
import base64
client_id = "your-client-id"
client_secret = "your-client-secret"
base_url = "https://cloud-api.prevu3d.com"
# Paso 1: obtener un token
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}"}
# Paso 2: descubrir la URL de la API y el ID de la organización
api_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ón
divisions = 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ón
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"]
# Paso 5: renombrar el sitio
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"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 nombre
requests.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}'.")

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 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)

Ú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.

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.