gjold API
An HTTPS and JSON API for card payments: create payment links, capture, refund, and void the transactions they create, and charge payers' saved cards.
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.
Coming from v1? Moving from v1 to v2 says what changes.
Requests
All requests go to https://api.gjold.com/v2/, or
https://api.gjold.dev/v2/ in the test environment, followed by the
object's URI, as URLs describes.
| Request | What it does |
|---|---|
POST {shopid}:pay/new |
Create payment link: a link to the payment window, for one payment. |
DELETE {shopid}:pay:{payid} |
Delete payment link, so that it can't be paid. |
POST {shopid}:trn:{trnid}/capture |
Capture transaction: take some or all of what was authorized. |
POST {shopid}:trn:{trnid}/refund |
Refund transaction: give back some or all of what was captured. |
POST {shopid}:trn:{trnid}/void |
Void transaction: release the authorization, before any capture. |
POST {shopid}:sub/new |
Create subscriber: a link where the payer saves their card. |
POST {shopid}:sub:{subid}/charge |
Charge subscriber without the payer. |
POST {shopid}:sub:{subid}/renew |
Renew subscriber: a link where the payer saves a new card. |
DELETE {shopid}:sub:{subid} |
Delete subscriber, so that it can't be charged. |
GET {shopid}:seq:{seq} |
Seq: the objects that changed since {seq}. |
POST to your URL |
Ping, which we send you when there are changes. |
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/v2/ |
| Test environment | https://api.gjold.dev/v2/, 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. See Authentication. |
| URL | /v2/{shopid}:{db}, /v2/{shopid}:{db}:{id}, or either followed by /{action}. See URLs. |
| Request body | One JSON object, in UTF-8. See Request bodies. |
| Success | 200, with a JSON object, application/json. |
| Error | A 4xx or 5xx status, 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, void, and charge take an Idempotency-Key. See Idempotency. |
| Waiting | Capture, refund, void, 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 and shop ID
replace {apikey} and {shopid} 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/v2/{shopid}:pay/new \
-u {apikey} \
-d '{"orderid":"1234zx","items":[{"total":"99.95 DKK"}]}'
We answer with the link's "url", and its "id", by which you could
delete it:
{
"url": "https://pay.gjold.dev/q3z3amhrkmyrgk6i",
"id": "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/v2/{shopid}: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/v2/{shopid}:trn: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/v2/{shopid}: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,
"who": "s1379#1",
"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 which key made it, and "left" shows that nothing more can be
captured.
6. Go live
In production, you don't call seq by hand. Give your API key a ping
URL, 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
shop ID and 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
{shopid}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.