Skip to main content
Invoices are statements of amounts owed by a customer. They are generated automatically by subscriptions or created manually for one-off charges.

Invoice Lifecycle

  1. Draft: The invoice is being built. You can add items.
  2. Open: The invoice is finalized and ready for payment. An email is sent to the customer.
  3. Paid: Payment has been successfully collected.
  4. Void: The invoice was cancelled before payment.
  5. Uncollectible: Payment is unlikely to be received (bad debt).

Invoice Items

An invoice consists of line items.
  • Subscription Items: Generated automatically from the plan.
  • One-off Items: Added manually (e.g., “Setup Fee”).
  • Usage Items: Calculated from reported usage.

PDF Invoices

Credibill automatically generates PDF invoices for your customers. You can retrieve the PDF URL via the API or Webhooks to send to your customers, or let Credibill email them directly.

Getting Invoices

Retrieve invoice information by fetching a specific invoice or listing invoices with optional filters.
  • Endpoint: https://giant-goldfish-922.convex.site/api/v1/invoices
  • Method: GET
  • Authentication: Authorization: Bearer <CREDIBILL_API_KEY>

Query Parameters

ParameterTypeRequiredDescription
invoiceIdstringNoThe unique ID of the invoice. If provided, fetches a single invoice.
customerIdstringNoFilter invoices by customer ID.
subscriptionIdstringNoFilter invoices by subscription ID. When combined with status=open, returns current invoice.
statusstringNoFilter invoices by status (e.g., “draft”, “open”, “paid”, “void”, “uncollectible”).

Examples

// src/hooks/useGetInvoices.ts

import { useQuery, UseQueryOptions } from "@tanstack/react-query";

export type Invoice = {
  _id: string,
  invoiceNumber: string,
  customerId: string,
  subscriptionId?: string,
  status: "draft" | "open" | "paid" | "void" | "uncollectible",
  amount: number,
  currency: string,
  dueDate: string,
  issuedDate: string,
  pdfUrl?: string,
  metadata?: Record<string, any>,
  [key: string]: any,
};

export type GetInvoicesResponse = {
  invoice?: Invoice,
  invoices?: Invoice[],
};

export type GetInvoicesFilters = {
  invoiceId?: string,
  customerId?: string,
  subscriptionId?: string,
  status?: string,
};

const useApiKey = () => {
// !! REPLACE THIS PLACEHOLDER !!
const apiKey = 'YOUR_CREDIBILL_API_KEY';
return apiKey;
};

const CREDIBILL_URL = 'https://giant-goldfish-922.convex.site';
const ENDPOINT = '/api/v1/invoices';

type UseGetInvoicesOptions = Omit<UseQueryOptions<
GetInvoicesResponse,
Error

> , 'queryFn' | 'queryKey'>;

/\*\*

