Sample Code
Complete integration examples in Node.js, Python, and cURL.
5 min read · Last updated:
Node.js / TypeScript — Full Pipeline
Create a proposal from a brief, run the full AI pipeline, and download the exported PDF.
const API_BASE = "https://ergate.ai/api/v1";
const API_KEY = "ek_live_abc123...";
const API_SECRET = "es_live_xyz789...";
const headers = {
"Content-Type": "application/json",
"X-API-Key": API_KEY,
"X-API-Secret": API_SECRET,
};
async function api(method: string, path: string, body?: object) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
return json.data;
}
// 1. Create a draft proposal
const proposal = await api("POST", "/proposals", {
title: "Website Redesign Proposal",
rawInputText: `
We need a complete redesign of our e-commerce website.
Current site has 50k monthly visitors, built on WordPress.
Budget range: $10,000–$20,000. Timeline: 6–8 weeks.
Must include mobile-first responsive design, Stripe integration,
and a new product catalog with filtering and search.
`,
clientId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
});
console.log("Created proposal:", proposal.id);
// 2. Trigger analysis (returns immediately with 202)
await api("POST", `/proposals/${proposal.id}/analyze`);
console.log("Analysis started — waiting for webhook...");
// 3. After receiving proposal.analysis_completed webhook:
await api("POST", `/proposals/${proposal.id}/generate`);
console.log("Generation started — waiting for webhook...");
// 4. After receiving proposal.generation_completed webhook:
await api("POST", `/proposals/${proposal.id}/score`);
// 5. After receiving proposal.scoring_completed webhook:
const exportResult = await api("POST", `/proposals/${proposal.id}/export`, {
format: "pdf",
});
console.log("Download PDF:", exportResult.downloadUrl);
Node.js — Webhook Receiver
Express server that receives webhook events and verifies signatures.
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";
const WEBHOOK_SECRET = "whsec_abc123..."; // from POST /webhooks response
const app = express();
// IMPORTANT: use raw body for signature verification
app.use("/webhooks/ergate", express.raw({ type: "application/json" }));
function verifySignature(body: Buffer, signature: string): boolean {
const expected = "sha256=" +
createHmac("sha256", WEBHOOK_SECRET).update(body).digest("hex");
if (expected.length !== signature.length) return false;
return timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post("/webhooks/ergate", (req, res) => {
const signature = req.headers["x-ergate-signature"] as string;
if (!signature || !verifySignature(req.body, signature)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
console.log(`Received ${event.type}:`, event.data);
switch (event.type) {
case "proposal.analysis_completed":
console.log(`Proposal ${event.data.proposalId} analysis done`);
// Trigger generation next...
break;
case "proposal.generation_completed":
console.log(`Proposal ${event.data.proposalId} generated`);
// Trigger scoring or export...
break;
case "proposal.scoring_completed":
console.log(`Proposal ${event.data.proposalId} scored`);
// Export and notify user...
break;
case "proposal.analysis_failed":
case "proposal.generation_failed":
console.error(`Pipeline failed for ${event.data.proposalId}`);
// Handle failure — retry or alert...
break;
case "proposal.exported":
console.log(`Exported ${event.data.format} for ${event.data.proposalId}`);
break;
}
res.status(200).json({ received: true });
});
app.listen(3001, () => console.log("Webhook server listening on :3001"));
Use express.raw() instead of express.json() for the webhook route. Signature verification needs the raw body bytes — parsing JSON first changes the byte representation.
Python — Webhook Receiver
Flask server with HMAC-SHA256 signature verification.
import hashlib
import hmac
import json
from flask import Flask, request, abort
WEBHOOK_SECRET = "whsec_abc123..."
app = Flask(__name__)
def verify_signature(payload: bytes, signature: str) -> bool:
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
@app.route("/webhooks/ergate", methods=["POST"])
def handle_webhook():
signature = request.headers.get("X-Ergate-Signature", "")
if not verify_signature(request.data, signature):
abort(401)
event = json.loads(request.data)
event_type = event["type"]
data = event["data"]
if event_type == "proposal.analysis_completed":
print(f"Analysis done for proposal {data['proposalId']}")
elif event_type == "proposal.generation_completed":
print(f"Proposal {data['proposalId']} generated")
elif event_type == "proposal.exported":
print(f"Proposal {data['proposalId']} exported as {data['format']}")
elif event_type.endswith("_failed"):
print(f"Pipeline failed: {event_type} for {data['proposalId']}")
return {"received": True}, 200
if __name__ == "__main__":
app.run(port=3001)
Python — Full Pipeline Client
Uses polling to wait for async pipeline stages. For production use, prefer webhooks instead.
import requests
import time
API_BASE = "https://ergate.ai/api/v1"
HEADERS = {
"Content-Type": "application/json",
"X-API-Key": "ek_live_abc123...",
"X-API-Secret": "es_live_xyz789...",
}
def api(method: str, path: str, json_body: dict = None) -> dict:
resp = requests.request(
method, f"{API_BASE}{path}", headers=HEADERS, json=json_body
)
data = resp.json()
if not resp.ok:
raise Exception(f"{data['error']['code']}: {data['error']['message']}")
return data["data"]
def wait_for_status(proposal_id: str, target: str, timeout: int = 120):
"""Poll until proposal reaches target status."""
start = time.time()
while time.time() - start < timeout:
proposal = api("GET", f"/proposals/{proposal_id}")
if proposal["status"] == target:
return proposal
if proposal["status"] == "draft":
raise Exception("Pipeline stage failed — status reset to draft")
time.sleep(3)
raise TimeoutError(f"Proposal did not reach '{target}' within {timeout}s")
# 1. Create proposal
proposal = api("POST", "/proposals", {
"title": "Mobile App Development",
"rawInputText": "We need a cross-platform mobile app for our fitness brand...",
})
print(f"Created: {proposal['id']}")
# 2. Analyze
api("POST", f"/proposals/{proposal['id']}/analyze")
wait_for_status(proposal["id"], "analyzed")
print("Analysis complete")
# 3. Generate
api("POST", f"/proposals/{proposal['id']}/generate")
wait_for_status(proposal["id"], "ready")
print("Generation complete")
# 4. Score (optional)
api("POST", f"/proposals/{proposal['id']}/score")
time.sleep(10)
# 5. Export
result = api("POST", f"/proposals/{proposal['id']}/export", {"format": "pdf"})
print(f"Download: {result['downloadUrl']}")
cURL — Quick Start
# Set your credentials
export API_KEY="ek_live_abc123..."
export API_SECRET="es_live_xyz789..."
export BASE="https://ergate.ai/api/v1"
# Create a proposal
curl -s -X POST "$BASE/proposals" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET" \
-d '{
"title": "Website Redesign",
"rawInputText": "Redesign our marketing site. Budget: $15k."
}'
# List proposals
curl -s "$BASE/proposals?limit=5" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET"
# Trigger analysis (replace PROPOSAL_ID)
curl -s -X POST "$BASE/proposals/PROPOSAL_ID/analyze" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET"
# Check usage
curl -s "$BASE/usage" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET"
# Register a webhook
curl -s -X POST "$BASE/webhooks" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET" \
-d '{
"url": "https://example.com/webhooks/ergate",
"events": [
"proposal.created",
"proposal.analysis_completed",
"proposal.generation_completed"
],
"sources": ["api"]
}'
# Test a webhook endpoint (replace ENDPOINT_ID)
curl -s -X POST "$BASE/webhooks/ENDPOINT_ID/test" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET"
# Export to PDF (replace PROPOSAL_ID)
curl -s -X POST "$BASE/proposals/PROPOSAL_ID/export" \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-H "X-API-Secret: $API_SECRET" \
-d '{"format": "pdf"}'