Back to Docs

Branded Western Astrology text reports

Branded Western Astrology text reports

Generate customizable full-text reports and branded PDFs

POST https://api.freeastroapi.com/api/v1/natal/calculate

Authentication header: x-api-key

Use the natal endpoint with psychological_report.enabled=true to generate rich Western astrology text reports, structured markdown/JSON output, and downloadable PDFs with your profile logo in the footer.

Full report generation usually takes about 5-9 minutes. This is expected because the report is produced through a rich multi-pass pipeline: chart synthesis, evidence ranking, chapter planning, drafting, revision, and validation.

Build the payload in the pipeline editor

The custom report builder lets you configure chart inputs, chapter structure, evidence topics, tone and depth, chart styling, PDF delivery, footer labels, saved presets, and profile branding without hand-writing JSON first.

Open pipeline editor
Safe retries with Idempotency-Key

Authenticated, billable astrology POST requests accept the optional header Idempotency-Key: <client-generated unique operation key>. Reuse the same key only when retrying the exact same method, path, query string, and JSON body after a timeout or network failure.

A completed replay returns the first response with Idempotency-Replayed: true, does not rerun the calculation, and does not consume extra quota. Keys are retained for about 24 hours.

Reusing a key with a changed request returns 409 idempotency_key_reused. A duplicate while the first request is still running returns 409 request_in_progress with Retry-After.

Zero-Credit Test Chart

Use this exact synthetic chart payload with the canonical demo pipeline to test the branded report flow without spending report credits. The bypass is intentionally narrow: same synthetic birth signature, standard report generation, and no custom chapter structure or custom generation instructions.

{
  "name": "Synthetic Demo Candidate",
  "year": 1992,
  "month": 11,
  "day": 23,
  "hour": 14,
  "minute": 17,
  "city": "Lisbon",
  "tz_str": "Auto",
  "psychological_report": {
    "enabled": true,
    "mode": "async",
    "style": "standard",
    "output_format": "pdf",
    "include_chart_in_pdf": true,
    "include_profile_branding_logo": true,
    "pdf_footer_label": "Acme Astrology",
    "markdown": true
  }
}

To test a custom chapter structure, send the same birth data with a minimal report_structure. This verifies customization, but it is treated as normal report generation and consumes 1 report credit unless the exact same payload returns from cache:

{
  "name": "Synthetic Demo Candidate",
  "year": 1992,
  "month": 11,
  "day": 23,
  "hour": 14,
  "minute": 17,
  "city": "Lisbon",
  "tz_str": "Auto",
  "psychological_report": {
    "enabled": true,
    "mode": "async",
    "style": "standard",
    "output_format": "pdf",
    "include_chart_in_pdf": true,
    "include_profile_branding_logo": true,
    "pdf_footer_label": "Acme Astrology",
    "markdown": true,
    "report_structure": {
      "chapters": [
        {
          "id": "orientation",
          "enabled": true,
          "title": "Orientation",
          "question": "What core psychological pattern does this chart describe?",
          "target_words": "450-650",
          "evidence_topics": ["identity", "temperament", "integration"],
          "subsections": [
            {
              "heading": "Dominant pattern",
              "instructions": "Name the chart's main psychological pattern."
            },
            {
              "heading": "Developmental direction",
              "instructions": "Describe the growth task without fatalistic prediction."
            }
          ]
        }
      ]
    }
  }
}

Any other birth data or substantive pipeline customization follows normal report credit billing.

Download Full Sample Outputs

These files are full outputs generated from the same synthetic demo chart, provided as fixed snapshots for integration preview.

Request Parameters

