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 responseimport 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 securelyconst 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 securelySet 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 onceAudit 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})`
);
}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`);