diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..5b03fdb6
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,39 @@
+name: Release
+
+on:
+ push:
+ branches: [main]
+
+concurrency:
+ group: release-${{ github.ref }}
+ cancel-in-progress: false
+
+permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+
+env:
+ NODE_VERSION: '20'
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ if: github.repository == 'Smartdevs17/agenticpay'
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - run: npm ci
+
+ - name: Release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: npx semantic-release
diff --git a/.gitpod.yml b/.gitpod.yml
new file mode 100644
index 00000000..d2187614
--- /dev/null
+++ b/.gitpod.yml
@@ -0,0 +1,38 @@
+image:
+ file: .devcontainer/Dockerfile
+
+tasks:
+ - name: Setup
+ init: |
+ npm ci --prefer-offline || npm install
+ if command -v rustup >/dev/null 2>&1; then
+ rustup target add wasm32-unknown-unknown
+ fi
+ (cd backend && npm run db:generate)
+ command: |
+ docker compose -f docker-compose.yml up -d postgres redis
+ npm run dev
+
+ports:
+ - name: Frontend
+ port: 3000
+ onOpen: open-preview
+ - name: Backend API
+ port: 3001
+ onOpen: ignore
+ - name: PostgreSQL
+ port: 5432
+ onOpen: ignore
+ visibility: private
+ - name: Redis
+ port: 6379
+ onOpen: ignore
+ visibility: private
+
+vscode:
+ extensions:
+ - dbaeumer.vscode-eslint
+ - esbenp.prettier-vscode
+ - bradlc.vscode-tailwindcss
+ - rust-lang.rust-analyzer
+ - Prisma.prisma
diff --git a/.releaserc.json b/.releaserc.json
new file mode 100644
index 00000000..a18d26f2
--- /dev/null
+++ b/.releaserc.json
@@ -0,0 +1,21 @@
+{
+ "branches": ["main"],
+ "plugins": [
+ "@semantic-release/commit-analyzer",
+ "@semantic-release/release-notes-generator",
+ [
+ "@semantic-release/changelog",
+ {
+ "changelogFile": "CHANGELOG.md"
+ }
+ ],
+ "@semantic-release/github",
+ [
+ "@semantic-release/git",
+ {
+ "assets": ["CHANGELOG.md"],
+ "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
+ }
+ ]
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..f18fe299
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# Changelog
+
+All notable changes to this project are documented in this file.
+
+Entries are generated automatically from
+[Conventional Commits](https://www.conventionalcommits.org/) by
+[semantic-release](https://semantic-release.gitbook.io/) on every push to `main`.
+See `.releaserc.json` and `.github/workflows/release.yml`.
diff --git a/backend/.env.example b/backend/.env.example
index 25346500..319ffb82 100644
--- a/backend/.env.example
+++ b/backend/.env.example
@@ -10,6 +10,10 @@ RATE_LIMIT_ENTERPRISE=1000
RATE_LIMIT_WINDOW_MS=900000
COMPRESSION_THRESHOLD=1024
+# Security headers
+HSTS_MAX_AGE_SECONDS=31536000
+PERMISSIONS_POLICY=camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), interest-cohort=()
+
# IP Allowlist (comma-separated CIDR ranges)
# IP_ALLOWLIST=10.0.0.0/8,192.168.1.0/24
# IP_ALLOWLIST_ENABLED=false
diff --git a/backend/src/config.ts b/backend/src/config.ts
index 3e2b6c5d..28bfd68d 100644
--- a/backend/src/config.ts
+++ b/backend/src/config.ts
@@ -23,6 +23,10 @@ const envSchema = z.object({
DB_POOL_ACQUIRE_TIMEOUT_MS: z.string().default('30000'),
DB_POOL_MAX_USES: z.string().default('7500'),
DB_STATEMENT_TIMEOUT_MS: z.string().default('30000'),
+ HSTS_MAX_AGE_SECONDS: z.string().default('31536000'),
+ PERMISSIONS_POLICY: z
+ .string()
+ .default('camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), interest-cohort=()'),
});
const parsed = envSchema.safeParse(process.env);
@@ -77,6 +81,14 @@ export const config = {
statementTimeoutMs: Number(env.DB_STATEMENT_TIMEOUT_MS),
},
},
+ security: {
+ hsts: {
+ maxAge: Number(env.HSTS_MAX_AGE_SECONDS),
+ includeSubDomains: true,
+ preload: true,
+ },
+ permissionsPolicy: env.PERMISSIONS_POLICY,
+ },
} as const;
export type Config = typeof config;
diff --git a/backend/src/index.ts b/backend/src/index.ts
index d3576b79..69974ffe 100644
--- a/backend/src/index.ts
+++ b/backend/src/index.ts
@@ -11,6 +11,7 @@ import { stellarRouter } from './routes/stellar.js';
import { catalogRouter } from './routes/catalog.js';
import { jobsRouter } from './routes/jobs.js';
import { healthRouter } from './routes/health.js';
+import { docsRouter } from './routes/docs.js';
import { queueRouter } from './routes/queue.js';
import { slaRouter } from './routes/sla.js';
import { startJobs, getJobScheduler } from './jobs/index.js';
@@ -31,7 +32,7 @@ import { pushRouter } from './routes/push.js';
import { ipAllowlistRouter } from './routes/ip-allowlist.js';
import { stripeRouter } from './routes/stripe.js';
import { ipAllowlistMiddleware, initIpAllowlist } from './middleware/ip-allowlist.js';
-import { SecurityMiddleware, SecurityMonitor } from './middleware/security.js';
+import { SecurityMiddleware, SecurityMonitor, securityHeadersMiddleware } from './middleware/security.js';
import { sanitizeInput, contentSecurityPolicy } from './middleware/sanitize.js';
import { notificationsRouter } from './routes/notifications.js';
import { auditRouter } from './routes/audit.js';
@@ -164,6 +165,7 @@ const invoiceLimiter = rateLimit({
legacyHeaders: false,
});
+app.use(securityHeadersMiddleware());
app.use(
cors({
origin: config.cors.allowedOrigins,
@@ -234,6 +236,9 @@ app.use((req: Request, res: Response, next: NextFunction) => {
// Health & Readiness checks
app.use(healthRouter);
+// Interactive API documentation & playground — Issue #758
+app.use('/docs', docsRouter);
+
import { versionMiddleware } from './middleware/versioning.js';
import { portfolioRouter } from './routes/portfolio.js';
diff --git a/backend/src/middleware/security.ts b/backend/src/middleware/security.ts
index f4c44901..9db84e21 100644
--- a/backend/src/middleware/security.ts
+++ b/backend/src/middleware/security.ts
@@ -2,12 +2,33 @@ import { Request, Response, NextFunction } from 'express';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { InputSanitizer, sanitizeInput, contentSecurityPolicy, createSecurityRateLimit } from './sanitize';
+import { config } from '../config';
/**
* Comprehensive Security Middleware Stack
* Implements defense-in-depth security measures
*/
+/**
+ * Security headers middleware — HSTS and Permissions-Policy.
+ *
+ * Kept independent of the CSP-bearing helmet() configuration in
+ * `applySecurity()` so it can be wired into the app without pulling in
+ * CSP directives, which are managed separately.
+ */
+export function securityHeadersMiddleware() {
+ const hsts = helmet.hsts({
+ maxAge: config.security.hsts.maxAge,
+ includeSubDomains: config.security.hsts.includeSubDomains,
+ preload: config.security.hsts.preload,
+ });
+
+ return (req: Request, res: Response, next: NextFunction): void => {
+ res.setHeader('Permissions-Policy', config.security.permissionsPolicy);
+ hsts(req, res, next);
+ };
+}
+
export class SecurityMiddleware {
private static instance: SecurityMiddleware;
private sanitizer: InputSanitizer;
diff --git a/frontend/app/developers/page.tsx b/frontend/app/developers/page.tsx
new file mode 100644
index 00000000..c874eeaa
--- /dev/null
+++ b/frontend/app/developers/page.tsx
@@ -0,0 +1,156 @@
+import type { Metadata } from 'next';
+import Link from 'next/link';
+import { BookOpen, Code2, ExternalLink, FileJson, Terminal } from 'lucide-react';
+
+export const metadata: Metadata = {
+ title: 'Developer Portal | AgenticPay',
+ description:
+ 'Explore the AgenticPay API playground, OpenAPI specification, and official SDKs for TypeScript, Python, and Go.',
+};
+
+const GITHUB_REPO = 'https://github.com/Smartdevs17/agenticpay';
+
+const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3001/api/v1';
+// The interactive docs/playground are served from the API origin, outside of /api/v1.
+const API_ORIGIN = API_BASE.replace(/\/api\/v1\/?$/, '');
+const PLAYGROUND_URL = `${API_ORIGIN}/docs`;
+const OPENAPI_SPEC_URL = `${API_ORIGIN}/docs/openapi.json`;
+
+interface Sdk {
+ language: string;
+ install: string;
+ packagePath: string;
+}
+
+const SDKS: Sdk[] = [
+ { language: 'TypeScript', install: 'npm install @agenticpay/sdk', packagePath: 'packages/sdk' },
+ { language: 'Python', install: 'pip install agenticpay', packagePath: 'sdks/python' },
+ { language: 'Go', install: 'go get github.com/Smartdevs17/agenticpay-sdk-go', packagePath: 'sdks/go' },
+];
+
+interface Guide {
+ title: string;
+ description: string;
+ file: string;
+}
+
+const GUIDES: Guide[] = [
+ { title: 'SDK overview', description: 'Available SDKs, quick start, and authentication.', file: 'docs/sdk/README.md' },
+ { title: 'Migrating from REST', description: 'Move from raw REST calls to the typed SDKs.', file: 'docs/sdk/MIGRATION-FROM-REST.md' },
+ { title: 'Error handling', description: 'The SDK error hierarchy and how to handle it.', file: 'docs/sdk/ERROR-HANDLING.md' },
+ { title: 'Testing', description: 'Mocking and testing code that uses the SDKs.', file: 'docs/sdk/TESTING.md' },
+ { title: 'Versioning', description: 'SDK release and API versioning policy.', file: 'docs/sdk/VERSIONING.md' },
+];
+
+export default function DevelopersPage() {
+ return (
+
+
+
+
+
+
+
+
+ Developer Portal
+
+
+ Build on AgenticPay
+
+
+ Explore the API in an interactive playground, browse the OpenAPI specification,
+ and get started with an official SDK.
+
+
+
+
+
+
+
+ Official SDKs
+
+
+ Typed clients for the AgenticPay API, published from this repository.
+
+
+ {SDKS.map((sdk) => (
+
+
{sdk.language}
+
+ {sdk.install}
+
+
+ View source
+
+
+
+ ))}
+
+
+
+
+
+ Guides
+
+
+ Reference documentation for integrating with the AgenticPay SDKs and API.
+
+
+ {GUIDES.map((guide) => (
+ -
+
+
+
+
+
+ {guide.title}
+
+ {guide.description}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/package.json b/package.json
index dadb5059..4dce5270 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
"generate:api-hooks:watch": "node packages/api-hooks-generator/dist/cli.js --watch",
"analyze:bundle": "bash scripts/analyze-bundle.sh",
"lighthouse:ci": "lhci autorun --config=./lighthouse/lighthouse.config.json",
- "log-viewer": "node packages/log-viewer/dist/cli.js"
+ "log-viewer": "node packages/log-viewer/dist/cli.js",
+ "release": "semantic-release"
},
"devDependencies": {
"turbo": "^1.10.16",