Quickstart
Create an API key, then make your first authenticated Connect API call in your language of choice.
Go from zero to a live, authenticated request in a few minutes. You create an API key in the dashboard, then read donations and your organization profile over the API.
Every step shows every supported language: curl, the typed
@givebear/connect JavaScript client, and plain HTTP in Python, PHP, Ruby,
and Go. Pick a language once; the tabs stay in sync across the page and
remember your choice. The API reference
generates samples for these and more, per endpoint.
This tutorial covers a single organization using an API key. To act on behalf of many organizations, read Authentication for the OAuth flow instead.
Prefer to see it running first? Open the full example (OAuth, the read API, and live webhooks) in StackBlitz:
Before you start
- You are an owner or admin of a Givebear organization.
- Any HTTP client works. The snippets use
curl, Node.js 18+, Python 3 withrequests, PHP with the cURL extension, Ruby's standard library, and Go's standard library.
Create an API key
Open your dashboard and go to Developers -> API keys. Create a key, then select the scopes it needs:
donations:readto read donation records.reference:readto read the organization profile, campaigns, and funds.
Givebear shows the key once. Copy it now. It starts with gb_live_.
The key is a secret. Store it in an environment variable or a secrets manager, never in client-side code or version control. See the full scope list.
Export it so the snippets below can read it:
export GIVEBEAR_TOKEN="gb_live_..."Make your first request
Every endpoint lives under https://givebear.io/api/v1 and takes the key as
a bearer token. Confirm the key works by reading the organization it belongs
to.
curl https://givebear.io/api/v1/organization \
-H "Authorization: Bearer $GIVEBEAR_TOKEN"// npm install @givebear/connect
import { GivebearConnect } from "@givebear/connect";
const gb = new GivebearConnect({ token: process.env.GIVEBEAR_TOKEN! });
const org = await gb.organization.get();
console.log(org.name);# pip install requests
import os
import requests
headers = {"Authorization": f"Bearer {os.environ['GIVEBEAR_TOKEN']}"}
org = requests.get(
"https://givebear.io/api/v1/organization", headers=headers
).json()
print(org["name"])<?php
$token = getenv("GIVEBEAR_TOKEN");
$ch = curl_init("https://givebear.io/api/v1/organization");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
$org = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $org["name"];require "net/http"
require "json"
uri = URI("https://givebear.io/api/v1/organization")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("GIVEBEAR_TOKEN")}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
puts JSON.parse(response.body)["name"]package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET", "https://givebear.io/api/v1/organization", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("GIVEBEAR_TOKEN"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var org struct {
Name string `json:"name"`
}
json.NewDecoder(resp.Body).Decode(&org)
fmt.Println(org.Name)
}{
"id": "...",
"object": "organization",
"name": "Community Center",
"slug": "community-center",
"contact_email": "[email protected]",
"created_at": "2026-01-15T12:00:00.000Z"
}List donations
curl "https://givebear.io/api/v1/donations?limit=10" \
-H "Authorization: Bearer $GIVEBEAR_TOKEN"const { data, has_more, next_cursor } = await gb.donations.list({ limit: 10 });
console.log(data.length, has_more, next_cursor);page = requests.get(
"https://givebear.io/api/v1/donations",
headers=headers,
params={"limit": 10},
).json()
print(len(page["data"]), page["has_more"], page["next_cursor"])$ch = curl_init("https://givebear.io/api/v1/donations?limit=10");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
echo count($page["data"]), " ", $page["has_more"] ? "true" : "false";uri = URI("https://givebear.io/api/v1/donations?limit=10")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("GIVEBEAR_TOKEN")}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
page = JSON.parse(response.body)
puts page["data"].length, page["has_more"]req, _ := http.NewRequest("GET", "https://givebear.io/api/v1/donations?limit=10", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("GIVEBEAR_TOKEN"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var page struct {
Data []map[string]any `json:"data"`
HasMore bool `json:"has_more"`
NextCursor *string `json:"next_cursor"`
}
json.NewDecoder(resp.Body).Decode(&page)
fmt.Println(len(page.Data), page.HasMore)A list endpoint returns a cursor envelope:
{
"data": [
{
"id": "...",
"object": "donation",
"amount_cents": 5000,
"net_amount_cents": 4750,
"currency": "usd",
"donor_name": "Jane Doe",
"donor_email": "[email protected]",
"fund_id": "fnd_123",
"created_at": "2026-06-01T18:30:00.000Z"
}
],
"has_more": true,
"next_cursor": "eyJ..."
}Page through results
Lists return at most limit rows (1 to 100, default 25). When has_more is
true, pass next_cursor back as cursor to fetch the next page. To sync
only recent records, pass updated_since as an ISO-8601 timestamp.
curl "https://givebear.io/api/v1/donations?limit=10&cursor=eyJ..." \
-H "Authorization: Bearer $GIVEBEAR_TOKEN"
curl "https://givebear.io/api/v1/donations?updated_since=2026-01-01T00:00:00Z" \
-H "Authorization: Bearer $GIVEBEAR_TOKEN"// listAll walks every page for you with an async iterator.
for await (const donation of gb.donations.listAll({
updated_since: "2026-01-01T00:00:00Z",
})) {
console.log(donation.amount_cents, donation.donor_email);
}params = {"limit": 10, "updated_since": "2026-01-01T00:00:00Z"}
while True:
page = requests.get(
"https://givebear.io/api/v1/donations", headers=headers, params=params
).json()
for donation in page["data"]:
print(donation["amount_cents"], donation["donor_email"])
if not page["has_more"]:
break
params["cursor"] = page["next_cursor"]$query = ["limit" => 10, "updated_since" => "2026-01-01T00:00:00Z"];
do {
$url = "https://givebear.io/api/v1/donations?" . http_build_query($query);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Authorization: Bearer $token"]);
$page = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($page["data"] as $donation) {
echo $donation["amount_cents"], " ", $donation["donor_email"], "\n";
}
$query["cursor"] = $page["next_cursor"];
} while ($page["has_more"]);params = { limit: 10, updated_since: "2026-01-01T00:00:00Z" }
loop do
uri = URI("https://givebear.io/api/v1/donations")
uri.query = URI.encode_www_form(params)
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("GIVEBEAR_TOKEN")}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
page = JSON.parse(response.body)
page["data"].each { |d| puts "#{d["amount_cents"]} #{d["donor_email"]}" }
break unless page["has_more"]
params[:cursor] = page["next_cursor"]
endcursor := ""
for {
url := "https://givebear.io/api/v1/donations?limit=10&updated_since=2026-01-01T00:00:00Z"
if cursor != "" {
url += "&cursor=" + cursor
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("GIVEBEAR_TOKEN"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
var page struct {
Data []struct {
AmountCents int `json:"amount_cents"`
DonorEmail string `json:"donor_email"`
} `json:"data"`
HasMore bool `json:"has_more"`
NextCursor string `json:"next_cursor"`
}
json.NewDecoder(resp.Body).Decode(&page)
resp.Body.Close()
for _, d := range page.Data {
fmt.Println(d.AmountCents, d.DonorEmail)
}
if !page.HasMore {
break
}
cursor = page.NextCursor
}Handle errors
Failed requests return a non-2xx status and an error envelope with a stable
type and a human-readable message:
{
"error": {
"type": "invalid_request",
"message": "`cursor` is malformed"
}
}# -f makes curl exit non-zero on HTTP errors; drop it to see the body.
curl -f "https://givebear.io/api/v1/donations?cursor=bad" \
-H "Authorization: Bearer $GIVEBEAR_TOKEN"import { ConnectApiError } from "@givebear/connect";
try {
await gb.donations.list();
} catch (err) {
if (err instanceof ConnectApiError) {
console.error(err.status, err.type, err.message);
}
}response = requests.get(
"https://givebear.io/api/v1/donations", headers=headers
)
if not response.ok:
error = response.json()["error"]
print(response.status_code, error["type"], error["message"])$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($status >= 400) {
$error = json_decode($body, true)["error"];
echo $status, " ", $error["type"], " ", $error["message"];
}unless response.is_a?(Net::HTTPSuccess)
error = JSON.parse(response.body)["error"]
puts "#{response.code} #{error["type"]} #{error["message"]}"
endif resp.StatusCode >= 400 {
var envelope struct {
Error struct {
Type string `json:"type"`
Message string `json:"message"`
} `json:"error"`
}
json.NewDecoder(resp.Body).Decode(&envelope)
fmt.Println(resp.StatusCode, envelope.Error.Type, envelope.Error.Message)
}What you just did
You created a scoped gb_live_ API key, read donations and your organization profile over https://givebear.io/api/v1, and paged through results. Every request was scoped to the organization that owns the key.
Next steps
Collect a donation on the organization's behalf. The intent settles into the org's own Stripe account, and card data goes straight to Stripe (you stay SAQ-A). This needs the payments:write scope:
curl https://givebear.io/api/v1/payment-intents \
-H "Authorization: Bearer $GIVEBEAR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount_cents": 5000,
"fund_id": "fnd_123",
"donor_email": "[email protected]",
"donor_name": "Jane Doe"
}'const intent = await gb.paymentIntents.create({
amount_cents: 5000,
fund_id: "fnd_123",
donor_email: "[email protected]",
donor_name: "Jane Doe",
});intent = requests.post(
"https://givebear.io/api/v1/payment-intents",
headers=headers,
json={
"amount_cents": 5000,
"fund_id": "fnd_123",
"donor_email": "[email protected]",
"donor_name": "Jane Doe",
},
).json()$ch = curl_init("https://givebear.io/api/v1/payment-intents");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer $token",
"Content-Type: application/json",
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
"amount_cents" => 5000,
"fund_id" => "fnd_123",
"donor_email" => "[email protected]",
"donor_name" => "Jane Doe",
]));
$intent = json_decode(curl_exec($ch), true);
curl_close($ch);uri = URI("https://givebear.io/api/v1/payment-intents")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("GIVEBEAR_TOKEN")}"
request["Content-Type"] = "application/json"
request.body = {
amount_cents: 5000,
fund_id: "fnd_123",
donor_email: "[email protected]",
donor_name: "Jane Doe",
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
intent = JSON.parse(response.body)body := strings.NewReader(`{
"amount_cents": 5000,
"fund_id": "fnd_123",
"donor_email": "[email protected]",
"donor_name": "Jane Doe"
}`)
req, _ := http.NewRequest("POST", "https://givebear.io/api/v1/payment-intents", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("GIVEBEAR_TOKEN"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()Render Stripe Elements with the returned client_secret, publishable_key,
and stripe_account_id.
Authentication and scopes
API keys, OAuth for multi-org apps, and the full scope list.
Webhooks
Subscribe to donation.created, payout.paid, and more, then verify the signature.
API reference
Every endpoint, parameter, field, and status code.
Versioning
How the v1 surface evolves and what stays stable.