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.
1. Create a payment link
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, asitems[].totalorbilling.address[]. - A field's type is that of JSON, but for
amount, which is a string in the amount format, andinteger, 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.