kamiPay LogokamiPay Docs

Checkouts

List, create and update checkouts (cajas) within your stores.

Each store can have multiple checkouts (cajas). Each checkout is linked to one of your merchant wallets, which determines where the settlement goes when a QR Code is paid. Use the endpoints below to manage your checkouts.

Before creating a checkout, you need a wallet_id from your merchant account. Use the Wallets endpoint to retrieve your available wallets.

Access: every Account Administration endpoint needs credentials with the pay-in scope (the ones kamiPay gives you have it) and a user with a management role, which they normally have; otherwise 401 without the scope, or 403 without the role, so contact support.


List Checkouts for a Store

GET /v2/stores/{store_id}/checkouts

Returns all checkouts for a given store, scoped to your merchant account. Returns 404 if the store doesn't exist or belongs to a different merchant.

Path Parameters

NameTypeDescription
store_idintegerRequired. The store ID to query checkouts for.

Example Request

const store_id = 1
const url = `${baseURL}/v2/stores/${store_id}/checkouts`

const response = await fetch(url, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${access_token}`,
  },
});
import requests

store_id = 1
url = f"{base_url}/v2/stores/{store_id}/checkouts"

headers = {
  "Authorization": f"Bearer {access_token}",
}

response = requests.get(url, headers=headers)
package main

import (
  "fmt"
  "net/http"
)

func main() {
  store_id := 1
  url := fmt.Sprintf("%s/v2/stores/%d/checkouts", baseURL, store_id)

  req, _ := http.NewRequest("GET", url, nil)
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error making request:", err)
    return
  }
  defer resp.Body.Close()
}

Response

[
  {
    "merchant_id": 1,
    "store_id": 1,
    "checkout_id": 1,
    "checkout_desc": "Main register",
    "wallet_id": 3,
    "default_qr": null
  },
  {
    "merchant_id": 1,
    "store_id": 1,
    "checkout_id": 500,
    "checkout_desc": "Counter 500",
    "wallet_id": 3,
    "default_qr": "printed_dynamic_qr"
  }
]

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

With a valid token without the pay-in scope:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}
{
  "detail": "User role not authorized for this endpoint"
}
{
  "detail": "Store not found"
}

A plain-text body, not JSON:

Internal Server Error

Response Fields

FieldTypeDescription
merchant_idintegerYour merchant ID.
store_idintegerThe store this checkout belongs to.
checkout_idintegerCheckout identifier. Use this when creating a QR Code.
checkout_descstringDescription of the checkout.
wallet_idintegerWallet associated with this checkout. Settlements go to this wallet's address.
default_qrstring | nullThe checkout's default_qr config. "printed_dynamic_qr" means the checkout is in Printed Dynamic QR mode; null or "dynamic" means standard dynamic QRs.

Create Checkout

POST /v2/stores/{store_id}/checkouts

Creates a new checkout within a store. The checkout_id is assigned automatically and is scoped to the store. The wallet_id must belong to your merchant account — cross-merchant wallet assignment is not allowed.

Path Parameters

NameTypeDescription
store_idintegerRequired. The store to create the checkout in.

Body Parameters

NameTypeDescription
checkout_descstringRequired. Description of the checkout (e.g. "Register 1", "Self-service kiosk").
wallet_idintegerRequired. The wallet to associate with this checkout. Must belong to your merchant account.

Example Request

const store_id = 1
const url = `${baseURL}/v2/stores/${store_id}/checkouts`

const body = {
  checkout_desc: "Register 1",
  wallet_id: 3,
}

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});
import requests

store_id = 1
url = f"{base_url}/v2/stores/{store_id}/checkouts"

body = {
  "checkout_desc": "Register 1",
  "wallet_id": 3,
}

headers = {
  "Authorization": f"Bearer {access_token}",
  "Content-Type": "application/json",
}

response = requests.post(url, json=body, headers=headers)
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "net/http"
)

func main() {
  store_id := 1
  url := fmt.Sprintf("%s/v2/stores/%d/checkouts", baseURL, store_id)

  body := map[string]interface{}{
    "checkout_desc": "Register 1",
    "wallet_id":     3,
  }

  requestBody, _ := json.Marshal(body)

  req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
  req.Header.Add("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

{
  "merchant_id": 1,
  "store_id": 1,
  "checkout_id": 2,
  "checkout_desc": "Register 1",
  "wallet_id": 3
}
{
  "detail": "Wallet not found"
}

Other detail value: "Wallet network deprecated", when the wallet isn't on Polygon (see Wallets).

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

With a valid token without the pay-in scope:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}
{
  "detail": "User role not authorized for this endpoint"
}
{
  "detail": "Store not found"
}

A plain-text body, not JSON:

Internal Server Error

400 Wallet not found is returned when the wallet_id doesn't exist or belongs to a different merchant. This prevents cross-merchant wallet assignment.

Response Fields

FieldTypeDescription
merchant_idintegerYour merchant ID.
store_idintegerThe store this checkout belongs to.
checkout_idintegerNewly assigned checkout identifier.
checkout_descstringDescription of the checkout.
wallet_idintegerThe wallet linked to this checkout.

Update Checkout

PATCH /v2/stores/{store_id}/checkouts/{checkout_id}

Changes a checkout's description or its wallet. This is the only way to move a checkout's settlement to another wallet: every payment processed on the checkout afterwards settles to the new wallet, QRs created before the change included. Only the fields you send are changed: a field you leave out, or send as null, keeps its value.

Path Parameters

NameTypeDescription
store_idintegerRequired. The store the checkout belongs to.
checkout_idintegerRequired. The checkout to update.

Body Parameters

NameTypeDescription
checkout_descstringOptional. New description of the checkout. At least 1 character.
wallet_idintegerOptional. The wallet the checkout settles to from now on. Must belong to your merchant account.

Example Request

const store_id = 1
const checkout_id = 500
const url = `${baseURL}/v2/stores/${store_id}/checkouts/${checkout_id}`

const body = {
  wallet_id: 4,
}

const response = await fetch(url, {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});
import requests

store_id = 1
checkout_id = 500
url = f"{base_url}/v2/stores/{store_id}/checkouts/{checkout_id}"

body = {
  "wallet_id": 4,
}

headers = {
  "Authorization": f"Bearer {access_token}",
  "Content-Type": "application/json",
}

response = requests.patch(url, json=body, headers=headers)
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "net/http"
)

func main() {
  store_id := 1
  checkout_id := 500
  url := fmt.Sprintf("%s/v2/stores/%d/checkouts/%d", baseURL, store_id, checkout_id)

  body := map[string]interface{}{
    "wallet_id": 4,
  }

  requestBody, _ := json.Marshal(body)

  req, _ := http.NewRequest("PATCH", url, bytes.NewBuffer(requestBody))
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
  req.Header.Add("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

{
  "merchant_id": 1,
  "store_id": 1,
  "checkout_id": 500,
  "checkout_desc": "Counter 500",
  "wallet_id": 4
}
{
  "detail": "Wallet not found"
}

Other detail value: "Wallet network deprecated", when the wallet isn't on Polygon (see Wallets).

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

With a valid token without the pay-in scope:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}
{
  "detail": "User role not authorized for this endpoint"
}
{
  "detail": "Checkout not found"
}

A plain-text body, not JSON:

Internal Server Error

Response Fields

FieldTypeDescription
merchant_idintegerYour merchant ID.
store_idintegerThe store this checkout belongs to.
checkout_idintegerThe checkout that was updated.
checkout_descstringDescription of the checkout.
wallet_idintegerThe wallet linked to this checkout.

Checkout Configs

Each checkout has a flexible key–value configuration store (checkout_configs) that lets you toggle features on a per-checkout basis. The tuple (merchant_id, store_id, checkout_id, config_type) is unique, so re-using a config_type overwrites the previous value.

The most relevant config_type today is default_qr. Setting default_qr=printed_dynamic_qr on a checkout makes create_dynamic_pix dispatch to the Printed Dynamic QR flow for that checkout.

default_qr accepts two values:

  • printed_dynamic_qr: printed mode.
  • dynamic: standard dynamic QRs. It's the value that reverts a printed checkout.

Any other value is rejected with 422, and nothing is stored.

Configure with care. These endpoints write directly to your checkout's configuration. A misconfigured checkout (wrong config_type, unsupported config_value, or a value that points to a resource that doesn't exist — e.g. setting default_qr=printed_dynamic_qr on a checkout that has no Printed Dynamic QR assigned) can break future charges on that checkout. Use these endpoints at your own risk. If you're not sure how to configure a checkout, contact support and we'll help you set it up.

Set a Checkout Config

POST /v2/stores/{store_id}/checkouts/{checkout_id}/configs

Upserts a single (config_type, config_value) row for the given checkout. Returns 404 if the checkout doesn't exist or doesn't belong to your merchant.

Path Parameters

NameTypeDescription
store_idintegerRequired. The store the checkout belongs to.
checkout_idintegerRequired. The checkout to configure.

Body Parameters

NameTypeDescription
config_typestringRequired. The configuration key (for example, default_qr).
config_valuestringRequired. The configuration value (for example, printed_dynamic_qr). For default_qr, printed_dynamic_qr or dynamic.

Example Request

const store_id = 1
const checkout_id = 500
const url = `${baseURL}/v2/stores/${store_id}/checkouts/${checkout_id}/configs`

const body = {
  config_type: "default_qr",
  config_value: "printed_dynamic_qr",
}

const response = await fetch(url, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${access_token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(body),
});
import requests

store_id = 1
checkout_id = 500
url = f"{base_url}/v2/stores/{store_id}/checkouts/{checkout_id}/configs"

body = {
  "config_type": "default_qr",
  "config_value": "printed_dynamic_qr",
}

headers = {
  "Authorization": f"Bearer {access_token}",
  "Content-Type": "application/json",
}

response = requests.post(url, json=body, headers=headers)
package main

import (
  "bytes"
  "encoding/json"
  "fmt"
  "net/http"
)

func main() {
  store_id := 1
  checkout_id := 500
  url := fmt.Sprintf("%s/v2/stores/%d/checkouts/%d/configs", baseURL, store_id, checkout_id)

  body := map[string]interface{}{
    "config_type":  "default_qr",
    "config_value": "printed_dynamic_qr",
  }

  requestBody, _ := json.Marshal(body)

  req, _ := http.NewRequest("POST", url, bytes.NewBuffer(requestBody))
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
  req.Header.Add("Content-Type", "application/json")

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

{
  "merchant_id": 1,
  "store_id": 1,
  "checkout_id": 500,
  "config_type": "default_qr",
  "config_value": "printed_dynamic_qr"
}

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

With a valid token without the pay-in scope:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}
{
  "detail": "User role not authorized for this endpoint"
}
{
  "detail": "Checkout not found"
}

A default_qr other than printed_dynamic_qr or dynamic, such as printed_dynamic, returns:

{
  "detail": [
    {
      "type": "value_error",
      "loc": ["body", "config_value"],
      "msg": "Value error, 'default_qr' must be one of ['printed_dynamic_qr', 'dynamic'], got 'printed_dynamic'",
      "input": "printed_dynamic",
      "ctx": {
        "error": {}
      }
    }
  ]
}

Nothing is stored. An empty config_type or config_value is rejected the same way, with type string_too_short on that field.

A plain-text body, not JSON:

Internal Server Error

Response Fields

FieldTypeDescription
merchant_idintegerYour merchant ID.
store_idintegerThe store the checkout belongs to.
checkout_idintegerThe checkout that was configured.
config_typestringThe configuration key that was set.
config_valuestringThe configuration value that was set.

List Checkout Configs

GET /v2/stores/{store_id}/checkouts/{checkout_id}/configs

Returns every checkout_configs row for the given checkout, ordered by config_type. Returns an empty array when the checkout has no configs, and 404 if the checkout doesn't exist or doesn't belong to your merchant.

Path Parameters

NameTypeDescription
store_idintegerRequired. The store the checkout belongs to.
checkout_idintegerRequired. The checkout to inspect.

Example Request

const store_id = 1
const checkout_id = 500
const url = `${baseURL}/v2/stores/${store_id}/checkouts/${checkout_id}/configs`

const response = await fetch(url, {
  method: "GET",
  headers: {
    Authorization: `Bearer ${access_token}`,
  },
});
import requests

store_id = 1
checkout_id = 500
url = f"{base_url}/v2/stores/{store_id}/checkouts/{checkout_id}/configs"

headers = {"Authorization": f"Bearer {access_token}"}

response = requests.get(url, headers=headers)
package main

import (
  "fmt"
  "net/http"
)

func main() {
  store_id := 1
  checkout_id := 500
  url := fmt.Sprintf("%s/v2/stores/%d/checkouts/%d/configs", baseURL, store_id, checkout_id)

  req, _ := http.NewRequest("GET", url, nil)
  req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))

  client := &http.Client{}
  resp, err := client.Do(req)
  if err != nil {
    fmt.Println("Error:", err)
    return
  }
  defer resp.Body.Close()
}

Response

[
  {
    "merchant_id": 1,
    "store_id": 1,
    "checkout_id": 500,
    "config_type": "default_qr",
    "config_value": "printed_dynamic_qr"
  }
]

With an invalid or expired token:

{
  "detail": "Could not validate credentials"
}

With a valid token without the pay-in scope:

{
  "detail": "Token Profile Not Authorized for this Endpoint"
}
{
  "detail": "User role not authorized for this endpoint"
}
{
  "detail": "Checkout not found"
}

A plain-text body, not JSON:

Internal Server Error

On this page