Pular para o conteúdo

Sua primeira mudança, do início ao fim

Esta página é um único script linear, de um token de acesso recém-obtido até uma mudança que você pode ver na Prevu3D Platform, e de volta. Ela usa o fluxo Client Credentials do início ao fim, e cada ID que precisa vem de uma resposta de API anterior no mesmo script: nada para preencher manualmente.


  1. Obter um token de acesso: autenticar-se com Client Credentials
  2. Descobrir sua organização: chamar /oauth/api-info para obter sua URL de API e o ID da organização
  3. Navegar até uma divisão: o primeiro filho da sua organização
  4. Navegar até um site: o primeiro filho dessa divisão
  5. Renomear o site: uma mudança pequena, evidente e reversível
  6. Confirmar na Prevu3D Platform: a linha de chegada
  7. Desfazer a renomeação: sem deixar rastros
  • Complete primeiro o guia Fluxo Client Credentials. Seu aplicativo OAuth precisa dos scopes read:hierarchy e write:hierarchy, e seu usuário de serviço precisa de acesso ao conteúdo e de uma função de nível de edição em pelo menos um site (veja o modelo de segurança).
  • Sua organização precisa de pelo menos uma divisão contendo pelo menos um site. Este percurso navega até o primeiro que encontrar.

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

Salve o access_token. Se isso falhar, consulte o guia Fluxo Client Credentials.

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

Cabeçalhos: 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"
}

Salve apiUrl e organization.id. Cada requisição a seguir 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
}

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

Use items[0].id como site_id, e guarde items[0].name ("Chicago Plant" neste caso); você precisará dele na etapa 7.

Renomear um nó é uma mudança pequena, evidente e reversível: exige write:hierarchy, não afeta mais nada, e é desfeita com uma única chamada.

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

Abra a Prevu3D Platform, navegue até a divisão da etapa 3 e abra o site da etapa 4.

Agora você deve ver o novo nome, Chicago Plant (test) neste exemplo, no topo da página do site e na lista de sites da divisão. Essa é a linha de chegada: uma mudança feita inteiramente pela API, visível no produto.

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

{ "name": "Chicago Plant" }

Envie o nome original do site da etapa 4. A resposta, e a Prevu3D Platform, agora mostram o site como estava antes da etapa 5.

Este script executa as etapas de 1 a 7 em ordem. Ele pausa depois da renomeação para que você possa conferir na Prevu3D Platform antes de desfazer a mudança.

import requests
import base64
client_id = "your-client-id"
client_secret = "your-client-secret"
base_url = "https://cloud-api.prevu3d.com"
# Etapa 1: obter um 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}"}
# Etapa 2: descobrir a URL da API e o ID da organização
api_info = requests.get(f"{base_url}/oauth/api-info", headers=headers).json()
api_url = api_info["apiUrl"]
organization_id = api_info["organization"]["id"]
# Etapa 3: navegar até a primeira divisão
divisions = requests.get(f"{api_url}/v1/nodes/{organization_id}/browse", headers=headers).json()
division_id = divisions["items"][0]["id"]
# Etapa 4: navegar até o primeiro site dessa divisão
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"]
# Etapa 5: renomear o 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} renomeado para '{new_name}'.")
print("Abra o site na Prevu3D Platform agora. Você deve ver o novo nome.")
input("Pressione Enter assim que confirmar, para desfazer a mudança...")
# Etapa 7: desfazer a renomeação
requests.patch(f"{api_url}/v1/nodes/{site_id}", json={"name": original_name}, headers=headers).raise_for_status()
print(f"Site {site_id} restaurado para '{original_name}'.")

O passo a passo acima é um caminho feliz: um token novo, uma rede tranquila, nada para tentar de novo. Um script que continua rodando — uma importação em massa, um loop de polling, um download grande — eventualmente encontra um 429, um token de acesso que expira no meio da execução, ou uma única chamada que precisa ser repetida enquanto o resto do lote continua. Em vez de sobrecarregar cada exemplo deste site com o mesmo código defensivo, esta seção introduz um pequeno cliente reutilizável uma única vez; os exemplos que precisam dele apontam de volta para cá em vez de repeti-lo.

O RCAPIClient envolve as chamadas requests brutas acima com três coisas: ele armazena em cache o token de acesso e o renova automaticamente — de forma proativa antes de expirar, e reativa em um 401 — ele tenta novamente um 429 usando o cabeçalho Retry-After (veja Limites de taxa) em vez de adivinhar um atraso, e desiste após um número máximo de tentativas ou um tempo máximo decorrido em vez de tentar para sempre.

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)

Use-o no lugar do requests diretamente, por exemplo para o token e a busca da organização das Etapas 1-2 acima:

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

O request() só recua ao ver um 429. Se uma carga de trabalho chegar perto da capacidade de um bucket sem disparar um 429 (tráfego sustentado de alto volume, por exemplo), leia X-RateLimit-Remaining e X-RateLimit-Reset de qualquer resposta e reduza a velocidade antes que o bucket se esvazie — veja Limites de taxa. O poll() constrói um loop de polling com recuo e desistência sobre o request(); Processamento e Monitoramento o utiliza.

Você foi de um token de acesso recém-obtido a uma mudança que pôde ver na Prevu3D Platform, e voltou.

  • Como encontrar seus IDs: mais formas de resolver IDs de organização, divisão, site e outros nós além da navegação.
  • Tipos de nó e hierarquia: a árvore de nós completa que você acabou de percorrer, e o que cada tipo de nó pode conter.
  • Introdução: o ponto de partida organizado por tarefas para tudo o mais que a API do RealityConnect pode fazer.