diff --git a/src/Donations/Controllers/DonationRequestController.php b/src/Donations/Controllers/DonationRequestController.php
new file mode 100644
index 0000000000..03913e9354
--- /dev/null
+++ b/src/Donations/Controllers/DonationRequestController.php
@@ -0,0 +1,91 @@
+<?php
+
+namespace Give\Donations\Controllers;
+
+use Give\Donations\Models\Donation;
+use Give\Donations\Repositories\DonationRepository;
+use Give\Donations\ValueObjects\DonationMetaKeys;
+use Give\Donations\ValueObjects\DonationRoute;
+use WP_Error;
+use WP_REST_Request;
+use WP_REST_Response;
+
+/**
+ * @unreleased
+ */
+class DonationRequestController
+{
+    /**
+     * @unreleased
+     *
+     * @return WP_Error | WP_REST_Response
+     */
+    public function getDonation(WP_REST_Request $request)
+    {
+        $donation = Donation::find($request->get_param('id'));
+
+        if ( ! $donation) {
+            return new WP_Error('donation_not_found', __('Donation not found', 'give'), ['status' => 404]);
+        }
+
+        return new WP_REST_Response($donation->toArray());
+    }
+
+    /**
+     * @unreleased
+     */
+    public function getDonations(WP_REST_Request $request): WP_REST_Response
+    {
+        $campaignId = $request->get_param('campaignId');
+        $page = $request->get_param('page');
+        $perPage = $request->get_param('per_page');
+
+        $query = give(DonationRepository::class)->prepareQuery();
+
+        if ($campaignId) {
+            $metaKey = DonationMetaKeys::CAMPAIGN_ID;
+            $query->attachMeta('give_donationmeta', 'ID', 'donation_id', $metaKey)
+                ->where("give_donationmeta_attach_meta_{$metaKey}.meta_value", $campaignId);
+        }
+
+        $query
+            ->limit($perPage)
+            ->offset(($page - 1) * $perPage);
+
+        $donations = $query->getAll() ?? [];
+        $totalDonations = empty($donations) ? 0 : Donation::query()->count();
+        $totalPages = (int)ceil($totalDonations / $perPage);
+
+        $response = rest_ensure_response($donations);
+        $response->header('X-WP-Total', $totalDonations);
+        $response->header('X-WP-TotalPages', $totalPages);
+
+        $base = add_query_arg(
+            map_deep($request->get_query_params(), function ($value) {
+                if (is_bool($value)) {
+                    $value = $value ? 'true' : 'false';
+                }
+
+                return urlencode($value);
+            }),
+            rest_url(DonationRoute::DONATIONS)
+        );
+
+        if ($page > 1) {
+            $prevPage = $page - 1;
+
+            if ($prevPage > $totalPages) {
+                $prevPage = $totalPages;
+            }
+
+            $response->link_header('prev', add_query_arg('page', $prevPage, $base));
+        }
+
+        if ($totalPages > $page) {
+            $nextPage = $page + 1;
+            $response->link_header('next', add_query_arg('page', $nextPage, $base));
+        }
+
+        return $response;
+    }
+}
diff --git a/src/Donations/Routes/RegisterDonationRoutes.php b/src/Donations/Routes/RegisterDonationRoutes.php
new file mode 100644
index 0000000000..29a8a46da4
--- /dev/null
+++ b/src/Donations/Routes/RegisterDonationRoutes.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace Give\Donations\Routes;
+
+use Give\Donations\Controllers\DonationRequestController;
+use Give\Donations\ValueObjects\DonationRoute;
+use WP_REST_Request;
+use WP_REST_Server;
+
+/**
+ * @unreleased
+ */
+class RegisterDonationRoutes
+{
+    /**
+     * @var DonationRequestController
+     */
+    protected $donationRequestController;
+
+    /**
+     * @unreleased
+     */
+    public function __construct(DonationRequestController $donationRequestController)
+    {
+        $this->donationRequestController = $donationRequestController;
+    }
+
+    /**
+     * @unreleased
+     */
+    public function __invoke()
+    {
+        $this->registerGetDonation();
+        $this->registerGetDonations();
+    }
+
+    /**
+     * Get Donation route
+     *
+     * @unreleased
+     */
+    public function registerGetDonation()
+    {
+        register_rest_route(
+            DonationRoute::NAMESPACE,
+            DonationRoute::DONATION,
+            [
+                [
+                    'methods' => WP_REST_Server::READABLE,
+                    'callback' => function (WP_REST_Request $request) {
+                        return $this->donationRequestController->getDonation($request);
+                    },
+                    'permission_callback' => function () {
+                        return current_user_can('manage_options');
+                    },
+                ],
+                'args' => [
+                    'id' => [
+                        'type' => 'integer',
+                        'required' => true,
+                    ],
+                ],
+            ]
+        );
+    }
+
+    /**
+     * Get Donations route
+     *
+     * @unreleased
+     */
+    public function registerGetDonations()
+    {
+        register_rest_route(
+            DonationRoute::NAMESPACE,
+            DonationRoute::DONATIONS,
+            [
+                [
+                    'methods' => WP_REST_Server::READABLE,
+                    'callback' => function (WP_REST_Request $request) {
+                        return $this->donationRequestController->getDonations($request);
+                    },
+                    'permission_callback' => '__return_true',
+                ],
+                'args' => [
+                    'page' => [
+                        'type' => 'integer',
+                        'default' => 1,
+                        'minimum' => 1,
+                    ],
+                    'per_page' => [
+                        'type' => 'integer',
+                        'default' => 30,
+                        'minimum' => 1,
+                        'maximum' => 100,
+                    ],
+                    'campaignId' => [
+                        'type' => 'integer',
+                        'required' => false,
+                        'default' => 0,
+                    ],
+                ],
+            ]
+        );
+    }
+}
diff --git a/src/Donations/ServiceProvider.php b/src/Donations/ServiceProvider.php
index c7b826baff..c9930ee2e9 100644
--- a/src/Donations/ServiceProvider.php
+++ b/src/Donations/ServiceProvider.php
@@ -56,6 +56,8 @@ public function boot()
             MoveDonationCommentToDonationMetaTable::class,
             UnserializeTitlePrefix::class,
         ]);
