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",
}),
});Alert destinations and visibility#
The examples above subscribe destinations to ALERT_CREATED. That selects which event types you receive—not which alerts within that type.
To reduce alert noise without removing the destination:
- Configure workspace alert visibility in the Dashboard (Settings → Alerts).
- Keep the destination subscribed to
ALERT_CREATED(andALERT_UPDATEDif you need lifecycle updates). - Save the workspace config. Until the first save, every matching alert event is still delivered.
For example, with INFO severity disabled, your webhook does not receive ALERT_CREATED for INFO alerts, but CRITICAL and WARNING alerts still flow through. Disabling the master switch pauses all alert destination delivery while Texture continues to capture alerts in the background.
See Destinations → Alert events and workspace visibility for the full delivery matrix.
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`);