Common Workflows

Real-world patterns for automating your Texture organization. Each workflow includes examples in multiple languages.


Provision a new workspace

Create a workspace and a dedicated API key — the typical flow for onboarding a new customer or environment. Deletion protection is enabled by default, so the workspace is immediately protected.

# 1. Create the workspace (deletion protection is on by default)
curl -X POST https://api.texturehq.com/v1/workspaces \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production"}'
 
# 2. Create a server API key for the workspace
curl -X POST https://api.texturehq.com/v1/api-keys \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production Server Key", "type": "SERVER"}'
# Save the key value from the response
import os
import requests
 
BASE = "https://api.texturehq.com/v1"
HEADERS = {
    "Texture-Api-Key": os.environ["TEXTURE_MGMT_KEY"],
    "Content-Type": "application/json",
}
 
# 1. Create workspace (deletion protection is on by default)
ws = requests.post(f"{BASE}/workspaces", json={"name": "Production"}, headers=HEADERS).json()
ws_id = ws["id"]
 
# 2. Create a server key
key_resp = requests.post(
    f"{BASE}/api-keys",
    json={"name": "Production Server Key", "type": "SERVER"},
    headers=HEADERS,
).json()
 
print(f"Workspace: {ws_id}")
print(f"Server Key: {key_resp['key']}")  # Store securely
const BASE = "https://api.texturehq.com/v1";
const headers = {
  "Texture-Api-Key": process.env.TEXTURE_MGMT_KEY,
  "Content-Type": "application/json",
};
 
// 1. Create workspace (deletion protection is on by default)
const ws = await fetch(`${BASE}/workspaces`, {
  method: "POST",
  headers,
  body: JSON.stringify({ name: "Production" }),
}).then((r) => r.json());
 
// 2. Create a server key
const keyResp = await fetch(`${BASE}/api-keys`, {
  method: "POST",
  headers,
  body: JSON.stringify({ name: "Production Server Key", type: "SERVER" }),
}).then((r) => r.json());
 
console.log(`Workspace: ${ws.id}`);
console.log(`Server Key: ${keyResp.key}`); // Store securely

Set up event destinations

Configure a webhook for device events and an email for alerts.

# Webhook for device events
curl -X POST https://api.texturehq.com/v1/destinations/webhooks \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Device Events Webhook",
    "url": "https://myapp.com/webhooks/texture",
    "eventTypes": ["DEVICE_CONNECTED", "DEVICE_DISCONNECTED", "DEVICE_UPDATED"],
    "deviceType": "ALL",
    "secret": "whsec_my-secret-for-verification"
  }'
 
# Email for alerts
curl -X POST https://api.texturehq.com/v1/destinations/emails \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "Alert Emails",
    "email": "alerts@example.com",
    "eventTypes": ["ALERT_CREATED"],
    "deviceType": "ALL"
  }'
# Webhook for device events
requests.post(
    f"{BASE}/destinations/webhooks",
    json={
        "label": "Device Events Webhook",
        "url": "https://myapp.com/webhooks/texture",
        "eventTypes": ["DEVICE_CONNECTED", "DEVICE_DISCONNECTED", "DEVICE_UPDATED"],
        "deviceType": "ALL",
        "secret": "whsec_my-secret-for-verification",
    },
    headers=HEADERS,
)
 
# Email for alerts
requests.post(
    f"{BASE}/destinations/emails",
    json={
        "label": "Alert Emails",
        "email": "alerts@example.com",
        "eventTypes": ["ALERT_CREATED"],
        "deviceType": "ALL",
    },
    headers=HEADERS,
)
// Webhook for device events
await fetch(`${BASE}/destinations/webhooks`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    label: "Device Events Webhook",
    url: "https://myapp.com/webhooks/texture",
    eventTypes: ["DEVICE_CONNECTED", "DEVICE_DISCONNECTED", "DEVICE_UPDATED"],
    deviceType: "ALL",
    secret: "whsec_my-secret-for-verification",
  }),
});
 
// Email for alerts
await fetch(`${BASE}/destinations/emails`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    label: "Alert Emails",
    email: "alerts@example.com",
    eventTypes: ["ALERT_CREATED"],
    deviceType: "ALL",
  }),
});

Manage your team

Invite members, update roles, and list your current team.

# Invite a new team member
curl -X POST https://api.texturehq.com/v1/invitations \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "engineer@example.com", "role": "USER"}'
 
# Promote a member to admin
curl -X PATCH "https://api.texturehq.com/v1/members/$MEMBER_ID" \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role": "ADMIN"}'
 
# List current team
curl https://api.texturehq.com/v1/members \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY"
# Invite a new team member
requests.post(
    f"{BASE}/invitations",
    json={"email": "engineer@example.com", "role": "USER"},
    headers=HEADERS,
)
 
# Promote a member to admin
requests.patch(
    f"{BASE}/members/{member_id}",
    json={"role": "ADMIN"},
    headers=HEADERS,
)
 
