
Integrate Webapi
Confirm a Power Pages code app has a shared Web API client, service modules, and Dataverse-style /_api/ calls after an integration session.
Overview
integrate-webapi is an agent skill for the Build phase that validates Web API client and service integration code on Power Pages code sites.
Install
npx skills add https://github.com/microsoft/power-platform-skills --skill integrate-webapiWhat is this skill?
- Stop-hook validation for Power Pages Web API integration sessions
- Expects shared API client (e.g. src/shared/powerPagesApi.ts)
- Requires service files under src/shared/services/ or src/services/
- Flags service files missing /_api/ endpoint references
- Optional schema validation via powerpages-schema-validator when types are expected
- Checks for /_api/ references in each service file
Adoption & trust: 77 installs on skills.sh; 349 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your agent claimed it wired Power Pages to the Web API, but you cannot tell if clients, services, and /_api/ routes were actually generated.
Who is it for?
Builders extending Power Pages code apps with typed services and a central API client after agent-assisted integration.
Skip if: Standalone SPAs outside Power Pages, pure Canvas apps without code sites, or API design sessions with no expected repo outputs.
When should I use this skill?
After an agent session that should have created Power Pages Web API integration files (shared client plus service modules).
What do I get? / Deliverables
Confirmed integration file layout and /_api/ usage—or explicit errors listing what to add before you ship data-connected features.
- Validation pass or block with file-level integration errors
Recommended Skills
Journey fit
How it compares
Post-session integration checker for Power Pages—not a generic OpenAPI codegen tool or an MCP server.
Common Questions / FAQ
Who is integrate-webapi for?
Solo and indie developers on Power Pages code projects who use coding agents to add Dataverse/Web API access and want automated proof of structure.
When should I use integrate-webapi?
Use it in Build right after sessions that should create powerPagesApi-style clients and service layers referencing /_api/ endpoints.
Is integrate-webapi safe to install?
It reads and analyzes local TypeScript files only during validation; review the Security Audits panel on this page before enabling repository hooks.
SKILL.md
READMESKILL.md - Integrate Webapi
#!/usr/bin/env node // Validates that Web API integration code was created for a Power Pages code site. // Runs as a Stop hook to verify the skill produced output. const fs = require('fs'); const path = require('path'); const { approve, block, runValidation, findProjectRoot } = require('../../../scripts/lib/validation-helpers'); const { validatePowerPagesSchema } = require('../../../scripts/lib/powerpages-schema-validator'); runValidation((cwd) => { const projectRoot = findProjectRoot(cwd); if (!projectRoot) approve(); // Not a Power Pages project, skip // Check if any Web API integration files exist — if none, this wasn't an integration session const apiClientExists = findApiClient(projectRoot); const serviceFiles = findServiceFiles(projectRoot); if (!apiClientExists && serviceFiles.length === 0) approve(); const errors = []; if (!apiClientExists) { errors.push('Missing shared API client (src/shared/powerPagesApi.ts or equivalent)'); } if (serviceFiles.length === 0) { errors.push('No service files found in src/shared/services/ or src/services/'); } for (const serviceFile of serviceFiles) { const content = fs.readFileSync(serviceFile, 'utf8'); if (!content.includes('/_api/')) { errors.push(`${path.basename(serviceFile)}: missing /_api/ endpoint references`); } } const typeFiles = findTypeFiles(projectRoot); if (typeFiles.length === 0 && serviceFiles.length > 0) { errors.push('No type definition files found in src/types/ — services should have corresponding type definitions'); } const schemaValidation = validatePowerPagesSchema(projectRoot); const schemaErrors = schemaValidation.findings .filter(finding => finding.severity === 'error') .map(finding => finding.filePath ? `${finding.message} (${path.basename(finding.filePath)})` : finding.message); if (schemaErrors.length > 0) { errors.push('Invalid Power Pages permissions/site-settings schema:\n - ' + schemaErrors.join('\n - ')); } if (errors.length > 0) { block('Web API integration validation failed:\n- ' + errors.join('\n- ')); } approve(); }); function findApiClient(projectRoot) { const candidates = [ path.join(projectRoot, 'src', 'shared', 'powerPagesApi.ts'), path.join(projectRoot, 'src', 'shared', 'powerPagesApi.js'), path.join(projectRoot, 'src', 'services', 'powerPagesApi.ts'), path.join(projectRoot, 'src', 'services', 'powerPagesApi.js'), ]; for (const candidate of candidates) { if (fs.existsSync(candidate)) return true; } // Fallback: search for any file containing powerPagesFetch export try { const sharedDir = path.join(projectRoot, 'src', 'shared'); if (fs.existsSync(sharedDir)) { for (const file of fs.readdirSync(sharedDir)) { if (file.endsWith('.ts') || file.endsWith('.js')) { const content = fs.readFileSync(path.join(sharedDir, file), 'utf8'); if (content.includes('powerPagesFetch') && content.includes('__RequestVerificationToken')) { return true; } } } } } catch {} return false; } function findServiceFiles(projectRoot) { const serviceDirs = [ path.join(projectRoot, 'src', 'shared', 'services'), path.join(projectRoot, 'src', 'services'), ]; const files = []; for (const dir of serviceDirs) { if (!fs.existsSync(dir)) continue; try { for (const file of fs.readdirSync(dir)) { if ((file.endsWith('Service.ts') || file.endsWith('Service.js')) && !file.startsWith('.')) { files.push(path.join(dir, file)); } } } catch {} } return files; } function findTypeFiles(projectRoot) { const typesDir = path.join(projectRoot, 'src', 'types'); if (!fs.existsSync(typesDir)) return []; const files = []; try { for (const file of fs.readdirSync(typesDir)) { if ((file.endsWith('.ts') || file.endsWith('.js')) && !file.startsWith('.') && file !== 'index.ts') { files.p