- Custom React Query hook for fetching invoice(s)
- @param filters - Optional filters (invoiceId, customerId, subscriptionId, status)
- @param options - Optional configuration options for useQuery
- @returns The query object provided by TanStack Query
  \*/
  export const useGetInvoices = (
  filters?: GetInvoicesFilters,
  options?: UseGetInvoicesOptions
  ) => {
  const apiKey = useApiKey();

return useQuery<GetInvoicesResponse, Error>({
queryKey: ['invoices', filters],
queryFn: async () => {
if (!apiKey) {
throw new Error("API Key is required for authentication.");
}

      const url = new URL(`${CREDIBILL_URL}${ENDPOINT}`);
      if (filters?.invoiceId) {
        url.searchParams.append('invoiceId', filters.invoiceId);
      }
      if (filters?.customerId) {
        url.searchParams.append('customerId', filters.customerId);
      }
      if (filters?.subscriptionId) {
        url.searchParams.append('subscriptionId', filters.subscriptionId);
      }
      if (filters?.status) {
        url.searchParams.append('status', filters.status);
      }

      const response = await fetch(url.toString(), {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${apiKey}`,
        },
      });

      if (!response.ok) {
        const errorBody = await response.json();
        throw new Error(errorBody.error || `HTTP error! Status: ${response.status}`);
      }

      return response.json();
    },
    ...options,

});
};

Usage in a Component

import React, { useState } from 'react';
import { useGetInvoices } from './hooks/useGetInvoices';

export const InvoicesViewer = () => {
  const [subscriptionId, setSubscriptionId] = useState('');
  const [status, setStatus] = useState('');

  const { data, isLoading, isError, error } = useGetInvoices({
    subscriptionId: subscriptionId || undefined,
    status: status || undefined,
  });

  return (
    <div className="p-6 max-w-4xl mx-auto bg-white rounded-xl shadow-lg space-y-4">
      <h2 className="text-2xl font-bold text-gray-800">View Invoices</h2>

      <div className="space-y-3">
        <div>
          <label htmlFor="subscriptionId" className="block text-sm font-medium text-gray-700">
            Subscription ID (Optional)
          </label>
          <input
            id="subscriptionId"
            type="text"
            value={subscriptionId}
            onChange={(e) => setSubscriptionId(e.target.value)}
            className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
          />
        </div>

        <div>
          <label htmlFor="status" className="block text-sm font-medium text-gray-700">
            Status (Optional)
          </label>
          <select
            id="status"
            value={status}
            onChange={(e) => setStatus(e.target.value)}
            className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
          >
            <option value="">All Statuses</option>
            <option value="draft">Draft</option>
            <option value="open">Open</option>
            <option value="paid">Paid</option>
            <option value="void">Void</option>
            <option value="uncollectible">Uncollectible</option>
          </select>
        </div>
      </div>

      {isLoading && <p className="text-gray-600">Loading invoices...</p>}
      {isError && (
        <div className="p-3 bg-red-100 border border-red-400 text-red-700 rounded-lg">
          Error: {error?.message}
        </div>
      )}

      {data?.invoices && data.invoices.length > 0 && (
        <div className="mt-6">
          <h3 className="text-lg font-semibold text-gray-700 mb-4">Invoices</h3>
          <div className="space-y-3">
            {data.invoices.map((invoice) => (
              <div key={invoice._id} className="p-4 border border-gray-200 rounded-lg">
                <div className="flex justify-between items-start">
                  <div>
                    <p className="font-semibold text-gray-800">Invoice #{invoice.invoiceNumber}</p>
                    <p className="text-sm text-gray-600">Status: <span className="font-medium">{invoice.status}</span></p>
                    <p className="text-sm text-gray-600">Due: {new Date(invoice.dueDate).toLocaleDateString()}</p>
                  </div>
                  <div className="text-right">
                    <p className="text-xl font-bold text-gray-900">
                      {invoice.amount} {invoice.currency}
                    </p>
                    {invoice.pdfUrl && (
                      <a
                        href={invoice.pdfUrl}
                        target="_blank"
                        rel="noopener noreferrer"
                        className="text-sm text-blue-600 hover:text-blue-800 mt-2 inline-block"
                      >
                        View PDF
                      </a>
                    )}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>
      )}

      {data?.invoice && (
        <div className="mt-6 p-4 border border-gray-200 rounded-lg bg-gray-50">
          <h3 className="text-lg font-semibold text-gray-700 mb-3">Invoice Details</h3>
          <div className="space-y-2">
            <p className="text-sm">Invoice #: <span className="font-mono">{data.invoice.invoiceNumber}</span></p>
            <p className="text-sm">Status: <span className="font-semibold">{data.invoice.status}</span></p>
            <p className="text-sm">Amount: <span className="text-lg font-bold">{data.invoice.amount} {data.invoice.currency}</span></p>
            <p className="text-sm">Due: {new Date(data.invoice.dueDate).toLocaleDateString()}</p>
            {data.invoice.pdfUrl && (
              <a
                href={data.invoice.pdfUrl}
                target="_blank"
                rel="noopener noreferrer"
                className="text-blue-600 hover:text-blue-800 mt-3 inline-block font-semibold"
              >
                Download PDF
              </a>
            )}
          </div>
        </div>
      )}
    </div>
  );
};

Response Examples

Get Single Invoice:
{
  "invoice": {
    "_id": "inv_abc123def456",
    "invoiceNumber": "INV-001",
    "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
    "subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
    "status": "open",
    "amount": 50000,
    "currency": "UGX",
    "dueDate": "2024-02-15",
    "issuedDate": "2024-01-15",
    "pdfUrl": "https://credibill.com/invoices/inv_abc123def456.pdf"
  }
}
List Invoices:
{
  "invoices": [
    {
      "_id": "inv_abc123def456",
      "invoiceNumber": "INV-001",
      "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
      "subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
      "status": "open",
      "amount": 50000,
      "currency": "UGX",
      "dueDate": "2024-02-15",
      "issuedDate": "2024-01-15",
      "pdfUrl": "https://credibill.com/invoices/inv_abc123def456.pdf"
    },
    {
      "_id": "inv_xyz789abc123",
      "invoiceNumber": "INV-002",
      "customerId": "j97bp2fjzhbx59ayye52ag2gsd7ydw1c",
      "subscriptionId": "m90dro4hlizjt71cvwg64cj4juf9ze3e",
      "status": "paid",
      "amount": 50000,
      "currency": "UGX",
      "dueDate": "2024-01-15",
      "issuedDate": "2023-12-15",
      "pdfUrl": "https://credibill.com/invoices/inv_xyz789abc123.pdf"
    }
  ]
}