A downloadable Kundali PDF report is the single highest-perceived-value feature you can add to an astrology app. Users share PDF reports with family members, astrologers, and matrimony contacts. A polished report with planets table, Dasha timeline, Yoga analysis, and AI-generated narrative turns a basic birth chart lookup into a product people pay for.
This guide walks through the complete pipeline: fetching structured data from Vedika API, generating a professional PDF with Node.js PDFKit (server-side) and jsPDF (browser-side), handling Hindi/English multi-language content, and delivering the report by email.
What You Will Build: A Node.js endpoint that accepts birth details, fetches birth chart, Dasha, and AI narrative from Vedika API in parallel, generates a multi-section Kundali PDF with PDFKit, and returns the PDF buffer — ready to download or email.
Report Sections Overview
A professional Kundali report contains these sections in order:
- Cover page — Name, date of birth, time, place, Lagna (ascendant), Rashi (moon sign)
- Planetary positions table — All 9 grahas: sign, house, degree, dignity, status (direct/retrograde/combust)
- House lordships — 12 houses with ruling planet and that planet's current position
- Vimshottari Dasha timeline — Current Mahadasha and Antardasha with dates; next 5 Mahadashas
- Active Yogas — List of all classical Yogas present in the chart with brief meaning
- Dosha analysis — Mangal Dosha, Kaal Sarp Dosha (if present), with remedies
- AI narrative interpretation — Vedika AI's personalised 400–600 word reading
- Gemstone and remedy recommendations — Based on weak planets (Shadbala analysis)
Step 1: Fetch All Data in Parallel
const VEDIKA_API = 'https://api.vedika.io';
const API_KEY = 'vk_live_YOUR_KEY_HERE';
const apiPost = async (path, body) => {
const res = await fetch(`${VEDIKA_API}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`${path} failed: ${res.status}`);
return res.json();
};
async function fetchKundaliData(birthDetails, language = 'en') {
const { datetime, latitude, longitude, timezone, name } = birthDetails;
const basePayload = { datetime, latitude, longitude, timezone };
const [birthChart, dasha, aiReading] = await Promise.all([
apiPost('/v2/astrology/birth-chart', basePayload),
apiPost('/v2/astrology/vimshottari-dasha', basePayload),
apiPost('/api/vedika/chat', {
question: language === 'hi'
? 'मेरे जन्म कुंडली का विस्तृत विश्लेषण करें। ग्रह स्थिति, योग, और दशा के बारे में बताएं।'
: 'Give a comprehensive analysis of my birth chart — planetary positions, active yogas, doshas, and what this chart means for career, relationships, and overall life trajectory.',
birthDetails: basePayload
})
]);
return {
name,
birthDetails,
planets: birthChart.data.planets,
houses: birthChart.data.houses,
ascendant: birthChart.data.ascendant,
activeYogas: birthChart.data.yogas || [],
doshas: birthChart.data.doshas || {},
currentDasha: dasha.data.currentDasha,
dashaTimeline: dasha.data.timeline,
aiNarrative: aiReading.answer,
remedies: birthChart.data.remedies || []
};
}
Step 2: Generate PDF with Node.js PDFKit
Install PDFKit:
npm install pdfkit
Complete PDF Generator
const PDFDocument = require('pdfkit');
const PLANETS_ORDER = [
'Sun', 'Moon', 'Mars', 'Mercury', 'Jupiter',
'Venus', 'Saturn', 'Rahu', 'Ketu'
];
async function generateKundaliPDF(kundaliData, language = 'en') {
return new Promise((resolve, reject) => {
const doc = new PDFDocument({
size: 'A4',
margins: { top: 50, bottom: 50, left: 60, right: 60 },
info: {
Title: `Kundali Report — ${kundaliData.name}`,
Author: 'Vedika Intelligence',
Subject: 'Vedic Birth Chart Analysis'
}
});
const buffers = [];
doc.on('data', chunk => buffers.push(chunk));
doc.on('end', () => resolve(Buffer.concat(buffers)));
doc.on('error', reject);
if (language === 'hi') {
doc.registerFont('Devanagari', './fonts/NotoSansDevanagari-Regular.ttf');
doc.registerFont('Devanagari-Bold', './fonts/NotoSansDevanagari-Bold.ttf');
}
const regularFont = language === 'hi' ? 'Devanagari' : 'Helvetica';
const boldFont = language === 'hi' ? 'Devanagari-Bold' : 'Helvetica-Bold';
doc.rect(0, 0, doc.page.width, 180).fill('#7c3aed');
doc.fillColor('white')
.font(boldFont).fontSize(28)
.text(language === 'hi' ? 'जन्म कुंडली' : 'Kundali Report', 60, 60);
doc.fontSize(16).text(kundaliData.name, 60, 100);
doc.fontSize(11).text('Powered by Vedika Intelligence · vedika.io', 60, 130);
doc.fillColor('#1a1a2e')
.font(boldFont).fontSize(13)
.text(language === 'hi' ? 'जन्म विवरण' : 'Birth Details', 60, 200);
const { datetime, latitude, longitude } = kundaliData.birthDetails;
doc.font(regularFont).fontSize(11).fillColor('#333')
.text(`Date & Time: ${new Date(datetime).toLocaleString('en-IN')}`, 60, 220)
.text(`Location: ${latitude.toFixed(4)}°N, ${longitude.toFixed(4)}°E`, 60, 235)
.text(`Ascendant: ${kundaliData.ascendant.sign} ${kundaliData.ascendant.degree.toFixed(2)}°`, 60, 250);
doc.addPage();
drawSectionHeader(doc, boldFont, language === 'hi' ? 'ग्रह स्थिति' : 'Planetary Positions');
const tableTop = 100;
const colWidths = [80, 90, 45, 50, 80, 70];
const colLabels = ['Planet', 'Sign', 'House', 'Degree', 'Dignity', 'Status'];
drawTableHeader(doc, boldFont, tableTop, colWidths, colLabels);
let rowY = tableTop + 25;
PLANETS_ORDER.forEach((planet, i) => {
const p = kundaliData.planets[planet.toLowerCase()];
if (!p) return;
const rowBg = i % 2 === 0 ? '#f8f9fa' : 'white';
doc.rect(60, rowY - 3, 475, 20).fill(rowBg);
doc.fillColor('#333').font(regularFont).fontSize(10);
const rowData = [
planet,
p.sign,
p.house.toString(),
p.degree.toFixed(2) + '°',
p.dignity || 'Neutral',
p.isRetrograde ? 'Retrograde' : p.isCombust ? 'Combust' : 'Direct'
];
let x = 60;
rowData.forEach((cell, ci) => {
doc.text(cell, x + 4, rowY, { width: colWidths[ci] - 8 });
x += colWidths[ci];
});
rowY += 20;
});
doc.addPage();
drawSectionHeader(doc, boldFont, language === 'hi' ? 'विंशोत्तरी दशा' : 'Vimshottari Dasha Timeline');
const { mahadasha, antardasha } = kundaliData.currentDasha;
doc.font(boldFont).fontSize(12).fillColor('#7c3aed')
.text(`Current Mahadasha: ${mahadasha.planet} (until ${mahadasha.endDate})`, 60, 100);
doc.font(regularFont).fillColor('#555')
.text(`Current Antardasha: ${antardasha.planet} (until ${antardasha.endDate})`, 60, 118);
let dashaY = 145;
kundaliData.dashaTimeline.slice(0, 8).forEach(d => {
const isCurrent = d.planet === mahadasha.planet;
doc.rect(60, dashaY, 475, 22).fill(isCurrent ? '#ede9fe' : '#f8f9fa');
doc.fillColor(isCurrent ? '#7c3aed' : '#333')
.font(isCurrent ? boldFont : regularFont).fontSize(10)
.text(`${d.planet} Mahadasha`, 65, dashaY + 6, { width: 150 })
.text(`${d.startDate} → ${d.endDate}`, 220, dashaY + 6)
.text(`${d.years} years`, 430, dashaY + 6);
dashaY += 26;
});
if (kundaliData.activeYogas.length > 0) {
doc.addPage();
drawSectionHeader(doc, boldFont, language === 'hi' ? 'सक्रिय योग' : 'Active Yogas');
let yogaY = 100;
kundaliData.activeYogas.forEach(yoga => {
doc.font(boldFont).fontSize(11).fillColor('#1a1a2e')
.text(yoga.name, 60, yogaY);
doc.font(regularFont).fontSize(10).fillColor('#555')
.text(yoga.description || yoga.meaning || '', 60, yogaY + 14, { width: 475 });
yogaY += 45;
if (yogaY > 720) { doc.addPage(); yogaY = 60; }
});
}
doc.addPage();
drawSectionHeader(doc, boldFont, language === 'hi' ? 'विस्तृत विश्लेषण' : 'Detailed Analysis');
doc.font(regularFont).fontSize(11).fillColor('#333')
.text(kundaliData.aiNarrative, 60, 100, {
width: 475, align: 'justify', lineGap: 4
});
const pageCount = doc.bufferedPageRange().count;
for (let i = 0; i < pageCount; i++) {
doc.switchToPage(i);
doc.fontSize(8).fillColor('#aaa')
.text(`Page ${i+1} of ${pageCount} · Generated by Vedika Intelligence · vedika.io`,
60, doc.page.height - 40, { align: 'center', width: 475 });
}
doc.end();
});
}
function drawSectionHeader(doc, boldFont, title) {
doc.rect(60, 65, 475, 3).fill('#7c3aed');
doc.font(boldFont).fontSize(16).fillColor('#1a1a2e').text(title, 60, 75);
}
function drawTableHeader(doc, boldFont, y, colWidths, labels) {
doc.rect(60, y - 5, 475, 22).fill('#7c3aed');
doc.font(boldFont).fontSize(10).fillColor('white');
let x = 60;
labels.forEach((label, i) => {
doc.text(label, x + 4, y, { width: colWidths[i] - 8 });
x += colWidths[i];
});
}
Step 3: Express API Endpoint
const express = require('express');
const router = express.Router();
router.post('/generate-kundali-pdf', async (req, res) => {
try {
const { name, datetime, latitude, longitude, timezone, language = 'en' } = req.body;
if (!name || !datetime || !latitude || !longitude) {
return res.status(400).json({ error: 'Missing required fields' });
}
const kundaliData = await fetchKundaliData(
{ name, datetime, latitude, longitude, timezone: timezone || '+05:30' },
language
);
const pdfBuffer = await generateKundaliPDF(kundaliData, language);
const filename = `kundali-${name.replace(/\s+/g, '-').toLowerCase()}.pdf`;
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send(pdfBuffer);
} catch (err) {
console.error('PDF generation error:', err);
res.status(500).json({ error: 'Failed to generate Kundali PDF' });
}
});
module.exports = router;
Step 4: Browser-Side Generation with jsPDF
For a "Download PDF" button in a web app without a server round-trip, use jsPDF with the autotable plugin:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js"></script>
async function downloadKundaliPDF(birthDetails) {
const data = await fetch('/api/kundali-data', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(birthDetails)
}).then(r => r.json());
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
doc.setFillColor(124, 58, 237);
doc.rect(0, 0, 210, 60, 'F');
doc.setTextColor(255, 255, 255);
doc.setFontSize(24);
doc.setFont('helvetica', 'bold');
doc.text('Kundali Report', 20, 30);
doc.setFontSize(14);
doc.text(data.name, 20, 45);
doc.setTextColor(0);
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.text('Planetary Positions', 20, 80);
const planetRows = Object.entries(data.planets).map(([planet, p]) => [
planet.charAt(0).toUpperCase() + planet.slice(1),
p.sign, p.house, p.degree.toFixed(2) + '°',
p.dignity || 'Neutral',
p.isRetrograde ? 'Rx' : p.isCombust ? 'Combust' : 'Direct'
]);
doc.autoTable({
head: [['Planet', 'Sign', 'House', 'Degree', 'Dignity', 'Status']],
body: planetRows,
startY: 85,
headStyles: { fillColor: [124, 58, 237], textColor: 255 },
alternateRowStyles: { fillColor: [248, 249, 250] },
styles: { fontSize: 10, cellPadding: 3 }
});
doc.addPage();
doc.setFontSize(14);
doc.setFont('helvetica', 'bold');
doc.text('Chart Analysis', 20, 20);
doc.setFont('helvetica', 'normal');
doc.setFontSize(10);
const narrativeLines = doc.splitTextToSize(data.aiNarrative, 170);
doc.text(narrativeLines, 20, 30);
doc.save(`kundali-${data.name.replace(/\s+/g, '-').toLowerCase()}.pdf`);
}
Step 5: Email Delivery with Nodemailer
const nodemailer = require('nodemailer');
async function emailKundaliReport(toEmail, name, pdfBuffer) {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587,
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS
}
});
await transporter.sendMail({
from: '"Vedika Astrology" <reports@yourapp.com>',
to: toEmail,
subject: `Your Kundali Report — ${name}`,
html: `
<p>Dear ${name},</p>
<p>Your personalised Kundali report is attached. It includes:</p>
<ul>
<li>Complete planetary positions with dignity and status</li>
<li>Vimshottari Dasha timeline</li>
<li>Active Yogas in your chart</li>
<li>AI-powered personalised interpretation</li>
</ul>
<p>Powered by Vedika Intelligence.</p>
`,
attachments: [{
filename: `kundali-${name.replace(/\s+/g, '-').toLowerCase()}.pdf`,
content: pdfBuffer,
contentType: 'application/pdf'
}]
});
}
async function generateAndEmailReport(req, res) {
const { name, email, datetime, latitude, longitude, timezone, language } = req.body;
const kundaliData = await fetchKundaliData({ name, datetime, latitude, longitude, timezone }, language);
const pdfBuffer = await generateKundaliPDF(kundaliData, language);
await emailKundaliReport(email, name, pdfBuffer);
res.json({ success: true, message: `Report sent to ${email}` });
}
Build Your Kundali Report Generator
Vedika API's structured JSON output maps directly to PDF layouts. Get your API key and start building today.
Get API Key
Advanced: Multi-Language Hindi Reports
For Hindi PDF reports, two things must align: the font and the API response language. Vedika AI returns responses in Hindi when you pass a Hindi question or explicitly request Hindi:
const PLANET_NAMES_HI = {
sun: 'सूर्य', moon: 'चन्द्र', mars: 'मंगल',
mercury: 'बुध', jupiter: 'गुरु', venus: 'शुक्र',
saturn: 'शनि', rahu: 'राहु', ketu: 'केतु'
};
const SIGN_NAMES_HI = [
'मेष', 'वृष', 'मिथुन', 'कर्क', 'सिंह', 'कन्या',
'तुला', 'वृश्चिक', 'धनु', 'मकर', 'कुम्भ', 'मीन'
];
Why Vedika API for Kundali Reports
Structured JSON Output
Every endpoint returns clean, typed JSON. Planets come with sign, house, degree, dignity, retrograde, and combust flags — exactly what PDF table layouts need.
AI Narrative in One Call
No need to write interpretation logic. The AI endpoint returns a 400–800 word personalised reading per chart that drops directly into the PDF narrative section.
30 Languages
Hindi, Tamil, Telugu, Bengali, Gujarati, Marathi, Kannada reports from the same API — just change the language parameter in your question.
Swiss Ephemeris Precision
Planetary positions used in the report are calculated using astronomical-grade Swiss Ephemeris. Professional astrologers will not find errors.
140+ Endpoints
Add Ashtakavarga tables, Divisional Charts (D9, D10), Shadbala scores, Panchang, or Muhurta sections to your PDF without changing providers.
$12/month
Starter plan covers all endpoints. A complete 8-section Kundali report costs a tiny fraction of a cent in API credits — the cost is in your subscription, not per call.
Conclusion
A professional Kundali PDF report is a high-perceived-value feature that differentiates your app from free web calculators. With Vedika API returning structured birth chart data, Dasha timelines, and AI narrative in parallel calls, and Node.js PDFKit handling the document generation, you can build a complete Kundali report pipeline in a single afternoon.
The same architecture extends to yearly predictions, compatibility reports, Muhurta selection reports, and Navamsa chart analysis — all with the same Vedika API subscription and the same PDF template.
Next steps:
- Get your API key at vedika.io/dashboard
- Test birth chart data in the free sandbox
- Read the Birth Chart API reference
- Add Dasha endpoint for the timeline section
- Use the Ashtakavarga endpoint for a premium report tier
About Vedika Intelligence: Vedika is the only B2B astrology API with AI-powered conversational capabilities, 140+ Vedic and Western calculation endpoints, and Swiss Ephemeris precision. Starting at $12/month, supporting 30 languages, used by production apps worldwide.