REST API reference
Create and reconcile subscriptions, test delivery, query tenant health, and integrate webhook senders. The machine-readable contract is available as OpenAPI 3.1 JSON.
The hosted service uses Microsoft Entra bearer tokens for management calls. Function keys plus x-org-id are for self-hosted deployments where Entra is not configured. These modes are mutually exclusive.
Base URL and formats
https://tdckbprod-flex-prod.azurewebsites.net/api
- Request and response bodies are JSON unless noted.
- Send
Content-Type: application/jsonfor request bodies. - Timestamps are UTC ISO 8601 strings.
- Webhook and API-key fields are write-only and never echoed back.
Authentication
| Credential | Send as | Used for |
|---|---|---|
| Entra access token | Authorization: Bearer … | Hosted management API. Tenant must be installed and caller must hold Connector.Admin. |
| Function host key | x-functions-key: … | Self-hosted management API when Entra is disabled. |
| Organization ID | x-org-id: acme | Required with a function key; ignored as identity in Entra mode. |
| Subscription key | ?key=… or x-api-key: … | Direct ingest and broadcast webhook authentication. |
A malformed bearer token never falls back to a function key. Never put management or ingest credentials in logs, browser analytics, Prometheus target URLs, or support screenshots.
Subscriptions
A subscription binds one platform to exactly one default destination: Teams Workflows webhook, bot channel, or bot DM. It may also include filters, routing, branding, and noise controls.
POST/subscriptions · create
Creates a subscription. The response returns the plaintext apiKey and complete ingestUrl once. Store the ingest URL in the sending platform immediately.
curl -X POST https://tdckbprod-flex-prod.azurewebsites.net/api/subscriptions \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Production builds",
"platform": "github",
"botChannel": {
"conversationId": "19:…@thread.tacv2",
"serviceUrl": "https://smba.trafficmanager.net/amer/"
},
"statusFilter": ["failure", "deployment_failure"],
"enabled": true
}'
Responses: 201 created, 400 invalid input, 402 plan limit.
GET/subscriptions · list
Lists the caller tenant's subscriptions. Each item can include lastDelivery. Ingest keys and destination URLs are redacted.
curl https://tdckbprod-flex-prod.azurewebsites.net/api/subscriptions \
-H "Authorization: Bearer $ACCESS_TOKEN"GET/subscriptions/{id} · read
Returns one redacted subscription and an ETag response header. Save that ETag before a declarative PUT.
PUT/subscriptions/{id} · full replacement
Replaces every client-configurable field. It requires the exact ETag returned by the latest GET. Omitted optional fields are intentionally cleared.
# 1. Read the current ETag
curl -sS -D headers.txt -o subscription.json \
https://tdckbprod-flex-prod.azurewebsites.net/api/subscriptions/$SUB_ID \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 2. Send the complete desired state
curl -X PUT https://tdckbprod-flex-prod.azurewebsites.net/api/subscriptions/$SUB_ID \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'If-Match: "current-etag"' \
-H "Content-Type: application/json" \
-d @desired-subscription.json
428 means the exact If-Match header is missing. 412 means another writer changed the subscription: GET, reconcile, and retry. IDs, organization, API-key hash, and creation time remain server-owned. Platform cannot change because it is part of the ingest URL.
PATCH/subscriptions/{id} · partial update
Updates only fields present in the body. Use PATCH for ordinary UI-style edits. Setting one destination clears the previous destination atomically.
curl -X PATCH https://tdckbprod-flex-prod.azurewebsites.net/api/subscriptions/$SUB_ID \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled":false}'DELETE/subscriptions/{id} · delete
Deletes a subscription and returns 204. The audit trail records the deletion.
POST/subscriptions/{id}/test · test delivery
Sends a synthetic success card through the subscription's actual destination. It bypasses filters so it tests connectivity. The result updates lastDelivery.
POST/subscriptions/{id}/rotate-key · rotate ingest key
Invalidates the old key and returns a new plaintext key and ingest URL once. Update the sender immediately.
Health and monitoring
GET/health · public liveness
Returns 200 when the process is alive. It does not check dependencies.
{"ok":true,"service":"permylastwebhook","version":"…","time":"…"}GET/ready · public readiness
Returns 200 only when required configuration and Cosmos are ready; otherwise 503 with sanitized named checks. No internal endpoint or SDK error is exposed.
GET/monitoring/status · tenant status
Authenticated tenant-scoped summary for scripts, Datadog, or Grafana. It contains healthy/failing/unknown/disabled counts and last-delivery outcomes, but no tenant IDs, payloads, keys, ingest URLs, webhook URLs, bot conversation details, or DM identifiers.
curl https://tdckbprod-flex-prod.azurewebsites.net/api/monitoring/status \
-H "Authorization: Bearer $ACCESS_TOKEN"See Monitoring & health for Prometheus and OTLP patterns.
Webhook ingestion
Direct ingest (recommended)
POST /api/ingest/{orgId}/{subscriptionId}/{platform}?key={subscriptionKey}
Use the exact ingestUrl returned on create or key rotation. A direct URL selects one subscription, validates the platform and key, then applies its filters and routing.
Broadcast ingest
POST /api/webhooks/{platform}
x-api-key: {subscriptionKey}
x-org-id: {organization}
For header-capable senders. It fans out to matching enabled subscriptions. Direct ingest is simpler and avoids ambiguous fan-out.
Errors and retries
Errors use {"error":"message","details":{…}}. Validation details contain field errors, never credentials.
| Status | Meaning | Client action |
|---|---|---|
| 400 | Invalid input or platform mismatch | Fix the request; do not retry unchanged. |
| 401 | Missing/invalid identity or API key | Refresh token or replace the key. |
| 402 | Plan limit | Reduce gated configuration or upgrade. |
| 404 | Resource not found | Refresh local state. |
| 409 | State conflict | Read current state before retrying. |
| 412 / 428 | Stale or missing PUT precondition | GET the ETag, reconcile, retry. |
| 429 | Rate limit | Wait for Retry-After. |
| 502 | Teams delivery exhausted retries | Inspect Last delivery; retry later. |
Download the live OpenAPI document, or email hello@permylastwebhook.com with an integration question.