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

# Overview

> Using our REST API directly to convert markdown to PDF

Rather than using the SDKs we provide, you can also use the REST API directly using regular HTTP requests. This is useful if you want to integrate the markdown to PDF conversion into your own applications or scripts without relying on a specific programming language SDK.

The overall process is similar to using the SDKs, but you will need to handle the HTTP requests and responses yourself. The `POST /markdown` endpoint accepts both [L402](/l402) (Lightning) and [X402](/x402) (USDC on Solana) payments — the 402 response advertises both offers and the client picks. Below is a step-by-step guide on how to use the REST API directly:

<Steps>
  <Step title="/markdown endpoint">
    Call the [`markdown`](/api-reference/generate-a-pdf-document-from-markdown-input) endpoint with your markdown content. If a payment is required, you receive a `402` whose body contains an `offers[]` array. Expect **two** entries: one with `payment_methods: ["lightning"]` (L402) and one with `payment_methods: ["x402"]`. The `X-Accept-Payment` response header advertises the same list. Pick whichever rail your agent has a wallet for.
  </Step>

  <Step title="Pay — L402 (Lightning)">
    For the L402 offer, call [`/payment_request`](/api-reference/get-payment-details-for-generation) with the `payment_context_token` and `offer_id` to fetch a BOLT11 Lightning invoice, pay it, then re-submit the same `POST /markdown` body.
  </Step>

  <Step title="Pay — X402 (USDC on Solana)">
    For the X402 offer, read the native `accepts[]` payload from `offers[].metadata.x402` (or call `/payment_request/x402`). Sign an `X-PAYMENT` base64 header using any x402-aware signer, then re-POST `/markdown` with that header. The server verifies + settles via the facilitator and returns `200` with `X-Payment-Response` carrying the settlement proof.
  </Step>

  <Step title="/job/{job_id}/status endpoint">
    Call the [`status`](/api-reference/poll-document-generation-job-status) endpoint repeatedly to check the status of your document generation job. It will return the current status and, once complete, provide a URL to download the generated PDF.
  </Step>

  <Step title="/job/{job_id}/output endpoint">
    Call the [`output`](/api-reference/get-document-output) endpoint to retrieve the URL from which you can download the generated PDF once the job is complete.
  </Step>
</Steps>

Some sample python code is provided below to help you get started with the REST API directly. The example picks the L402 (Lightning) offer; see the [X402 page](/x402) for an x402-native variant.

```python theme={null}
import httpx
import time
from datetime import datetime
from urllib.parse import urljoin

DEFAULT_API_URL = "https://api.markdown2pdf.ai"
POLL_INTERVAL = 3
MAX_DOC_GENERATION_POLLS = 10

def build_url(path, base_url):
    if path.startswith("http://") or path.startswith("https://"):
        return path
    return urljoin(base_url, 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...")

def convert(markdown, title="Markdown2PDF.ai converted document", date=None, download_path=None, return_bytes=False, on_payment_request=pay, api_url=DEFAULT_API_URL):
    if not date:
        dt = datetime.now()
        date = f"{dt.day} {dt.strftime('%B %Y')}"

    payload = {
        "data": {
            "text_body": markdown,
            "meta": {
                "title": title,
                "date": date,
            }
        },
        "options": {
            "document_name": "converted.pdf"
        }
    }

    with httpx.Client() as client:
        while True:
            print("Sending initial request to convert markdown...")
            response = client.post(f"{api_url}/markdown", json=payload)

            if response.status_code == 402:
                print("Received 402 Payment Required response, fetching payment offer...")
                l402_offer = response.json()
                # Select the Lightning offer explicitly. The 402 body may also list an
                # x402 offer (payment_methods=["x402"]) — see /x402 for that flow.
                offer_data = next(
                    o for o in l402_offer["offers"] if "lightning" in o["payment_methods"]
                )
                offer = {
                    "offer_id": offer_data["id"],
                    "amount": offer_data["amount"],
                    "currency": offer_data["currency"],
                    "description": offer_data.get("description", ""),
                    "payment_context_token": l402_offer["payment_context_token"],
                    "payment_request_url": l402_offer["payment_request_url"]
                }

                invoice_resp = client.post(offer["payment_request_url"], json={
                    "offer_id": offer["offer_id"],
                    "payment_context_token": offer["payment_context_token"],
                    "payment_method": "lightning"
                })

                if not invoice_resp.is_success:
                    raise Exception(f"Failed to fetch invoice: {invoice_resp.status_code}")

                invoice_data = invoice_resp.json()
                offer["payment_request"] = invoice_data["payment_request"]["payment_request"]

                if not on_payment_request:
                    raise Exception("Payment required but no handler provided.")

                print("Prompting for payment...")
                on_payment_request(offer)

                time.sleep(POLL_INTERVAL)
                continue

            if not response.is_success:
                raise Exception(f"Initial request failed: {response.status_code}, {response.text}")

            response_data = response.json()
            path = response_data["path"]
            break

        status_url = build_url(path, api_url)
        attempt = 0
        print("Polling for document generation status...")
        while attempt < MAX_DOC_GENERATION_POLLS:
            poll_resp = client.get(status_url)
            if poll_resp.status_code != 200:
                raise Exception(f"Polling error (status {poll_resp.status_code})")

            poll_data = poll_resp.json()
            if poll_data.get("status") == "Done":
                final_metadata_url = poll_data.get("path")
                if not final_metadata_url:
                    raise Exception("Missing 'path' field pointing to final metadata.")

                metadata_resp = client.get(final_metadata_url)
                if not metadata_resp.is_success:
                    raise Exception("Failed to retrieve metadata at final path.")

                final_data = metadata_resp.json()
                if "url" not in final_data:
                    raise Exception("Missing final download URL in metadata response.")

                final_download_url = final_data["url"]
                break

            time.sleep(POLL_INTERVAL)
            attempt += 1
        else:
            raise Exception(f"Polling exceeded max attempts ({MAX_DOC_GENERATION_POLLS}) without completion.")

        print("Downloading final PDF...")
        pdf_resp = client.get(final_download_url)
        if not pdf_resp.is_success:
            raise Exception("Failed to download final PDF.")

        pdf_content = pdf_resp.content

    if return_bytes:
        return pdf_content

    if download_path:
        with open(download_path, "wb") as f:
            f.write(pdf_content)
        return download_path

    return final_download_url

# Example usage
if __name__ == "__main__":
    url = convert(
        markdown="# Hello markdown2pdf REST APIs",
        title="My document title",
        date="5th June 2025"
    )
    print("PDF URL:", url)
```
