Skip to main content
GET
/
api
/
billing
/
invoices
curl -X GET https://www.wiseyield.co/api/billing/invoices \
  -H "Authorization: Bearer wy_YOUR_API_KEY"
{
  "invoices": [
    {
      "id": "inv_abc123",
      "userId": "user_xyz789",
      "paymentId": "in_1234567890",
      "customerId": "cus_9876543210",
      "subscriptionId": "sub_abc123",
      "amount": 8900,
      "currency": "eur",
      "status": "paid",
      "invoiceUrl": "https://payments.dodopayments.com/invoices/inv_...",
      "invoiceNumber": "INV-0042",
      "periodStart": "2025-11-01T00:00:00.000Z",
      "periodEnd": "2025-12-01T00:00:00.000Z",
      "paidAt": "2025-11-01T10:05:00.000Z",
      "createdAt": "2025-11-01T10:00:00.000Z"
    },
    {
      "id": "inv_def456",
      "userId": "user_xyz789",
      "paymentId": "in_0987654321",
      "customerId": "cus_9876543210",
      "subscriptionId": "sub_abc123",
      "amount": 8900,
      "currency": "eur",
      "status": "paid",
      "invoiceUrl": "https://payments.dodopayments.com/invoices/inv_...",
      "invoiceNumber": "INV-0041",
      "periodStart": "2025-10-01T00:00:00.000Z",
      "periodEnd": "2025-11-01T00:00:00.000Z",
      "paidAt": "2025-10-01T10:05:00.000Z",
      "createdAt": "2025-10-01T10:00:00.000Z"
    }
  ]
}

Documentation Index

Fetch the complete documentation index at: https://docs.wiseyield.co/llms.txt

Use this file to discover all available pages before exploring further.

Endpoint

GET https://www.wiseyield.co/api/billing/invoices

Authentication

Requires a valid API key with billing:read scope.

Response

Returns an array of invoice objects (max 10 most recent).
invoices
array
curl -X GET https://www.wiseyield.co/api/billing/invoices \
  -H "Authorization: Bearer wy_YOUR_API_KEY"
{
  "invoices": [
    {
      "id": "inv_abc123",
      "userId": "user_xyz789",
      "paymentId": "in_1234567890",
      "customerId": "cus_9876543210",
      "subscriptionId": "sub_abc123",
      "amount": 8900,
      "currency": "eur",
      "status": "paid",
      "invoiceUrl": "https://payments.dodopayments.com/invoices/inv_...",
      "invoiceNumber": "INV-0042",
      "periodStart": "2025-11-01T00:00:00.000Z",
      "periodEnd": "2025-12-01T00:00:00.000Z",
      "paidAt": "2025-11-01T10:05:00.000Z",
      "createdAt": "2025-11-01T10:00:00.000Z"
    },
    {
      "id": "inv_def456",
      "userId": "user_xyz789",
      "paymentId": "in_0987654321",
      "customerId": "cus_9876543210",
      "subscriptionId": "sub_abc123",
      "amount": 8900,
      "currency": "eur",
      "status": "paid",
      "invoiceUrl": "https://payments.dodopayments.com/invoices/inv_...",
      "invoiceNumber": "INV-0041",
      "periodStart": "2025-10-01T00:00:00.000Z",
      "periodEnd": "2025-11-01T00:00:00.000Z",
      "paidAt": "2025-10-01T10:05:00.000Z",
      "createdAt": "2025-10-01T10:00:00.000Z"
    }
  ]
}

Errors

401
error
Unauthorized - Missing or invalid API key
500
error
Internal Server Error - Failed to fetch invoices

Invoice Statuses

Limits

Response Limit: Returns maximum 10 most recent invoices, ordered by createdAt descending. Time Range: No time limit - returns all historical invoices up to the limit.

Use Cases

Display Invoice History

