API versionv1 live

gjold API

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 launches in early 2027, and v1 is supported for years after it: see Moving from v1 to v2.

One rule shapes it: requests change objects, and only synchronization tells you what they became. When a payer pays, or you capture, we send you a ping, and you read the changed objects with 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 describes. The shop is your API key's.

Request What it does
POST new Create payment link: a link to the payment window, for one payment.
POST transactions/{trnid}/capture Capture transaction: take some or all of what was authorized.
POST transactions/{trnid}/refund Refund transaction: give back some or all of what was captured.
POST new, with "subscriber" Create subscriber: a link where the payer saves their card.
POST subscribers/{subid}/charge Charge subscriber without the payer.
POST subscribers/{subid}/renew Renew subscriber: a link where the payer saves a new card.
GET seq/{seq} Seq: the objects that changed since {seq}.
POST to your URL 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 and Limits, below, are what every request shares, each with a link to the page that has the details, and the implementation 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.
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.
URL /v1/ and the request's path, with a transaction's or subscriber's ID in it. See URLs.
Request body One JSON object, in UTF-8. Unknown fields, and fields of the wrong type, are ignored. See 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.
Caching Every response has Cache-Control: no-store.
Amounts A decimal string and a currency code, as in "99.95 DKK". See Amounts.
Retries Capture, refund, and charge take an Idempotency-Key. See Idempotency.
Waiting Capture, refund, and charge wait for the acquirer, for up to 10 minutes per call. See Timeouts.
Changes You learn of every change by ping, and read it with seq. See 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. Its API key replaces {apikey} below.

Create a link for the amount to pay. -u gives curl your API key to authenticate with, and -d the body, with your order ID and the items:

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

We answer with the link's "url":

{
  "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 you. Either way, read the new changes with seq. Your first call starts at 0:

curl https://api.gjold.dev/v1/seq/0 \
    -u {apikey}
{
  "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:

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

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:

curl https://api.gjold.dev/v1/seq/1 \
    -u {apikey}
{
  "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 shows the loop, and the implementation note on it 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, 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. The API docs link to it where those rules decide what your code must do.