ParameterTypeReqDescription
psychological_report.enabledbooleanYesEnable report generation. Default: false.
psychological_report.modestringNoReport delivery mode: async or sync. Default: async.
psychological_report.stylestringNoReport style: standard or jungian_parental. Default: standard.
psychological_report.markdownbooleanNoInclude report_markdown in completed responses. Default: false.
psychological_report.output_formatstringNoSet to pdf when you want a downloadable PDF URL. Default: json.
psychological_report.include_chart_in_pdfbooleanNoAdd a natal chart cover page to PDF output. Default: false.
psychological_report.include_profile_branding_logobooleanNoApply the API-key owner's profile branding logo to PDF footers. Default: true.
psychological_report.pdf_footer_labelstringNoCustom text printed in the PDF footer before date and page number.
psychological_report.report_structureobjectNoCustom chapter plan with titles, questions, target words, evidence topics, and subsections.
psychological_report.tone / depth / audiencestringNoWriting controls used by the report pipeline.
psychological_report.focus_areasstring[]NoHigh-level report priorities, such as identity, relationships, or integration.
psychological_report.custom_instructionsstringNoAdditional generation guidance for the report pipeline.
psychological_report.include_natal_chart_in_responsebooleanNoInclude natal chart payload in report polling response. Default: false.

Create a branded custom report

curl -X POST "https://api.freeastroapi.com/api/v1/natal/calculate" \
 -H "Content-Type: application/json" \
 -H "x-api-key: YOUR_API_KEY" \
 -H "Idempotency-Key: report-synthetic-demo-001" \
 -d '{
    "name": "Synthetic Demo Candidate",
    "year": 1992,
    "month": 11,
    "day": 23,
    "hour": 14,
    "minute": 17,
    "city": "Lisbon",
    "tz_str": "Auto",
    "include_fixed_stars": true,
    "fixed_stars": ["Sirius", "Regulus", "Spica"],
    "include_dominants": true,
    "include_rulers": true,
    "psychological_report": {
      "enabled": true,
      "mode": "async",
      "style": "standard",
      "output_format": "pdf",
      "include_chart_in_pdf": true,
      "include_profile_branding_logo": true,
      "pdf_footer_label": "Acme Astrology",
      "markdown": true,
      "tone": "essayistic",
      "depth": "deep",
      "audience": "advanced astrology app user",
      "focus_areas": ["identity", "relationships", "integration"],
      "custom_instructions": "Prioritize precise psychological synthesis over generic placement descriptions.",
      "report_structure": {
        "chapters": [
          {
            "id": "orientation",
            "enabled": true,
            "title": "Orientation",
            "question": "What core psychological pattern does this chart describe?",
            "target_words": "450-650",
            "evidence_topics": ["identity", "temperament", "integration"],
            "subsections": [
              {
                "heading": "Dominant pattern",
                "instructions": "Name the chart's main psychological pattern."
              },
              {
                "heading": "Developmental direction",
                "instructions": "Describe the growth task without fatalistic prediction."
              }
            ]
          }
        ]
      }
    }
 }'

Poll and download the PDF

  1. Create report request with mode: "async".
  2. Read job_id and next_poll_after_seconds.
  3. Poll GET /api/v1/natal/report/{job_id}?format=json using the server cadence.
  4. When status is completed, download the PDF with format=pdf.
  5. Use include_profile_branding_logo=false on the PDF request if you need an unbranded copy.
{
  "subject": { "...": "normal natal chart payload..." },
  "credits_used": 0,
  "psychological_report": {
    "status": "pending",
    "mode": "async", 
    "style": "standard",
    "output_format": "pdf",
    "include_chart_in_pdf": true,
    "job_id": "7a7f3f9f-...",
    "fetch_url": "/api/v1/natal/report/7a7f3f9f-...",
    "pdf_url": "/api/v1/natal/report/7a7f3f9f-...?format=pdf&include_profile_branding_logo=true",
    "credits_used": 0,
    "include_profile_branding_logo": true,
    "next_poll_after_seconds": 15
  }
}
curl -X GET "https://api.freeastroapi.com/api/v1/natal/report/{job_id}?format=json" \
 -H "x-api-key: YOUR_API_KEY"
{
  "job_id": "7a7f3f9f-...",
  "status": "completed",
  "credits_used": 0,
  "report_metadata": {
    "report_id": "e5efe6ea-...",
    "title": "Jane Example Natal Psychological Report",
    "generated_at": "2026-03-21T11:44:09.531Z",
    "provider": "openai-compatible",
    "model": "gpt-5.3-chat-latest",
    "summary": "Core report summary...",
    "word_count": 7350
  },
  "report_markdown": "## Introduction ... (full markdown report)"
}
curl -L "https://api.freeastroapi.com/api/v1/natal/report/{job_id}?format=pdf&include_profile_branding_logo=true" \
 -H "x-api-key: YOUR_API_KEY" \
 -o branded-western-report.pdf

