> For the complete documentation index, see [llms.txt](https://help.dollarlabs.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://help.dollarlabs.io/dollarback-store-credit/developer-tools/merchant-api-keys-and-endpoints.md).

# Merchant API: keys & endpoints

{% embed url="<https://dollarlabs.neetorecord.com/watch/068497057337a7f7d2d1>" %}

*This article's section starts at 1:39 in the video.*

The Merchant API lets your own backend read your cashback configuration, trigger rewards (birthdays collected in your mobile app, social follows verified by your own system, fully custom rewards), and move credit directly with no program behind it. This article covers key management, authentication, and all seven endpoints.

## Prerequisites

* A server-side environment to call the API from. The key must never ship in client or browser code.
* For the birthday, social, and custom endpoints: a matching cashback config of that type, enabled (see [Custom rewards via API](/dollarback-store-credit/earning-cashback-programs/custom-rewards-via-api.md)).
* The credit and debit endpoints need no config: they move credit directly.

## Manage your API key

The key lives on the REST API page, two steps in:

1. Open **DollarBack admin → Settings → Developer API** and click **Manage**.
2. You land on **Developer API**, a hub with two cards: **REST API** — everything in this article — and **Storefront API**, which needs no key ([Storefront window API](/dollarback-store-credit/developer-tools/storefront-window-api.md)). Open **REST API**.
3. Click **Generate API key**. The key appears straight away, masked: use the eye button to **Show key** / **Hide key**, and **Copy** to put it on your clipboard.

Once a key exists, that page leads with it, then documents **Authentication** and **Endpoints** below. Two fields sit alongside the key:

| Field         | What it tells you                                                                                                                                           |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Created**   | When the current key was issued. Rotating resets it.                                                                                                        |
| **Last used** | When a request last authenticated with it. **Never** means no call has ever got through — the quickest way to tell a silent integration from a working one. |

{% hint style="info" %}
This key is not a show-once secret. It stays readable on the page, so **Show key** recovers it whenever you need it and you never have to keep a copy anywhere but your server's environment variables.
{% endhint %}

{% hint style="warning" %}
The admin page says it plainly: "Use this key only from your server runtime — never in client/browser code". Anyone holding the key can credit **and debit** any customer's balance on your store, to any amount. Keep it in server-side environment variables, out of repositories, and rotate it the moment you suspect it has leaked.
{% endhint %}

### Rotating a key

**Rotate** issues a new key and invalidates the old one the moment you click it. There is no confirmation dialog and no overlap window where both keys work, so any server still sending the old key starts getting `401 unauthorized` immediately. Have the new value ready to deploy before you rotate, not after.

{% hint style="warning" %}
There is **one key per store**, not one per environment. Rotating cuts off staging and production together, along with anything else pointed at the Merchant API. If several systems share the key, rotate in a window where you can update all of them at once.
{% endhint %}

## Authentication

Every request needs two headers:

```
X-DollarBack-Shop: your-store.myshopify.com
Authorization: Bearer <your API key>
```

A missing or wrong key returns `401 {"error": "unauthorized"}`. Unexpected server errors return `500 {"error": "internal"}`.

## GET /api/v1/merchant/config

Returns your enabled cashback configurations and tier setup. Use it to discover config IDs for the POST endpoints and to render program details in your own UI.

```bash
curl "https://<app host>/api/v1/merchant/config" \
  -H "X-DollarBack-Shop: your-store.myshopify.com" \
  -H "Authorization: Bearer <your API key>"
```

Success (200):

```json
{
  "shop": "your-store.myshopify.com",
  "configurations": [ ... ],
  "tiers": { "enabled": true, "tiers": [ ... ] }
}
```

The endpoint URLs (with your app host filled in) are listed with **Copy** buttons in the **Endpoints** section of the Merchant API page.

## POST /api/v1/merchant/birthday

Submits a customer's birthday and schedules the birthday credit, the API equivalent of the widget's birthday form.

| Body field         | Type             | Notes                                          |
| ------------------ | ---------------- | ---------------------------------------------- |
| `customerId`       | string or number | The Shopify customer ID.                       |
| `birthday`         | object           | `{ "day": 14, "month": 3, "year": 1992 }`      |
| `configId`         | string           | The birthday config's ID (from `GET /config`). |
| `customerCurrency` | string           | Currency to credit in.                         |

Success (200):

```json
{
  "success": true,
  "message": "...",
  "data": { "customerId": "...", "birthday": { ... }, "nextCreditDate": "...", "creditAmount": ... }
}
```

Errors: `400 {"error": "missing_fields"}` or `{"error": "invalid_birthday"}`, `404 {"error": "Birthday config not found"}`.

## POST /api/v1/merchant/social

Claims a social-follow reward for a customer. Use it when your own system verifies the follow.

| Body field         | Type             | Notes                                  |
| ------------------ | ---------------- | -------------------------------------- |
| `customerId`       | string or number | The Shopify customer ID.               |
| `socialUrl`        | string           | The social link the customer followed. |
| `configId`         | string           | The social config's ID.                |
| `customerCurrency` | string           | Currency to credit in.                 |

Success (200): `{"success": true, "message": "Social follow reward is being processed"}`. The credit is processed asynchronously; verify it in the credit log rather than expecting it in the response.

Errors: `400 {"error": "missing_fields"}`.

## POST /api/v1/merchant/custom

Dispatches a custom (API-only) reward, the endpoint behind [Custom rewards via API](/dollarback-store-credit/earning-cashback-programs/custom-rewards-via-api.md). Credit any behavior you can detect: reviews, referral milestones, offline events.

| Body field         | Type             | Notes                                             |
| ------------------ | ---------------- | ------------------------------------------------- |
| `configId`         | string           | Must be a **custom-type** config that is enabled. |
| `customerId`       | string or number | The Shopify customer ID.                          |
| `customerCurrency` | string           | Currency to credit in.                            |

Success (200): `{"success": true, "requestId": "...", "message": "Custom reward is being processed"}`. Keep the `requestId` for support and deduplication.

Errors: `400 {"error": "missing_fields"}`, `{"error": "invalid_json"}`, or `{"error": "invalid_config", "reason": "not_found" | "not_custom" | "disabled" | "not_scheduled"}`. The reason tells you whether the config ID is wrong, isn't a custom config, or is turned off.

## POST /api/v1/merchant/credit

Credits a customer directly, with no cashback program involved. Use it for goodwill gestures, support resolutions, or any credit your own system decides on. Unlike the reward endpoints above this one is **synchronous**: the response tells you whether the money actually moved, and hands back the transaction ID and the new balance.

| Body field           | Type             | Required | Notes                                                                                                              |
| -------------------- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------ |
| `customerId`         | string or number | Yes      | The numeric Shopify customer ID.                                                                                   |
| `amount`             | number           | Yes      | Greater than `0`, expressed in `currency`. Anything above 1,000,000 is rejected.                                   |
| `currency`           | string           | Yes      | The currency `amount` is in, e.g. `"USD"`. Converted to your shop currency for the credit log.                     |
| `reason`             | string           | No       | Shown in the credit log and in the customer's email.                                                               |
| `expiryInDays`       | number           | No       | Default `0` — the credit never expires.                                                                            |
| `notify`             | boolean          | No       | Default `true` — send the customer the **Credited** email (**Redeemed** on `/debit`), if that template is enabled. |
| `sendToIntegrations` | boolean          | No       | Default `true` — forward the event to Klaviyo, Omnisend, and Flow.                                                 |
| `idempotencyKey`     | string           | No       | A retry with the same key never moves money twice.                                                                 |

```bash
curl -X POST "https://<app host>/api/v1/merchant/credit" \
  -H "X-DollarBack-Shop: your-store.myshopify.com" \
  -H "Authorization: Bearer <your API key>" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7345098912345",
    "amount": 5,
    "currency": "USD",
    "reason": "Support goodwill",
    "idempotencyKey": "case-4821"
  }'
```

Success (200):

```json
{
  "success": true,
  "duplicate": false,
  "transactionId": "gid://shopify/StoreCreditAccountTransaction/1",
  "balance": { "balance": 17.5, "formattedBalance": "$17.50" },
  "amount": 5,
  "currency": "USD"
}
```

Errors: `400 {"error": "missing_fields: ..."}`, `{"error": "invalid_amount: ..."}`, `{"error": "invalid_json"}`, or `{"error": "operation_failed", "message": "..."}` when Shopify itself refuses the movement.

{% hint style="info" %}
Send an `idempotencyKey` on every call — something stable from your own system, like a support ticket or refund ID. If a request times out and you retry it, DollarBack recognises the key, answers `duplicate: true` and credits nothing. A duplicate response repeats the original `transactionId`, but `balance` comes back `null`: nothing moved, so there is no new balance to report. Keys are scoped to your shop, so they only need to be unique within your own store.
{% endhint %}

## POST /api/v1/merchant/debit

Removes store credit from a customer. Body, headers, and response are identical to `/credit`, except `expiryInDays` is ignored.

```bash
curl -X POST "https://<app host>/api/v1/merchant/debit" \
  -H "X-DollarBack-Shop: your-store.myshopify.com" \
  -H "Authorization: Bearer <your API key>" \
  -H "Content-Type: application/json" \
  -d '{
    "customerId": "7345098912345",
    "amount": 5,
    "currency": "USD",
    "reason": "Order adjustment",
    "idempotencyKey": "refund-9912"
  }'
```

Success (200):

```json
{
  "success": true,
  "duplicate": false,
  "transactionId": "gid://shopify/StoreCreditAccountTransaction/2",
  "balance": { "balance": 12.5, "formattedBalance": "$12.50" },
  "amount": 5,
  "currency": "USD"
}
```

Debiting more than the customer holds fails rather than pushing the balance negative: you get `400 {"error": "operation_failed", "message": "..."}` carrying Shopify's own message, and nothing moves. If you need to take "up to" whatever someone has, read `GET /balance` first and debit what it reports.

{% hint style="warning" %}
Debits through this endpoint are real: the balance drops immediately and the customer sees it in the widget and their account. With `notify` left at its default, a debit sends your **Redeemed** email template — worded for a customer spending credit, which may read oddly for an adjustment or a clawback. Set `notify: false` when the change shouldn't be announced, or reword that template ([Customize email templates](/dollarback-store-credit/notifications/customize-email-templates.md)). Test against a staff account before pointing production traffic at it.
{% endhint %}

## GET /api/v1/merchant/balance

Reads a customer's current store credit. Use it before a debit, to show a balance in your own app or portal, or to reconcile against your records.

Shopify keeps **one store credit account per currency**, so a customer who has earned in more than one currency has more than one balance. The endpoint returns all of them and is never cached.

| Query parameter | Required | Notes                                                                  |
| --------------- | -------- | ---------------------------------------------------------------------- |
| `customerId`    | Yes      | The numeric Shopify customer ID. A `gid://` prefix is rejected.        |
| `currency`      | No       | Narrow the response to a single account, e.g. `USD`. Case-insensitive. |

```bash
curl "https://<app host>/api/v1/merchant/balance?customerId=7345098912345" \
  -H "X-DollarBack-Shop: your-store.myshopify.com" \
  -H "Authorization: Bearer <your API key>"
```

Success (200):

```json
{
  "customerId": "7345098912345",
  "balances": [
    { "amount": 17.5, "currency": "USD", "formatted": "$17.50" },
    { "amount": 4, "currency": "EUR", "formatted": "€4.00" }
  ]
}
```

A customer who holds no store credit returns `"balances": []` with a 200, not an error. Treat an empty array as a zero balance.

Errors: `400 {"error": "missing_fields: customerId is required"}`, `400 {"error": "invalid_customer_id"}` when the ID isn't numeric, and `404 {"error": "customer_not_found"}` when no such customer exists in your store.

{% hint style="info" %}
Read the currency, don't assume it. `balances` is an array in no guaranteed order, so pick the entry whose `currency` matches the one you care about rather than taking the first. `formatted` is a convenience for display only — do arithmetic on `amount`.
{% endhint %}

## Verify it works

Call `GET /config` or `GET /balance` first: a 200 confirms your key and headers, and neither one writes anything. Then fire one POST for a test customer and watch **DollarBack admin → Analytics → Activity** for the credit. The birthday, social, and custom endpoints are asynchronous — allow a few minutes. `/credit` and `/debit` are synchronous: a 200 with `duplicate: false` means the balance has already changed, and the response shows it.

## Common issues

* `401 unauthorized`: wrong or rotated key, or a missing `X-DollarBack-Shop` header.
* `invalid_config` on `/custom`: the config ID isn't a custom config, or the config is disabled.
* The POST returned 200 but no credit appears: processing is asynchronous; check [Activity: credit logs & scheduled rewards](/dollarback-store-credit/analytics-data-and-account/credit-logs-and-scheduled-rewards.md) after a few minutes.
* `operation_failed` on `/debit`: the customer's balance is smaller than `amount`. Nothing moved; call `GET /balance` and debit what they actually hold.
* `balances: []` from `/balance`: the customer exists but has never held store credit on your store. That's a zero balance, not a failure — a missing customer returns `404 customer_not_found` instead.
* `duplicate: true` in the response: an earlier call with the same `idempotencyKey` already succeeded. That's the retry working as intended, not an error.

## Related articles

* [Custom rewards via API](/dollarback-store-credit/earning-cashback-programs/custom-rewards-via-api.md)
* [Storefront window API (theme devs)](/dollarback-store-credit/developer-tools/storefront-window-api.md)
* [Set up birthday rewards](/dollarback-store-credit/earning-cashback-programs/set-up-birthday-rewards.md)
* [Set up social follow rewards](/dollarback-store-credit/earning-cashback-programs/set-up-social-follow-rewards.md)
* [Shopify Flow: triggers and actions](/dollarback-store-credit/integrations/shopify-flow-triggers-and-actions.md)