# List current team
members = requests.get(f"{BASE}/members", headers=HEADERS).json()
for m in members["data"]:
    profile = m["userProfile"]
    print(f"  {profile['firstName']} {profile['lastName']}{m['role']}")
// Invite a new team member
await fetch(`${BASE}/invitations`, {
  method: "POST",
  headers,
  body: JSON.stringify({ email: "engineer@example.com", role: "USER" }),
});
 
// Promote a member to admin
await fetch(`${BASE}/members/${memberId}`, {
  method: "PATCH",
  headers,
  body: JSON.stringify({ role: "ADMIN" }),
});
 
// List current team
const members = await fetch(`${BASE}/members`, { headers }).then((r) =>
  r.json()
);
for (const m of members.data) {
  const { firstName, lastName } = m.userProfile;
  console.log(`  ${firstName} ${lastName} — ${m.role}`);
}

Rotate API keys (zero-downtime)

See the detailed guide in API Keys → Zero-downtime key rotation. Here's the quick version:

# 1. Create replacement key
NEW_KEY=$(curl -s -X POST https://api.texturehq.com/v1/api-keys \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Production Key (rotated)", "type": "SERVER"}' \
  | jq -r '.key')
 
echo "New key: $NEW_KEY"
# 2. Deploy the new key to your application
# 3. Verify the new key is working
# 4. THEN revoke the old key
 
curl -X POST "https://api.texturehq.com/v1/api-keys/$OLD_KEY_ID/revoke" \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY"

Create an OAuth application

Register an OAuth app for the Texture Connect flow.

curl -X POST https://api.texturehq.com/v1/oauth-apps \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Partner Portal",
    "description": "Partner integration for energy monitoring",
    "redirectUris": ["https://partner.example.com/callback"],
    "allowedScopes": ["read:devices", "read:sites"],
    "type": "CONFIDENTIAL"
  }'
# Save clientId and clientSecret from the response — the secret is only shown once

Audit recent activity

Review management actions for security or compliance.

# All actions in the last 24 hours
curl "https://api.texturehq.com/v1/audit-logs?startDate=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)" \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY"
 
# Filter to API key actions only
curl "https://api.texturehq.com/v1/audit-logs?resourceType=ApiKey&perPage=100" \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY"
from datetime import datetime, timezone, timedelta
 
# All actions in the last 24 hours
yesterday = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%dT%H:%M:%SZ")
logs = requests.get(
    f"{BASE}/audit-logs",
    params={"startDate": yesterday},
    headers=HEADERS,
).json()
 
for entry in logs["data"]:
    print(f"  [{entry['createdAt']}] {entry['action']} on {entry['resourceType']} ({entry['resourceId']})")
const yesterday = new Date(Date.now() - 86400000).toISOString();
const logs = await fetch(
  `${BASE}/audit-logs?startDate=${yesterday}`,
  { headers }
).then((r) => r.json());
 
for (const entry of logs.data) {
  console.log(
    `  [${entry.createdAt}] ${entry.action} on ${entry.resourceType} (${entry.resourceId})`
  );
}
Tip

Combine API and Dashboard. Use the API for automated compliance checks and SIEM integrations, and the Dashboard (Settings → Audit Log) for ad-hoc investigations.


Build a device collection

Create a dynamic collection that automatically includes matching devices.

curl -X POST https://api.texturehq.com/v1/collections \
  -H "Texture-Api-Key: $TEXTURE_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "All EV Chargers",
    "collectionType": "DEVICE",
    "description": "Every EV charger across all workspaces",
    "criteria": {
      "rule": {
        "attribute": "deviceType",
        "operator": "EQUALS",
        "value": "CHARGERS"
      }
    }
  }'
collection = requests.post(
    f"{BASE}/collections",
    json={
        "name": "All EV Chargers",
        "collectionType": "DEVICE",
        "description": "Every EV charger across all workspaces",
        "criteria": {
            "rule": {
                "attribute": "deviceType",
                "operator": "EQUALS",
                "value": "CHARGERS",
            }
        },
    },
    headers=HEADERS,
).json()
 
# List entities in the collection
entities = requests.get(
    f"{BASE}/collections/{collection['id']}/entities",
    headers=HEADERS,
).json()
print(f"Found {entities['meta']['total']} chargers")
const collection = await fetch(`${BASE}/collections`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    name: "All EV Chargers",
    collectionType: "DEVICE",
    description: "Every EV charger across all workspaces",
    criteria: {
      rule: {
        attribute: "deviceType",
        operator: "EQUALS",
        value: "CHARGERS",
      },
    },
  }),
}).then((r) => r.json());
 
// List entities in the collection
const entities = await fetch(
  `${BASE}/collections/${collection.id}/entities`,
  { headers }
).then((r) => r.json());
console.log(`Found ${entities.meta.total} chargers`);