> clickup-observability
Monitor ClickUp API integrations with metrics, tracing, structured logging, and alerting using Prometheus, OpenTelemetry, and Grafana. Trigger: "clickup monitoring", "clickup metrics", "clickup observability", "monitor clickup", "clickup alerts", "clickup tracing", "clickup dashboard".
curl "https://skillshub.wtf/jeremylongshore/claude-code-plugins-plus-skills/clickup-observability?format=md"ClickUp Observability
Overview
Monitor ClickUp API v2 integrations with metrics (request rate, latency, errors, rate limit usage), distributed tracing, and alerting.
Key Metrics
| Metric | Type | Labels | Description |
|---|---|---|---|
clickup_requests_total | Counter | method, endpoint, status | Total API requests |
clickup_request_duration_seconds | Histogram | method, endpoint | Request latency |
clickup_errors_total | Counter | status_code, error_type | Errors by type |
clickup_rate_limit_remaining | Gauge | token_hash | Rate limit headroom |
clickup_rate_limit_resets_total | Counter | Times we hit 429 |
Prometheus Instrumentation
import { Registry, Counter, Histogram, Gauge } from 'prom-client';
const registry = new Registry();
const requestCounter = new Counter({
name: 'clickup_requests_total',
help: 'Total ClickUp API v2 requests',
labelNames: ['method', 'endpoint', 'status'] as const,
registers: [registry],
});
const requestDuration = new Histogram({
name: 'clickup_request_duration_seconds',
help: 'ClickUp API request duration in seconds',
labelNames: ['method', 'endpoint'] as const,
buckets: [0.05, 0.1, 0.25, 0.5, 1, 2, 5],
registers: [registry],
});
const rateLimitGauge = new Gauge({
name: 'clickup_rate_limit_remaining',
help: 'ClickUp rate limit remaining requests',
registers: [registry],
});
const errorCounter = new Counter({
name: 'clickup_errors_total',
help: 'ClickUp API errors by status code',
labelNames: ['status_code', 'error_type'] as const,
registers: [registry],
});
Instrumented Client
async function instrumentedClickUpRequest<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const method = options.method ?? 'GET';
// Normalize endpoint for cardinality control (replace UUIDs)
const endpoint = path.replace(/\/[a-zA-Z0-9]{6,}(?=\/|$|\?)/g, '/:id');
const timer = requestDuration.startTimer({ method, endpoint });
try {
const response = await fetch(`https://api.clickup.com/api/v2${path}`, {
...options,
headers: {
'Authorization': process.env.CLICKUP_API_TOKEN!,
'Content-Type': 'application/json',
...options.headers,
},
});
// Update rate limit gauge
const remaining = response.headers.get('X-RateLimit-Remaining');
if (remaining) rateLimitGauge.set(parseInt(remaining));
const status = response.ok ? 'success' : `${response.status}`;
requestCounter.inc({ method, endpoint, status });
if (!response.ok) {
const body = await response.json().catch(() => ({}));
errorCounter.inc({
status_code: String(response.status),
error_type: body.ECODE ?? 'unknown',
});
throw new Error(`ClickUp ${response.status}: ${body.err}`);
}
return response.json();
} catch (error) {
if (!(error instanceof Error && error.message.startsWith('ClickUp'))) {
errorCounter.inc({ status_code: 'network', error_type: 'fetch_error' });
}
throw error;
} finally {
timer();
}
}
OpenTelemetry Tracing
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('clickup-integration', '1.0.0');
async function tracedClickUpCall<T>(
operationName: string,
path: string,
fn: () => Promise<T>
): Promise<T> {
return tracer.startActiveSpan(`clickup.${operationName}`, async (span) => {
span.setAttribute('clickup.path', path);
span.setAttribute('clickup.method', 'GET');
try {
const result = await fn();
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error: any) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
throw error;
} finally {
span.end();
}
});
}
Structured Logging
import pino from 'pino';
const logger = pino({ name: 'clickup', level: process.env.LOG_LEVEL ?? 'info' });
function logClickUpCall(data: {
method: string;
path: string;
status: number;
durationMs: number;
rateLimitRemaining?: number;
error?: string;
}): void {
const level = data.status >= 500 ? 'error' : data.status >= 400 ? 'warn' : 'info';
logger[level]({
service: 'clickup',
...data,
}, `ClickUp ${data.method} ${data.path} -> ${data.status} (${data.durationMs}ms)`);
}
Alert Rules
# prometheus/clickup_alerts.yaml
groups:
- name: clickup
rules:
- alert: ClickUpHighErrorRate
expr: rate(clickup_errors_total[5m]) / rate(clickup_requests_total[5m]) > 0.05
for: 5m
labels: { severity: warning }
annotations:
summary: "ClickUp API error rate > 5%"
- alert: ClickUpHighLatency
expr: histogram_quantile(0.95, rate(clickup_request_duration_seconds_bucket[5m])) > 3
for: 5m
labels: { severity: warning }
annotations:
summary: "ClickUp P95 latency > 3s"
- alert: ClickUpRateLimitLow
expr: clickup_rate_limit_remaining < 10
for: 1m
labels: { severity: critical }
annotations:
summary: "ClickUp rate limit nearly exhausted"
- alert: ClickUpAuthFailures
expr: increase(clickup_errors_total{status_code="401"}[5m]) > 0
labels: { severity: critical }
annotations:
summary: "ClickUp authentication failures detected"
Metrics Endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', registry.contentType);
res.send(await registry.metrics());
});
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| High cardinality | Dynamic IDs in labels | Normalize paths (replace IDs with :id) |
| Missing metrics | Uninstrumented code path | Wrap all API calls through instrumented client |
| Alert storm | Threshold too sensitive | Tune for duration and threshold |
| Trace gaps | Missing context propagation | Ensure span context is passed |
Resources
Next Steps
For incident response, see clickup-incident-runbook.
> related_skills --same-repo
> fathom-cost-tuning
Optimize Fathom API usage and plan selection. Trigger with phrases like "fathom cost", "fathom pricing", "fathom plan".
> fathom-core-workflow-b
Sync Fathom meeting data to CRM and build automated follow-up workflows. Use when integrating Fathom with Salesforce, HubSpot, or custom CRMs, or creating automated post-meeting email summaries. Trigger with phrases like "fathom crm sync", "fathom salesforce", "fathom follow-up", "fathom post-meeting workflow".
> fathom-core-workflow-a
Build a meeting analytics pipeline with Fathom transcripts and summaries. Use when extracting insights from meetings, building CRM sync, or creating automated meeting follow-up workflows. Trigger with phrases like "fathom analytics", "fathom meeting pipeline", "fathom transcript analysis", "fathom action items sync".
> fathom-common-errors
Diagnose and fix Fathom API errors including auth failures and missing data. Use when API calls fail, transcripts are empty, or webhooks are not firing. Trigger with phrases like "fathom error", "fathom not working", "fathom api failure", "fix fathom".