Combine Monthly Reports into Quarterly Summaries with Go
Every quarter, someone in finance has to manually combine January, February, and March reports into a single Q1 document. They open each PDF, copy pages, paste into a new file, fix the formatting, and repeat. It's tedious and error-prone.
If your reports come from different systems or teams, they might have inconsistent formatting. Merging them manually means dealing with different page sizes, orientations, and margins.
The aPDF.io Merge API handles all of this automatically. Send a list of PDFs, get back a single combined document. You can even specify which pages to include from each file.
Quick Example
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
apiToken := "YOUR_API_TOKEN"
apiURL := "https://apdf.io/api/pdf/file/merge"
payload := map[string]interface{}{
"files": []map[string]string{
{"file": "https://storage.example.com/reports/january-2024.pdf"},
{"file": "https://storage.example.com/reports/february-2024.pdf"},
{"file": "https://storage.example.com/reports/march-2024.pdf"},
},
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", apiURL, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("Q1 Report: %s\n", result["file"])
fmt.Printf("Total pages: %.0f\n", result["pages"])
}
Understanding the Response
The API returns the merged file URL and metadata:
{
"file": "https://apdf-files.s3.eu-central-1.amazonaws.com/a736755a3dab32b8.pdf",
"expiration": "2024-12-08T14:49:16.997135Z",
"pages": 45,
"size": 2893102
}
The merged PDF preserves the original formatting of each source document. Different page sizes and orientations are maintained.
Real-World Scenario: Automated Quarterly Report Generator
You're building a financial reporting system. At the end of each quarter, it should automatically compile monthly reports, exclude cover pages, and add a table of contents.
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const (
apiToken = "YOUR_API_TOKEN"
mergeURL = "https://apdf.io/api/pdf/file/merge"
createURL = "https://apdf.io/api/pdf/file/create"
)
type MergeFile struct {
File string `json:"file"`
Pages string `json:"pages,omitempty"`
}
type MergeRequest struct {
Files []MergeFile `json:"files"`
}
type MergeResponse struct {
File string `json:"file"`
Expiration string `json:"expiration"`
Pages int `json:"pages"`
Size int `json:"size"`
}
func mergePDFs(files []MergeFile) (*MergeResponse, error) {
payload := MergeRequest{Files: files}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", mergeURL, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{Timeout: 60 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result MergeResponse
json.NewDecoder(resp.Body).Decode(&result)
return &result, nil
}
func createCoverPage(title string, period string) (string, error) {
html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background: linear-gradient(135deg, #1a1a2e 0%%, #16213e 100%%);
color: white;
}
h1 { font-size: 48px; margin-bottom: 20px; }
.period { font-size: 24px; color: #aaa; }
.date { margin-top: 40px; font-size: 14px; color: #666; }
</style>
</head>
<body>
<h1>%s</h1>
<div class="period">%s</div>
<div class="date">Generated: %s</div>
</body>
</html>
`, title, period, time.Now().Format("January 2, 2006"))
payload := map[string]interface{}{
"html": html,
"format": "a4",
}
jsonData, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", createURL, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
return result["file"].(string), nil
}
func generateQuarterlyReport(quarter int, year int, monthlyReports []string) {
fmt.Printf("Generating Q%d %d Report...\n", quarter, year)
// Step 1: Create cover page
fmt.Println("Creating cover page...")
coverURL, err := createCoverPage(
"Financial Report",
fmt.Sprintf("Q%d %d", quarter, year),
)
if err != nil {
fmt.Printf("Error creating cover: %v\n", err)
return
}
// Step 2: Prepare files for merge
// Skip cover pages (page 1) from each monthly report
files := []MergeFile{
{File: coverURL}, // Our new cover page
}
for _, report := range monthlyReports {
files = append(files, MergeFile{
File: report,
Pages: "2-z", // Skip original cover page, include rest
})
}
// Step 3: Merge everything
fmt.Printf("Merging %d documents...\n", len(files))
result, err := mergePDFs(files)
if err != nil {
fmt.Printf("Error merging: %v\n", err)
return
}
fmt.Println("\n=== Quarterly Report Ready ===")
fmt.Printf("URL: %s\n", result.File)
fmt.Printf("Pages: %d\n", result.Pages)
fmt.Printf("Size: %.2f MB\n", float64(result.Size)/1024/1024)
}
func main() {
// Monthly reports for Q1 2024
monthlyReports := []string{
"https://storage.example.com/reports/2024-01-financial.pdf",
"https://storage.example.com/reports/2024-02-financial.pdf",
"https://storage.example.com/reports/2024-03-financial.pdf",
}
generateQuarterlyReport(1, 2024, monthlyReports)
}
Selecting Specific Pages
The merge API supports powerful page selection. You can include only the pages you need:
// Include all pages from first file
files := []MergeFile{
{File: "https://example.com/report1.pdf"},
}
// Include only pages 2-5 from second file
files = append(files, MergeFile{
File: "https://example.com/report2.pdf",
Pages: "2-5",
})
// Include specific pages from third file
files = append(files, MergeFile{
File: "https://example.com/report3.pdf",
Pages: "1,3,5-10",
})
// Include from page 2 to the end (skip cover)
files = append(files, MergeFile{
File: "https://example.com/report4.pdf",
Pages: "2-z",
})
// Include last 3 pages only
files = append(files, MergeFile{
File: "https://example.com/report5.pdf",
Pages: "r3-r1",
})
Batch Processing: Annual Report Compilation
Compile all quarterly reports into an annual summary:
func generateAnnualReport(year int, quarterlyReports []string) {
fmt.Printf("Generating %d Annual Report...\n", year)
// Create annual cover
coverURL, _ := createCoverPage(
"Annual Financial Report",
fmt.Sprintf("Fiscal Year %d", year),
)
// Build file list with section dividers
files := []MergeFile{{File: coverURL}}
quarters := []string{"Q1", "Q2", "Q3", "Q4"}
for i, report := range quarterlyReports {
// Create quarter divider page
dividerHTML := fmt.Sprintf(`
<html>
<body style="display:flex;justify-content:center;align-items:center;
height:100vh;font-family:Arial;background:#f0f0f0;">
<h1 style="font-size:72px;color:#333;">%s</h1>
</body>
</html>
`, quarters[i])
dividerPayload := map[string]interface{}{"html": dividerHTML, "format": "a4"}
jsonData, _ := json.Marshal(dividerPayload)
req, _ := http.NewRequest("POST", createURL, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+apiToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
client := &http.Client{}
resp, _ := client.Do(req)
var dividerResult map[string]interface{}
json.NewDecoder(resp.Body).Decode(÷rResult)
resp.Body.Close()
// Add divider then quarterly report
files = append(files, MergeFile{File: dividerResult["file"].(string)})
files = append(files, MergeFile{File: report, Pages: "2-z"}) // Skip Q covers
}
// Merge everything
result, _ := mergePDFs(files)
fmt.Printf("Annual Report: %s\n", result.File)
fmt.Printf("Total pages: %d\n", result.Pages)
}
Next Steps
Once you're merging reports, consider these enhancements:
- Add page numbers: Use the Overlay endpoint to add consistent page numbers across the merged document.
- Compress the result: Use the Compress endpoint to reduce file size before distribution.