+
+        $this->registerRoutes();
     }
 
     /**
@@ -129,4 +131,12 @@ private function addCustomFieldsToDonationDetails()
             echo (new DonationDetailsController())->show($donationId);
         });
     }
+
+    /**
+     * @unreleased
+     */
+    private function registerRoutes()
+    {
+        Hooks::addAction('rest_api_init', Routes\RegisterDonationRoutes::class);
+    }
 }
diff --git a/src/Donations/ValueObjects/DonationRoute.php b/src/Donations/ValueObjects/DonationRoute.php
new file mode 100644
index 0000000000..13e7add090
--- /dev/null
+++ b/src/Donations/ValueObjects/DonationRoute.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Give\Donations\ValueObjects;
+
+use Give\Framework\Support\ValueObjects\Enum;
+
+/**
+ * @unreleased
+ *
+ * @method static DonationRoute NAMESPACE()
+ * @method static DonationRoute DONATION()
+ * @method static DonationRoute DONATIONS()
+ * @method bool isNamespace()
+ * @method bool isDonation()
+ * @method bool isDonations()
+ */
+class DonationRoute extends Enum
+{
+    const NAMESPACE = 'give-api/v2';
+    const DONATION = 'donations/(?P<id>[0-9]+)';
+    const DONATIONS = 'donations';
+}
diff --git a/tests/Unit/Donations/Routes/GetDonationsTest.php b/tests/Unit/Donations/Routes/GetDonationsTest.php
new file mode 100644
index 0000000000..9baa536ae4
--- /dev/null
+++ b/tests/Unit/Donations/Routes/GetDonationsTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Unit\Donations\Routes;
+
+use Exception;
+use Give\Campaigns\Models\Campaign;
+use Give\Donations\Models\Donation;
+use Give\Donations\ValueObjects\DonationMetaKeys;
+use Give\Donations\ValueObjects\DonationRoute;
+use Give\Tests\RestApiTestCase;
+use Give\Tests\TestTraits\RefreshDatabase;
+use WP_REST_Request;
+use WP_REST_Server;
+
+/**
+ * @unreleased
+ */
+class GetDonationsTest extends RestApiTestCase
+{
+    use RefreshDatabase;
+
+    /**
+     * @unreleased
+     *
+     * @throws Exception
+     */
+    public function testGetDonationsByCampaignId()
+    {
+        /** @var Campaign $campaign */
+        $campaign = Campaign::factory()->create();
+
+        /** @var  Donation $donation */
+        $donation = Donation::factory()->create();
+        give()->payment_meta->update_meta($donation->id, DonationMetaKeys::CAMPAIGN_ID, $campaign->id);
+
+        $route = '/' . DonationRoute::NAMESPACE . '/' . DonationRoute::DONATIONS;
+        $request = new WP_REST_Request(WP_REST_Server::READABLE, $route);
+        $request->set_query_params(
+            [
+                'campaignId' => $campaign->id,
+            ]
+        );
+
+        $response = $this->dispatchRequest($request);
+        $status = $response->get_status();
+        $data = $response->get_data();
+
+        $this->assertEquals(200, $status);
+        $this->assertEquals($donation->id, $data[0]->id);
+    }
+}