> ## Documentation Index
> Fetch the complete documentation index at: https://markdown2pdf.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Easily use markdown2pdf.ai to convert markdown (.md) to high-quality and impactful PDF in your Python-based agentic workflows using our Python software development kit (SDK). 

# Getting started

<Icon icon="github" iconType="solid" size={20} /> [github.com/Serendipity-AI/markdown2pdf-python](https://github.com/Serendipity-AI/markdown2pdf-python)

Our [Python SDK](https://github.com/Serendipity-AI/markdown2pdf-python) makes it easy to use markdown2pdf.ai in your agentic workflows. Whilst using our REST APIs is straightforward, there are some complexities to handle around polling for payment completion and document generation that the SDK avoids you needing to worry about.

First up, you'll need to install the SDK:

```bash theme={null}
pip install markdown2pdf-python
```

With that done, you can try some sample code:

```python theme={null}
from markdown2pdf import MarkdownPDF

def pay(offer):
    print("⚡ Lightning payment required")
    print(f"Amount: {offer['amount']} {offer['currency']}")
    print(f"Description: {offer['description']}")
    print(f"Invoice: {offer['payment_request']}")
    input("Press Enter once paid...")

client = MarkdownPDF(on_payment_request=pay)
path = client.convert(markdown="# Insert your own markdown content here", title="My document title", download_path="output.pdf")
print("Saved PDF to:", path)
```

This code will convert the markdown content you provide into a PDF, prompting you to pay via Lightning Network if necessary. The `on_payment_request` function is called when payment is required, allowing you to handle the payment process in your application. There are several ways of handling payments automatically, such as [fewsats.com](https://fewsats.com) or [albyhub](https://albyhub.com) or using a Lightning wallet that supports automatic payments. In the example above, we simply print the payment details and wait for you to confirm payment manually. You can simply copy and paste the lightning invoice into your lightning wallet to pay.

# SDK Reference

### `class MarkdownPDF`

The main class for interacting with the markdown2pdf.ai service.

#### `constructor`

Used for initializing the MarkdownPDF client.

<ParamField body="on_payment_request" type="function" required>
  This function is called when a payment is required. It receives an `offer` object containing details about the payment, such as amount, currency, description, and the Lightning invoice. You can implement your own logic to handle payments here. This parameter is required because the service uses the L402 protocol, which requires a payment to be made before generating the PDF.

  ```python theme={null}
  def pay(offer):
      print("⚡ Lightning payment required")
      print(f"Amount: {offer['amount']} {offer['currency']}")
      print(f"Description: {offer['description']}")
      print(f"Invoice: {offer['payment_request']}")
      input("Press Enter once paid...")

  client = MarkdownPDF(on_payment_request=pay)
  ```

  ```json theme={null}
  offer = {
      "offer_id": "abc",
      "amount": 1,
      "currency": "usd",
      "description": "test payment",
      "payment_context_token": "def",
      "payment_request_url": "http://path.to/payment/request",
  }
  ```
</ParamField>

<ParamField body="api_url" type="string">
  This parameter enables you to override the default API URL. This is typically used in development or testing environments, but for normal usage you won't need to touch it and it can be set to `None` or omitted.
</ParamField>

#### `convert`

Converts the provided markdown content into a PDF document. This method handles the payment process automatically if required, and saves a PDF to the specified path or returns it as bytes.

<ParamField body="markdown" type="string" required>
  A string containing the markdown content you want to convert to PDF.

  <Tip>
    A note on markdown hashing

    markdown2pdf.ai uses a hashing mechanism to ensure that you won't get charge twice for creating the same PDF. Subsequent requests to convert the same markdown content will return the previously generated PDF without requiring a new payment.
  </Tip>

  Typically your markdown will come from the output of an LLM or AI agent. If you want to load some from a file, you can do it like this:

  ```python theme={null}
  from markdown2pdf import MarkdownPDF
  from pathlib import Path

  def pay(offer):
      print("⚡ Lightning payment required")
      print(f"Amount: {offer['amount']} {offer['currency']}")
      print(f"Description: {offer['description']}")
      print(f"Invoice: {offer['payment_request']}")
      input("Press Enter once paid...")

  client = MarkdownPDF(api_url="https://qa.api.markdown2pdf.ai", on_payment_request=pay)
  markdown = Path("examples/md/Song of the silent stars.md").read_text(encoding="utf-8")
  url = client.convert(markdown=markdown, title="Song of the silent stars", date="5th June 2025" ) 
  print("PDF URL:", url)
  ```
</ParamField>

<ParamField body="date" type="string">
  A string containing the date to use on the front cover. If not provided, the current date will be used.
</ParamField>

<ParamField body="title" type="string">
  A string containing the title to use on the front cover. If not provided, a default will be used.
</ParamField>

<ParamField body="download_path" type="string">
  A string containing the path to use for saving the PDF file. If not provided, the PDF will be returned as bytes.

  For example:

  ```python theme={null}
  path = client.convert(markdown="# Save this one", download_path="output.pdf") 
  print("Saved PDF to:", path)
  ```
</ParamField>

<ParamField body="return_bytes" type="boolean">
  Whether or not to return the PDF as bytes instead of saving it to a file. If set to `True`, the method will return the PDF content as a byte string. If set to `False`, it will save the PDF to the specified `download_path` or return the path if no path is provided.

  For example:

  ```python theme={null}
  pdf_bytes = client.convert(markdown="# Memory use case", return_bytes=True) 
  print(f"PDF size in memory: {len(pdf_bytes)} bytes")
  ```
</ParamField>

# Automating payments

If you want to automate payments, you can use a service like [fewsats.com](https://fewsats.com) or [Albyhub](https://albyhub.com) for Lightning, or any Solana wallet that holds USDC for X402. These services let you pay programmatically, completely headlessly.

<Tabs>
  <Tab title="Albyhub">
    ```python theme={null}
    from markdown2pdf import MarkdownPDF
    from dotenv import load_dotenv
    from pyalby import Account, Invoice, Payment

    load_dotenv()
    # In your .env file, ensure you have set the following. You can learn more about alby at https://albyhub.com.
    # BASE_URL = https://api.getalby.com
    # ALBY_ACCESS_TOKEN = <your_alby_access_token>
    # LOG_LEVEL = INF

    def pay(offer):
        print("Paying using Alby:")
        payment = Payment()
        pay = payment.bolt11_payment(offer["payment_request"])
        print(f"Payment made: {pay}")

    client = MarkdownPDF(on_payment_request=pay)
    path = client.convert(markdown="# Save this one using Alby", download_path="output.pdf") # Replace with your own unique markdown content to ensure you trigger L402.
    print("Saved PDF to:", path)
    ```
  </Tab>

  <Tab title="lnbits">
    ```python theme={null}
    from markdown2pdf import MarkdownPDF
    import requests

    LN_BITS_URL = "https://demo.lnbits.com/api/v1/payments"
    ADMIN_KEY = "<your_lnbits_admin_key>"  # Replace with your LNbits admin key, see https://lnbits.com

    def pay(offer):
        print("Paying using lnbits:")
        headers = {"X-Api-Key": ADMIN_KEY, "Content-Type": "application/json"}
        data = {
            "out": True,
            "bolt11": offer["payment_request"],
        }

        res = requests.post(LN_BITS_URL, json=data, headers=headers)
        res.raise_for_status()
        print("Payment status:", res)

    client = MarkdownPDF(on_payment_request=pay)
    path = client.convert(markdown="# Save this one using lnbits", download_path="output.pdf") # Replace with your own unique markdown content to ensure you trigger L402.
    print("Saved PDF to:", path)
    ```
  </Tab>

  <Tab title="Fewsats">
    ```python theme={null}
    from markdown2pdf import MarkdownPDF
    from dotenv import load_dotenv
    from fewsats.core import *
    load_dotenv()
    fs = Fewsats()
    # In your .env file, ensure you have set the following. You can learn more about fewsats at https://fewsats.com.
    # FEWSATS_API_KEY = <your_fewsats_api_key>

    def pay(offer):
        print("Paying using Fewsats:")
        r = fs.pay_lightning(invoice=offer["payment_request"], amount=offer["amount"], currency=offer["currency"], description=offer["description"])
        print(f"Payment made: {r}")

    client = MarkdownPDF(on_payment_request=pay)
    path = client.convert(markdown="# Save this one using Fewsats", download_path="output.pdf") # Replace with your own unique markdown content to ensure you trigger L402.
    print("Saved PDF to:", path)

    ```
  </Tab>

  <Tab title="Coinbase X402">
    X402 uses a different flow to L402 — instead of a callback that receives an invoice,
    the signer constructs a base64 `X-PAYMENT` header that is attached to the request
    automatically. The simplest integration is to bypass the L402 `on_payment_request`
    path entirely and talk to the REST API with an x402-aware HTTP client:

    ```python theme={null}
    import os
    import asyncio
    from solders.keypair import Keypair
    from x402 import x402Client, prefer_network
    from x402.http.clients.httpx import x402HttpxClient
    from x402.mechanisms.svm import KeypairSigner
    from x402.mechanisms.svm.exact import ExactSvmScheme

    async def main():
        # 1. Initialize signer from base58 private key
        keypair = Keypair.from_base58_string(os.environ["SOLANA_SIGNER_KEY"])
        signer = KeypairSigner(keypair)

        # 2. Configure x402 client for Solana Mainnet
        NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
        x402 = x402Client()
        x402.register(NETWORK, ExactSvmScheme(signer=signer))
        x402.register_policy(prefer_network(NETWORK))

        # 3. The async client handles the 402 → sign → retry loop
        async with x402HttpxClient(x402, timeout=120.0) as client:
            resp = await client.post(
                "https://api.markdown2pdf.ai/markdown",
                json={
                    "data": {"text_body": "# Save this one using X402"},
                    "options": {"document_name": "output.pdf"},
                },
            )
            resp.raise_for_status()
            print("Paid tx proof:", resp.headers.get("payment-response"))
            print("Poll:", resp.json()["path"])

    if __name__ == "__main__":
        asyncio.run(main())
    ```

    A future release of the SDK will expose a first-class `on_x402_payment_required`
    callback so you can pick the rail from a single `MarkdownPDF` constructor. See the
    [X402 page](/x402) for details.
  </Tab>
</Tabs>

# Using from your AI Agents

In our Python SDK we provide examples of how to use the markdown2pdf.ai service from your AI agents using the [langchain](https://langchain.com) framework.
