hubspot-agency-multi-portal
Manage 10-100 HubSpot portals for agency clients with credential isolation that prevents cross-portal data contamination, per-portal audit trails for billing and GDPR/CCPA attribution, and a scriptable bulk-onboarding workflow that eliminates one-at-a-time credential setup. Use when onboarding new client portals, building a compliant per-client API call log, rotating tokens across a full agency fleet, or generating per-client compliance reports. Trigger with "hubspot agency", "multi-portal management", "hubspot credential isolation", "per-portal audit log", "hubspot compliance report", "bulk portal onboarding", "token rotation cascade", "hubspot client portals".
What this skill does
# HubSpot Agency Multi-Portal
## Overview
Operate a fleet of HubSpot portals for agency clients without cross-portal contamination, attribution loss, or onboarding bottlenecks. This is not a getting-started guide — it is the infrastructure your agency runs on day one with client one and scales to client one hundred without revisiting.
The six production failures this skill prevents:
1. **Cross-portal credential contamination** — a shared `HUBSPOT_ACCESS_TOKEN` env var causes API writes intended for Client A to silently land in Client B's CRM. The HubSpot API does not reject the call; it accepts it. Data corruption is silent and may not be discovered for days. Per-portal credential isolation — enforced in code, not convention — is the only fix.
2. **Audit trail gaps** — agency billing, SLA compliance, and GDPR/CCPA data-processing agreements all require proof of which API calls were made on behalf of which client. A shared token makes post-hoc attribution impossible. A per-portal structured audit log with portalId, clientSlug, operation, and timestamp makes attribution irrefutable.
3. **Bulk onboarding bottleneck** — onboarding 50 new clients one-at-a-time requires 50 manual credential setups, 50 manual verifications, and 50 opportunities for human error. A scriptable bulk onboarding workflow reads a CSV of client names and tokens, validates each against the account-info endpoint, and seeds the credential store in one pass.
4. **Token rotation cascade** — rotating one client's private-app token in HubSpot does not update any downstream system. With 50 portals, a partial rotation — some systems updated, some not — leaves stale tokens in production for undetermined periods. A per-portal rotation runbook with a cross-system checklist closes the gap.
5. **Rate-limit aggregation confusion** — each portal has its own independent 500K/day quota. An agency analytics system reading all 50 portals is NOT limited to 500K calls total — it has 500K per portal per day, but only if the token used for each portal belongs to that portal. A shared token collapses all quota attribution to one portal, causing artificial exhaustion and incorrect monitoring.
6. **Compliance reporting ambiguity** — under GDPR Article 30 and CCPA, a data processor (the agency) must demonstrate which operations were performed on which controller's (client's) data and when. A shared token makes this demonstration impossible after the fact. Per-portal audit logs with structured fields make it a simple query.
## Prerequisites
- Node.js 18+ or Python 3.10+
- One HubSpot private-app token per client portal (Settings → Integrations → Private Apps → Create private app → Auth tab)
- A secret store the credential router can read at startup: AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or `pass` for local development
- `jq` installed for CLI validation steps
- `python3` with standard library only for bulk onboarding script (no external deps required)
- CSV file of client slugs and tokens for bulk onboarding (format: `clientSlug,portalToken,portalId`)
## Instructions
Build in this order. Each section closes one production failure mode.
### 1. Credential store design (closes cross-portal contamination)
The credential store is a JSON map of `clientSlug → token`. It lives in your secret manager, never in source code or environment variables. The key insight is that `clientSlug` is the primary key — every operation starts by selecting a slug, which deterministically selects the token. There is no ambient credential and no fallback to a global env var.
```typescript
// Shape of the credential store (stored in secret manager, NOT in git or env vars)
interface PortalCredentialStore {
version: number; // increment on every write; used for drift detection
portals: Record<string, PortalCredential>;
}
interface PortalCredential {
token: string; // HubSpot private-app token: pat-na1-...
portalId: number; // HubSpot portal ID — verified via account-info on onboard
clientSlug: string; // kebab-case client identifier: "acme-corp"
addedAt: string; // ISO 8601 — when this credential was seeded
lastRotatedAt: string; // ISO 8601 — updated on every token rotation
datacenter: string; // "na1" | "eu1" | "au1" — extracted from token prefix
}
// Load from secret manager at process startup — never re-read per-request
async function loadCredentialStore(): Promise<PortalCredentialStore> {
const raw = await readSecret("hubspot/agency-portals");
const store: PortalCredentialStore = JSON.parse(raw);
if (!store.portals || typeof store.portals !== "object") {
throw new Error("Credential store is malformed — missing portals map");
}
return store;
}
```
The `datacenter` field matters: a `pat-na1-*` token sent to `api.hubapi.com` (which routes to `na1`) will work, but if HubSpot migrates the portal to `eu1`, the token prefix changes and calls to the wrong datacenter return 404. Storing the datacenter alongside the token surfaces this mismatch immediately.
### 2. Portal identity verification (confirm token points to expected portal)
Every token must be verified against the `GET /account-info/v3/details` endpoint before being admitted to the credential store. This endpoint returns the portalId for the token's portal — which is the ground truth for "which portal does this token belong to."
```typescript
interface PortalDetails {
portalId: number;
timeZone: string;
currency: string;
portalType: string; // "STANDARD" | "DEVELOPER" | "SANDBOX" | "TRIAL"
}
async function verifyPortalIdentity(
token: string,
expectedPortalId?: number
): Promise<PortalDetails> {
const res = await fetch("https://api.hubapi.com/account-info/v3/details", {
headers: { Authorization: `Bearer ${token}` },
});
if (res.status === 401) {
throw new Error("Token rejected (401) — revoked, malformed, or wrong datacenter");
}
if (res.status === 403) {
throw new Error("Token lacks account-info scope — re-create private app with account-info scope");
}
if (!res.ok) {
throw new Error(`account-info returned ${res.status}: ${await res.text()}`);
}
const details: PortalDetails = await res.json();
if (expectedPortalId !== undefined && details.portalId !== expectedPortalId) {
throw new Error(
`Portal ID mismatch — token belongs to portal ${details.portalId}, ` +
`expected ${expectedPortalId}. Token is for the wrong client.`
);
}
return details;
}
```
Run `verifyPortalIdentity` during onboarding (to populate `portalId` in the credential store) and during rotation (to confirm the new token belongs to the same portal before committing it).
### 3. Per-portal HTTP client factory with audit-log middleware (closes audit trail gaps)
The client factory produces an HTTP client bound to a single portal's token. Every request made through this client is logged to the audit trail with a structured record. There is no way to make an unlogged HubSpot API call through this factory — the audit middleware is non-optional.
```typescript
interface AuditRecord {
ts: string; // ISO 8601 timestamp
portalId: number;
clientSlug: string;
method: string; // GET | POST | PATCH | DELETE
path: string; // /crm/v3/objects/contacts
statusCode: number;
durationMs: number;
objectType?: string; // "contacts" | "deals" | "companies" — extracted from path
objectId?: string; // from path or response body
actor: string; // "hubspot-agency-router/2.0.0"
rateLimitRemaining?: number; // from X-HubSpot-RateLimit-Daily-Remaining header
}
type AuditWriter = (record: AuditRecord) => void | Promise<void>;
function createPortalClient(
credential: PortalCredential,
auditWriter: AuditWriter
) {
const baseUrl = "https://api.hubapi.com";
return async function portalFetch(
path: string,
init: RequestInit = {}
): Promise<Response> {
const start = Date.now();
const methoRelated in Backend & APIs
jfrog
IncludedInteract with the JFrog Platform via the JFrog CLI and REST/GraphQL APIs. Use this skill when the user wants to manage Artifactory repositories, upload or download artifacts, manage builds, configure permissions, manage users and groups, work with access tokens, configure JFrog CLI servers, search artifacts, manage properties, set up replication, manage JFrog Projects, run security audits or scans, look up CVE details, query exposures scan results from JFrog Advanced Security, manage release bundles and lifecycle operations, aggregate or export platform data, or perform any JFrog Platform administration task. Also use when the user mentions jf, jfrog, artifactory, xray, distribution, evidence, apptrust, onemodel, graphql, workers, mission control, curation, advanced security, exposures, or any JFrog product name.
cupynumeric-migration-readiness
IncludedPre-migration readiness assessor for porting NumPy to cuPyNumeric. Use BEFORE substantial porting work begins when the user asks whether code will scale on GPU, whether they should migrate to cuPyNumeric, which NumPy patterns transfer cleanly, what must be refactored before porting, or mentions pre-port assessment, scaling analysis, or refactor planning. Inspect the user's source code, look up NumPy usage, cross-reference the cuPyNumeric API support manifest, and distinguish distributed-scaling-friendly patterns from blockers such as unsupported APIs, scalar synchronization, host round-trips, Python/object-heavy control flow, shape/data-dependent branching, and in-place mutation hazards. Produce a verdict of READY, LIGHT REFACTOR, SIGNIFICANT REFACTOR, or NOT RECOMMENDED, with concrete refactor pointers.
alibabacloud-data-agent-skill
IncludedInvoke Alibaba Cloud Apsara Data Agent for Analytics via CLI to perform natural language-driven data analysis on enterprise databases. Data Agent for Analytics is an intelligent data analysis agent developed by Alibaba Cloud Database team for enterprise users. It automatically completes requirement analysis, data understanding, analysis insights, and report generation based on natural language descriptions. This tool supports: discovering data resources (instances/databases/tables) managed in DMS, initiating query or deep analysis sessions, real-time progress tracking, and retrieving analysis conclusions and generated reports. Use this Skill when users need to query databases, analyze data trends, generate data reports, ask questions in natural language, or mention "Data Agent", "data analysis", "database query", "SQL analysis", "data insights".
token-optimizer
IncludedReduce OpenClaw token usage and API costs through smart model routing, heartbeat optimization, budget tracking, and native 2026.2.15 features (session pruning, bootstrap size limits, cache TTL alignment). Use when token costs are high, API rate limits are being hit, or hosting multiple agents at scale. The 4 executable scripts (context_optimizer, model_router, heartbeat_optimizer, token_tracker) are local-only — no network requests, no subprocess calls, no system modifications. Reference files (PROVIDERS.md, config-patches.json) document optional multi-provider strategies that require external API keys and network access if you choose to use them. See SECURITY.md for full breakdown.
resend-cli
IncludedUse this skill when the task is specifically about operating Resend from an AI agent, terminal session, or CI job via the official resend CLI: installing/authenticating the CLI, sending/listing/updating/cancelling emails, batch sends, domains and DNS, webhooks and local listeners, inbound receiving, contacts, topics, segments, broadcasts, templates, API keys, profiles, or debugging Resend CLI/API failures. Trigger on mentions of Resend CLI, `resend`, `resend doctor`, `resend emails send`, `resend domains`, `resend webhooks listen`, `resend emails receiving`, or agent-friendly terminal automation.
alibabacloud-odps-maxframe-coding
IncludedUse this skill for MaxFrame SDK development and documentation navigation on Alibaba Cloud MaxCompute (ODPS). Helps answer MaxFrame API, concept, official example, and supported pandas API questions; create data processing programs; read/write MaxCompute tables; debug jobs (remote or local); and build custom DPE runtime images. Trigger when users mention MaxFrame, MaxCompute with MaxFrame, ODPS table processing, DPE runtime, MaxFrame docs/examples, DataFrame/Tensor operations, or GPU runtime setup. Works for both English and Chinese queries about Alibaba Cloud data processing with MaxFrame.