Ergate
FeaturesMarketplacePricingDocsAbout
Sign inGet Started
Documentation
Cmd+K
Welcome
Getting Started
  • Sign Up
  • Onboarding
  • Your First Proposal
Proposals
  • Creating a Proposal
  • How the AI Works
  • Editing
  • Pricing Calculator
  • Timeline
  • Proposal Score
  • Exporting
  • Sharing
Clients
Templates
Prompt Packages
Pipeline Skills
Marketplace
  • Selling
API & Integrations
  • Authentication
  • Proposals API
  • AI Pipeline & Export
  • Webhooks
  • Sample Code
Settings
  • Profile
  • Branding
  • AI Prompts
  • Billing
  • Team
Billing
  • Plans
  • Credits
DocsApi referenceSample Code

Sample Code

Complete integration examples in Node.js, Python, and cURL.

5 min read · Last updated: 2026-03-17

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"}'
Previous
Webhooks
Next
Settings

On this page

  • Node.js / TypeScript — Full Pipeline
  • Node.js — Webhook Receiver
  • Python — Webhook Receiver
  • Python — Full Pipeline Client
  • cURL — Quick Start
Ergate

AI-powered proposal platform for service businesses. Turn client briefs into winning proposals.

Product

  • Features
  • Pricing
  • About

Resources

  • Documentation
  • Getting Started
  • Marketplace

Legal

  • Privacy Center
  • Privacy Policy
  • Terms of Service

© 2026 Ergate. All rights reserved.

Built with ♥ in San Francisco