Docs/API Reference/API Reference Overview

API Reference Overview

API Reference
4 min read

API Reference Overview

iContentForge exposes APIs for integrating content generation and publishing into your own workflows. This overview covers authentication, signature verification, rate limits, and error handling conventions that apply across all endpoints.

Base URL

https://icontentforge.com/api
鈩癸笍

The Public Sitemap API uses a dedicated path: /api/public/sitemap/:projectId — no authentication required.


Authentication

Bearer Token (Recommended)

Pass your API key in the Authorization header:

curl -X GET 'https://icontentforge.com/api/v1/projects' \
  -H 'Authorization: Bearer YOUR_API_KEY'
鈿狅笍

Never expose API keys in client-side JavaScript. Rotate keys immediately if compromised. Manage your keys from Account Settings → API Keys.


Webhook Signature Verification

iContentForge signs all outgoing webhook requests with HMAC-SHA256. The signature is sent in the X-ICF-Signature HTTP header.

How It Works

  1. iContentForge computes HMAC-SHA256(secret, rawRequestBody) and sends the hex digest in X-ICF-Signature.
  2. Your server reads the raw request body bytes and computes the same HMAC using your shared Secret Key.
  3. Compare the two values using a timing-safe equality check.
鈿狅笍

Critical: You must compute the HMAC over the raw request body string — not a re-serialized version. If you call req.json() (which parses JSON) and then JSON.stringify() again, the byte sequence may differ (field ordering, whitespace), and the signatures will not match.

✅ Correct: rawBody = await req.text() → compute HMAC → then JSON.parse(rawBody)
❌ Wrong: body = await req.json()JSON.stringify(body) → compute HMAC

Verification Example — Node.js / Next.js App Router

import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";

export async function POST(req: NextRequest) {
  // ✅ Step 1: Read raw body BEFORE parsing
  const rawBody = await req.text();

  // ✅ Step 2: Read signature header
  const signature =
    req.headers.get("x-icf-signature") 
    req.headers.get("x-contentforge-signature")  // legacy header
    "";

  // ✅ Step 3: Compute expected HMAC using the same raw bytes
  const secret   = process.env.ICF_WEBHOOK_SECRET!;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)        // ← raw string, NOT JSON.stringify(parsed)
    .digest("hex");

  // ✅ Step 4: Timing-safe comparison
  const valid = (() => {
    try {
      return crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expected)
      );
    } catch {
      return false; // different lengths → invalid
    }
  })();

  if (!valid) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  // ✅ Step 5: Safe to parse now
  const payload = JSON.parse(rawBody);
  console.log("Article received:", payload.title);

  return NextResponse.json({ success: true });
}

Verification Example — Express.js

const express = require("express");
const crypto  = require("crypto");
const app     = express();

// ⚠️ Use express.raw() — NOT express.json() — to get the raw Buffer
app.use("/webhook/icf", express.raw({ type: "application/json" }));

app.post("/webhook/icf", (req, res) => {
  const rawBody   = req.body; // Buffer
  const signature = req.headers["x-icf-signature"]  "";
  const expected  = crypto
    .createHmac("sha256", process.env.ICF_WEBHOOK_SECRET)
    .update(rawBody)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const payload = JSON.parse(rawBody.toString());
  res.json({ received: true });
});

Verification Example — Python / Flask

import hashlib, hmac, json, os
from flask import request, jsonify, abort

@app.route("/webhook/icf", methods=["POST"])
def handle_webhook():
    secret    = os.environ["ICF_WEBHOOK_SECRET"].encode()
    signature = request.headers.get("X-ICF-Signature", "")

    # ✅ get_data() returns raw bytes — do NOT use request.json
    raw_body  = request.get_data()
    expected  = hmac.new(secret, raw_body, hashlib.sha256).hexdigest()

    if not hmac.compare_digest(signature, expected):
        abort(401)

    payload = json.loads(raw_body)
    return jsonify({"received": True}), 200

Available APIs

Generic Webhook (Push)

iContentForge pushes a signed POST request to your endpoint when an article is ready to publish. See the full guide: Generic Webhook Integration.

Public Sitemap API

Returns a dynamically generated XML sitemap for your published articles. No authentication required.

GET /api/public/sitemap/:projectId

# Example
curl https://icontentforge.com/api/public/sitemap/proj_abc123

Rate Limits

Public API rate limits may change as the product evolves. Treat 429 Too Many Requests as the source-of-truth response for temporary throttling and retry using exponential backoff.

Rate-limited responses include headers such as:

HeaderDescription
X-RateLimit-LimitYour limit per minute.
X-RateLimit-RemainingRequests remaining in current window.
X-RateLimit-ResetUnix epoch time when the window resets.

Standard Error Format

{
  "error": "RESOURCE_NOT_FOUND",
  "message": "The requested article could not be found.",
  "details": { "pageId": "cma1b2c3d4e5f" }
}
HTTP CodeMeaning
400Bad Request — malformed JSON or invalid parameters.
401Unauthorized — missing or invalid API key / webhook signature.
404Not Found — project or article does not exist.
429Too Many Requests — rate limit exceeded.
500Internal Server Error — something went wrong on our side.

Next Steps