# gjold API

Source: <https://docs.gjold.com/v1>

An HTTPS and JSON API for card payments: create payment links, capture and refund the transactions they create, and charge payers' saved cards.

These docs are of v1, the API in use. [v2](https://docs.gjold.com/v2) launches in early 2027, and v1 is supported for years after it: see [Moving from v1 to v2](https://docs.gjold.com/notes/moving-to-v2).

One rule shapes it: requests change objects, and only [synchronization](https://docs.gjold.com/v1/synchronization) tells you what they became. When a payer pays, or you capture, we send you a [ping](https://docs.gjold.com/v1/synchronization/ping), and you read the changed objects with [seq](https://docs.gjold.com/v1/synchronization/seq). A request that changes an object answers with little more than whether it succeeded.

## Requests

All requests go to `https://api.gjold.com/v1/`, or `https://api.gjold.dev/v1/` in the test environment, followed by the request's path, as [URLs](https://docs.gjold.com/v1/requests#urls) describes. The shop is your API key's.

| Request | What it does |
|---|---|
| `POST new` | [Create payment link](https://docs.gjold.com/v1/payment-links/create): a link to the payment window, for one payment. |
| `POST transactions/{trnid}/capture` | [Capture transaction](https://docs.gjold.com/v1/transactions/capture): take some or all of what was authorized. |
| `POST transactions/{trnid}/refund` | [Refund transaction](https://docs.gjold.com/v1/transactions/refund): give back some or all of what was captured. |
| `POST new`, with `"subscriber"` | [Create subscriber](https://docs.gjold.com/v1/subscribers/create): a link where the payer saves their card. |
| `POST subscribers/{subid}/charge` | [Charge subscriber](https://docs.gjold.com/v1/subscribers/charge) without the payer. |
| `POST subscribers/{subid}/renew` | [Renew subscriber](https://docs.gjold.com/v1/subscribers/renew): a link where the payer saves a new card. |
| `GET seq/{seq}` | [Seq](https://docs.gjold.com/v1/synchronization/seq): the objects that changed since `{seq}`. |
| `POST` to your URL | [Ping](https://docs.gjold.com/v1/synchronization/ping), which we send you when there are changes. |

v1 has no request to void a transaction, or to delete a link or a subscriber.

[Protocol](https://docs.gjold.com/v1#protocol) and [Limits](https://docs.gjold.com/v1#limits), below, are what every request shares, each with a link to the page that has the details, and the [implementation notes](https://docs.gjold.com/notes) say how to build an integration that holds up under load and failure.

## Protocol

| What | Value |
|---|---|
| Production | `https://api.gjold.com/v1/` |
| Test environment | `https://api.gjold.dev/v1/`, with its own accounts and keys. See [Environments](https://docs.gjold.com/v1/requests#environments). |
| Transport | HTTPS only. Every response has `Strict-Transport-Security: max-age=63072000`. |
| Authentication | `Authorization: Basic` with an API key, `{shopid}:{secret}`, which names the shop. See [Authentication](https://docs.gjold.com/v1/authentication). |
| URL | `/v1/` and the request's path, with a transaction's or subscriber's ID in it. See [URLs](https://docs.gjold.com/v1/requests#urls). |
| Request body | One JSON object, in UTF-8. Unknown fields, and fields of the wrong type, are ignored. See [Request bodies](https://docs.gjold.com/v1/requests#request-bodies). |
| Success | `200`, with a JSON object, `application/json`. |
| Error | A `4xx` or `5xx` status, most often `500`, with a one-line message in `text/plain`. See [Errors](https://docs.gjold.com/v1/errors). |
| Caching | Every response has `Cache-Control: no-store`. |
| Amounts | A decimal string and a currency code, as in `"99.95 DKK"`. See [Amounts](https://docs.gjold.com/v1/amounts). |
| Retries | Capture, refund, and charge take an `Idempotency-Key`. See [Idempotency](https://docs.gjold.com/v1/idempotency). |
| Waiting | Capture, refund, and charge wait for the acquirer, for up to 10 minutes per call. See [Timeouts](https://docs.gjold.com/notes/retries#timeouts). |
| Changes | You learn of every change by [ping](https://docs.gjold.com/v1/synchronization/ping), and read it with [seq](https://docs.gjold.com/v1/synchronization/seq). See [Synchronization](https://docs.gjold.com/v1/synchronization). |

## Getting started

Follow one payment through the API with curl: from the link that starts it, to its capture. You need an account in the [test environment](https://docs.gjold.com/v1/requests#environments). Its API key replaces `{apikey}` below.

### 1. Create a payment link

Create a link for the amount to pay. `-u` gives curl your API key to [authenticate](https://docs.gjold.com/v1/authentication) with, and `-d` the body, with your order ID and the items:

```sh
curl https://api.gjold.dev/v1/new \
    -u {apikey} \
    -d '{"orderid":"1234zx","items":[{"total":"99.95 DKK"}]}'
```

We answer with the link's `"url"`:

```json
{
  "url": "https://pay.gjold.dev/q3z3amhrkmyrgk6i"
}
```

### 2. Pay it

Open the `"url"` in a browser, as a payer would after you redirect them, and pay. Once the payer's bank has authorized the payment, it is a transaction.

### 3. Read the transaction

In production, we would now [ping](https://docs.gjold.com/v1/synchronization/ping) you. Either way, read the new changes with [seq](https://docs.gjold.com/v1/synchronization/seq). Your first call starts at `0`:

```sh
curl https://api.gjold.dev/v1/seq/0 \
    -u {apikey}
```

```json
{
  "changes": [
    {
      "type": "transaction",
      "id": 1,
      "rev": 1,
      "locked": null,
      "method": {
        "type": "card",
        "id": "JlVFYo77jukOmuAmy6PavAjClA61pM0ydR2rAhCs7lw=",
        "card": {
          "brand": "visa",
          "last4": "1111"
        }
      },
      "services": [
        "clearhaus-1"
      ],
      "info": {
        "ip": "198.51.100.2",
        "ndisplay": 1,
        "nfail": 0
      },
      "time": {
        "created": 1761139386,
        "authorized": 1761139415
      },
      "orderid": "1234zx",
      "billing": {
        "name": "",
        "company": "",
        "vatin": "",
        "gln": "",
        "email": "",
        "phone": "",
        "address": [],
        "city": "",
        "zip": "",
        "state": "",
        "country": ""
      },
      "shipping": {
        "name": "",
        "company": "",
        "email": "",
        "phone": "",
        "address": [],
        "city": "",
        "zip": "",
        "state": "",
        "country": ""
      },
      "items": [
        {
          "quantity": 1,
          "total": "99.95 DKK",
          "name": "",
          "sku": ""
        }
      ],
      "acts": [],
      "totals": {
        "attempted": "99.95 DKK",
        "authorized": "99.95 DKK",
        "captured": "0 DKK",
        "refunded": "0 DKK",
        "voided": "0 DKK",
        "left": "99.95 DKK"
      }
    }
  ],
  "seq": 1
}
```

The one change is the new transaction, with your `"orderid"`. You need its `"id"`, to capture it, and the `"seq"` at the end, where your next call to seq starts.

### 4. Capture it

Capture the amount, as you would when you ship the order. The `"index"` is the number of acts that the transaction has, `0` so far, which keeps the same capture from going through twice:

```sh
curl https://api.gjold.dev/v1/transactions/1/capture \
    -u {apikey} \
    -H 'Idempotency-Key: 6f1c2a9e0b7d4c3f8e5a1b2c3d4e5f60' \
    -d '{"total":"99.95 DKK","index":0}'
```

```json
{}
```

The empty object says that the capture succeeded, and nothing more.

### 5. Read the change

Call seq again, from the `"seq"` that the last call returned, `1`:

```sh
curl https://api.gjold.dev/v1/seq/1 \
    -u {apikey}
```

```json
{
  "changes": [
    {
      "type": "transaction",
      "id": 1,
      "rev": 2,
      "locked": null,
      "method": {
        "type": "card",
        "id": "JlVFYo77jukOmuAmy6PavAjClA61pM0ydR2rAhCs7lw=",
        "card": {
          "brand": "visa",
          "last4": "1111"
        }
      },
      "services": [
        "clearhaus-1"
      ],
      "info": {
        "ip": "198.51.100.2",
        "ndisplay": 1,
        "nfail": 0
      },
      "time": {
        "created": 1761139386,
        "authorized": 1761139415
      },
      "orderid": "1234zx",
      "billing": {
        "name": "",
        "company": "",
        "vatin": "",
        "gln": "",
        "email": "",
        "phone": "",
        "address": [],
        "city": "",
        "zip": "",
        "state": "",
        "country": ""
      },
      "shipping": {
        "name": "",
        "company": "",
        "email": "",
        "phone": "",
        "address": [],
        "city": "",
        "zip": "",
        "state": "",
        "country": ""
      },
      "items": [
        {
          "quantity": 1,
          "total": "99.95 DKK",
          "name": "",
          "sku": ""
        }
      ],
      "acts": [
        {
          "act": "capture",
          "time": 1761139447,
          "uid": null,
          "apikey": "",
          "total": "99.95 DKK",
          "history": []
        }
      ],
      "totals": {
        "attempted": "99.95 DKK",
        "authorized": "99.95 DKK",
        "captured": "99.95 DKK",
        "refunded": "0 DKK",
        "voided": "0 DKK",
        "left": "0 DKK"
      }
    }
  ],
  "seq": 2
}
```

Seq returns the transaction whole, as it is now. Its `"rev"` went from 1 to 2, so a copy of it in your database can't overwrite this one: the higher revision wins. The capture is in `"acts"`, with when it happened, and `"left"` shows that nothing more can be captured.

### 6. Go live

In production, you don't call seq by hand. Set your shop's ping URL in the dashboard, and when a ping comes, call seq until it returns no more changes. [Synchronization](https://docs.gjold.com/v1/synchronization) shows the loop, and the [implementation note on it](https://docs.gjold.com/notes/synchronization) how to run it safely. When your integration works, use your production account's API key, at `api.gjold.com`.

## Limits

| What | Limit | When exceeded |
|---|---|---|
| Request line and headers | 4 KiB together, at most 64 headers | `413` |
| Path | 8 segments | `414` |
| Request body | 32 KiB | `413` |
| Request body, time to send | 30 seconds after the headers | `408` |
| First request on a connection | 60 seconds after connecting | The connection is closed. |
| Idle keep-alive connection | 300 seconds | The connection is closed. |
| `Idempotency-Key` | Kept for 24 hours | A later request with the key runs anew. |
| Seq response | 64 KiB, or one change if that is larger | The rest comes in the next call. |
| Ping, your answer | 7 seconds to its first byte | We hang up, and ping again with the next change or in 5 minutes. |

## Conventions in these docs

- `{trnid}` and the like are placeholders, to replace with a value, braces and all.
- A JSON field is written in quotes, as `"orderid"`, and a field within another by its path, as `items[].total` or `billing.address[]`.
- A field's type is that of JSON, but for `amount`, which is a string in the [amount format](https://docs.gjold.com/v1/amounts), and `integer`, which is a number without a fraction or exponent.
- "Required" means that a request without the field fails. Any field not marked so can be left out, and an empty string is the same as leaving it out.

## Beyond the API

How card payments work, from authorizations and their time limits to 3-D Secure and the rules for charging saved cards, is in the [knowledge base](https://docs.gjold.com/learn). The API docs link to it where those rules decide what your code must do.