Credits and Cache

  • This flow uses special report credits, not normal request quota.
  • The published synthetic test chart is exempt only for the canonical demo/default pipeline and returns credits_used: 0 for endpoint testing.
  • Customizing generation substance consumes 1 report credit, even on the synthetic test chart. This includes custom report_structure, chapter questions/subsections, custom_instructions, focus_areas, tone/depth/audience changes, or non-default report style.
  • Fresh report generation consumes 1 report credit (credits_used: 1).
  • A cache hit for the same birth data and same report settings reuses the stored report and does not consume another credit.
  • The same payload, including the same custom report_structure, can return the same completed job/report from cache.
  • Use Idempotency-Key for safe retries of the original POST request.

Output Format

Completed responses always include report_metadata. Output body is exclusive: with psychological_report.markdown=false (default) you receive report_content; with psychological_report.markdown=true you receive report_markdown.

Profile Branding

  • The logo is stored on the user profile, not inside the report request payload.
  • Upload or replace it from the Delivery panel in the custom report builder.
  • PDF downloads use the logo attached to the account that owns the API key.
  • Set include_profile_branding_logo=false on the request or PDF URL to omit it.
  • Logo changes are applied at PDF render time; cached report text does not need to be regenerated.

Common Mistakes

  • Missing x-api-key header.
  • Polling too fast instead of respecting next_poll_after_seconds / Retry-After.
  • Providing callback_url without callback_signing_secret.
  • Forgetting that markdown is opt-in. Set psychological_report.markdown=true if you want report_markdown.
  • Expecting the logo to follow an API key from a different account. Branding is tied to the API-key owner profile.

Advanced: Webhook Callback (Optional)

This is optional and more advanced. If you send callback_url and callback_signing_secret, the API sends a signed callback when the job is completed or failed.

ParameterTypeReqDescription
psychological_report.callback_urlstringNoHTTPS callback endpoint for completed/failed report events. Default: null.
psychological_report.callback_signing_secretstringNoWebhook signing secret. Required when callback_url is set. Default: null.
{
  "psychological_report": {
    "enabled": true,
    "mode": "async",
    "output_format": "pdf",
    "callback_url": "https://example.com/webhooks/freeastro/report",
    "callback_signing_secret": "replace-with-a-long-random-secret"
  }
}
{
  "event": "psychological_report.completed",
  "job_id": "7a7f3f9f-...",
  "status": "completed",
  "credits_used": 1,
  "report_metadata": { "...": "same metadata object as polling response" },
  "report_markdown": "## Introduction ...",
  "created_at": "2026-03-21T11:44:09.531Z"
} 

Headers:
X-FreeAstro-Event: psychological_report.completed
X-FreeAstro-Timestamp: 1711025109
X-FreeAstro-Signature: <HMAC_SHA256_HEX>
import crypto from "node:crypto";
import express from "express";

const app = express();
app.use(express.raw({ type: "application/json" }));

app.post("/webhooks/freeastro/report", (req, res) => {
  const rawBody = req.body.toString("utf8");
  const timestamp = req.header("X-FreeAstro-Timestamp") || "";
  const signature = req.header("X-FreeAstro-Signature") || "";
  const secret = process.env.FREE_ASTRO_CALLBACK_SECRET || "";

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send("invalid signature");
  }

  const event = JSON.parse(rawBody);
  // Save report by event.job_id, then ack
  return res.status(200).json({ ok: true });
});