async function getInvoiceHistory() {
  const response = await fetch('/api/billing/invoices', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { invoices } = await response.json();

  return invoices.map(invoice => ({
    id: invoice.id,
    number: invoice.invoiceNumber,
    amount: `€${(invoice.amount / 100).toFixed(2)}`,
    status: invoice.status,
    date: new Date(invoice.createdAt).toLocaleDateString(),
    downloadUrl: invoice.invoiceUrl
  }));
}

// Usage
const history = await getInvoiceHistory();
console.table(history);

Check Last Payment

async function getLastPayment() {
  const response = await fetch('/api/billing/invoices', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { invoices } = await response.json();

  const paidInvoices = invoices.filter(inv => inv.status === 'paid');

  if (paidInvoices.length === 0) {
    return null;
  }

  const latest = paidInvoices[0]; // Already sorted by createdAt desc

  return {
    amount: latest.amount / 100,
    date: new Date(latest.paidAt),
    invoiceNumber: latest.invoiceNumber
  };
}

// Usage
const lastPayment = await getLastPayment();
if (lastPayment) {
  console.log(`Last payment: €${lastPayment.amount} on ${lastPayment.date.toLocaleDateString()}`);
}

Calculate Total Spent

async function getTotalSpent() {
  const response = await fetch('/api/billing/invoices', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { invoices } = await response.json();

  const total = invoices
    .filter(inv => inv.status === 'paid')
    .reduce((sum, inv) => sum + inv.amount, 0);

  return total / 100; // Convert cents to euros
}

// Usage
const totalSpent = await getTotalSpent();
console.log(`Total spent: €${totalSpent.toFixed(2)}`);

Check for Unpaid Invoices

async function hasUnpaidInvoices() {
  const response = await fetch('/api/billing/invoices', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { invoices } = await response.json();

  const unpaid = invoices.filter(inv => inv.status === 'open');

  return {
    hasUnpaid: unpaid.length > 0,
    count: unpaid.length,
    unpaidInvoices: unpaid
  };
}

// Usage
const { hasUnpaid, count, unpaidInvoices } = await hasUnpaidInvoices();
if (hasUnpaid) {
  console.log(`You have ${count} unpaid invoice(s)`);
}

Best Practices

Display Currency: Always format amount by dividing by 100 and showing currency symbol.
Download Links: Use invoiceUrl to allow users to download/view invoice PDFs.
Date Formatting: Format createdAt and paidAt for user’s locale.
Status Badges: Show visual indicators (colors/icons) for different invoice statuses.
10 Invoice Limit: Endpoint returns max 10 invoices. For full history, direct users to Dodo Payments billing portal.
Amount in Cents: amount field is in cents. Always divide by 100 before displaying to users.
Invoice URLs: Invoice URLs may be temporary. Don’t cache them - fetch fresh on each page load.

Integration Examples

Invoice History Table

function InvoiceHistoryTable() {
  const [invoices, setInvoices] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadInvoices() {
      try {
        const response = await fetch('/api/billing/invoices', {
          headers: { 'Authorization': `Bearer ${apiKey}` }
        });

        const { invoices } = await response.json();
        setInvoices(invoices);
      } finally {
        setLoading(false);
      }
    }

    loadInvoices();
  }, []);

  if (loading) return <div>Loading invoices...</div>;

  if (invoices.length === 0) {
    return <div>No invoices yet</div>;
  }

  return (
    <table className="invoice-table">
      <thead>
        <tr>
          <th>Invoice #</th>
          <th>Date</th>
          <th>Amount</th>
          <th>Status</th>
          <th>Download</th>
        </tr>
      </thead>
      <tbody>
        {invoices.map(invoice => (
          <tr key={invoice.id}>
            <td>{invoice.invoiceNumber}</td>
            <td>{new Date(invoice.createdAt).toLocaleDateString()}</td>
            <td>{(invoice.amount / 100).toFixed(2)}</td>
            <td>
              <StatusBadge status={invoice.status} />
            </td>
            <td>
              <a href={invoice.invoiceUrl} target="_blank" rel="noopener noreferrer">
                Download PDF
              </a>
            </td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

Invoice Status Badge

function StatusBadge({ status }) {
  const statusConfig = {
    paid: { color: 'green', text: 'Paid', icon: '✓' },
    open: { color: 'yellow', text: 'Pending', icon: '⏳' },
    void: { color: 'gray', text: 'Void', icon: '✗' },
    uncollectible: { color: 'red', text: 'Failed', icon: '!' },
  };

  const config = statusConfig[status] || statusConfig.open;

  return (
    <span className={`badge badge-${config.color}`}>
      {config.icon} {config.text}
    </span>
  );
}

Invoice Summary Card

function InvoiceSummaryCard() {
  const [summary, setSummary] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadSummary() {
      const response = await fetch('/api/billing/invoices', {
        headers: { 'Authorization': `Bearer ${apiKey}` }
      });

      const { invoices } = await response.json();

      const paidInvoices = invoices.filter(inv => inv.status === 'paid');
      const totalSpent = paidInvoices.reduce((sum, inv) => sum + inv.amount, 0) / 100;

      setSummary({
        totalInvoices: invoices.length,
        totalPaid: paidInvoices.length,
        totalSpent,
        lastPayment: paidInvoices[0] || null
      });

      setLoading(false);
    }

    loadSummary();
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div className="summary-card">
      <h3>Billing Summary</h3>

      <div className="stats">
        <div className="stat">
          <label>Total Invoices</label>
          <value>{summary.totalInvoices}</value>
        </div>

        <div className="stat">
          <label>Total Spent</label>
          <value>{summary.totalSpent.toFixed(2)}</value>
        </div>

        {summary.lastPayment && (
          <div className="stat">
            <label>Last Payment</label>
            <value>
{(summary.lastPayment.amount / 100).toFixed(2)} on{' '}
              {new Date(summary.lastPayment.paidAt).toLocaleDateString()}
            </value>
          </div>
        )}
      </div>
    </div>
  );
}

Download All Invoices

async function downloadAllInvoices() {
  const response = await fetch('/api/billing/invoices', {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  const { invoices } = await response.json();

  // Open each invoice URL in new tab
  invoices.forEach((invoice, index) => {
    setTimeout(() => {
      window.open(invoice.invoiceUrl, '_blank');
    }, index * 500); // Stagger to avoid popup blocker
  });

  toast.success(`Opening ${invoices.length} invoice(s) in new tabs`);
}