KulPayAPI Live

API reference

Routing

GET /v1/admin/routing

Routing rules in force

The active rule set and its version, every version, and pending approvals (PSP-6.4). A PSP is eligible when its status allows the merchant (live; pilot for its allow-listed merchants; any working status in test mode), its circuit is closed, its capability matrix allows the request and its daily and monthly limits have room. With no matching rule a payment runs on its account's own connector binding. Failover (failover, max_failovers, default 2): after a soft decline or an error before the request was sent (connection refused, DNS failure, open circuit) the payment moves to the next eligible target, never on an unknown outcome or after the PSP may have accepted it; each move is transaction.failover.attempted and each attempt its own submit stage. Requires connectors:read.

Responses

  • 200 The rule set.

Request

curl -X GET "$KULPAY_URL/v1/admin/routing" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "active": {
    "rules": [
      {
        "id": "cards",
        "match": {
          "methods": [
            "card"
          ]
        },
        "targets": [
          {
            "psp_id": "3KWPqtgPmeCaYplKVRxyJiriQOG",
            "weight": 100
          }
        ]
      }
    ]
  },
  "active_version": 1,
  "versions": [
    {
      "activated_at": "2026-10-10T22:15:44.945952516Z",
      "approval_id": "3KWPqpGgl5DVi0nsBOTijTV7GWK",
      "created_at": "2026-10-10T22:15:44.903403745Z",
      "created_by": "key_accounts",
      "id": "3KWPqrCkfWzVjEDju03YNzH5ntR",
      "reason": "cards to Rota",
      "status": "active",
      "version": 1
    }
  ]
}

POST /v1/admin/routing/dry-run

Dry-run routing rules on recent traffic

Replays the last days (default 7) of a mode's payments through rules (or stored version) and returns the expected split per PSP (weighted targets count by their share), the current split, hits per rule, payments that fall back to their own binding and those no PSP could take. Card BIN and phone-prefix conditions are not replayed (not stored with payments). Nothing is reserved or changed.

Request body application/json required

FieldTypeDescription
daysinteger
modestring
One of: test, live
rulesobject[] Ordered rules: the first that matches decides.
rules[].descriptionstring
rules[].failoverboolean
rules[].id requiredstring
rules[].matchobject Empty fields match anything.
rules[].match.bin_rangesobject[]
rules[].match.countriesstring[]
rules[].match.currenciesstring[]
rules[].match.max_amountstring
rules[].match.merchantsstring[] Account ids or metadata.merchant_id values.
rules[].match.methodsstring[]
rules[].match.min_amountstring
rules[].match.phone_prefixesstring[]
rules[].match.referenceobject The shape of a deposit reference (IO-1.1), on the reference upper-cased and without spaces: every condition set must hold. A lookup whose reference matches no rule goes to the default resolver.
rules[].match.typesstring[]
rules[].max_failoversinteger
rules[].targets requiredobject[]
rules[].targets[].psp_id requiredstring
rules[].targets[].weight requiredinteger Share among the eligible targets; 0 is a fallback only.
versioninteger

Responses

  • 200 {sample, by_psp: [{psp_id, name, expected, share, current}], by_rule, fallback, not_routed, truncated, limitation}.

Request

curl -X POST "$KULPAY_URL/v1/admin/routing/dry-run" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "test",
  "rules": [
    {
      "id": "cards",
      "match": {
        "methods": [
          "card"
        ]
      },
      "targets": [
        {
          "psp_id": "3KWPqtgPmeCaYplKVRxyJiriQOG",
          "weight": 100
        }
      ]
    }
  ]
}'
200 Response application/json
{
  "by_psp": [
    {
      "current": 0,
      "expected": 3,
      "name": "Rota Lda",
      "psp_id": "3KWPqtgPmeCaYplKVRxyJiriQOG",
      "share": 1
    }
  ],
  "by_rule": {
    "cards": 3
  },
  "fallback": 0,
  "limitation": "Card BIN and phone-prefix conditions are not replayed: payments do not store the card number or phone; such rules are counted as not matching.",
  "not_routed": 0,
  "sample": 3,
  "truncated": false,
  "window": "2026-10-03T22:15:44Z to 2026-10-10T22:15:44Z"
}

POST /v1/admin/routing/lookup-preview

Preview where deposit-reference lookups go

Routes sample deposit references through the active rule set and through a proposed one (IO-1.1), without calling any resolver or recording anything: for each, the rule and the resolver now and proposed, and whether it changes. Use it before proposing a rule set with reference conditions. Requires connectors:read.

Request body application/json required

FieldTypeDescription
account_idstring The partner whose lookups are previewed.
methodstring
modestring
One of: test, live
references requiredstring[]
rules requiredobject[] The proposed rules, as in POST /v1/admin/routing/versions.

Responses

  • 200 {data, total}.
  • 400 The request body or query failed validation. The response body lists each field-level issue. invalid_request

Request

curl -X POST "$KULPAY_URL/v1/admin/routing/lookup-preview" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "mode": "test",
  "references": [
    "01234567890",
    "841234567"
  ],
  "rules": [
    {
      "id": "contadores",
      "match": {
        "reference": {
          "digits_only": true,
          "max_length": 11,
          "min_length": 11,
          "prefixes": [
            "0"
          ]
        },
        "types": [
          "NAME_LOOKUP"
        ]
      },
      "targets": [
        {
          "psp_id": "psp_resolver",
          "weight": 1
        }
      ]
    }
  ]
}'
200 Response application/json
{
  "data": [
    {
      "changed": true,
      "current": {
        "psp_id": "psp_kuloffice",
        "rule_id": "method_binding"
      },
      "normalized": "01234567890",
      "proposed": {
        "psp_id": "psp_resolver",
        "rule_id": "contadores"
      },
      "reference": "01234567890"
    }
  ],
  "total": 1
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "The request body failed validation.",
  "error": {
    "code": "invalid_request",
    "errors": [
      {
        "field": "money.amount",
        "message": "must be a positive decimal with at most 2 places"
      }
    ],
    "message": "The request body failed validation."
  },
  "errors": [
    {
      "field": "money.amount",
      "message": "must be a positive decimal with at most 2 places"
    }
  ],
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

POST /v1/admin/routing/versions

Prepare a routing rules version

A new version of the whole rule set; it is active once a second person approves it (routing.activate, POST /v1/admin/approvals/{id}/decide). Requires connectors:write.

Request body application/json required

FieldTypeDescription
reason requiredstring
rules requiredobject[] Ordered rules: the first that matches decides.
rules[].descriptionstring
rules[].failoverboolean
rules[].id requiredstring
rules[].matchobject Empty fields match anything.
rules[].match.bin_rangesobject[]
rules[].match.countriesstring[]
rules[].match.currenciesstring[]
rules[].match.max_amountstring
rules[].match.merchantsstring[] Account ids or metadata.merchant_id values.
rules[].match.methodsstring[]
rules[].match.min_amountstring
rules[].match.phone_prefixesstring[]
rules[].match.referenceobject The shape of a deposit reference (IO-1.1), on the reference upper-cased and without spaces: every condition set must hold. A lookup whose reference matches no rule goes to the default resolver.
rules[].match.typesstring[]
rules[].max_failoversinteger
rules[].targets requiredobject[]
rules[].targets[].psp_id requiredstring
rules[].targets[].weight requiredinteger Share among the eligible targets; 0 is a fallback only.

Responses

  • 202 Prepared; approval is the request to approve.
  • 400 Invalid rules (each problem listed) or one already pending. invalid_request

Request

curl -X POST "$KULPAY_URL/v1/admin/routing/versions" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "cards to Rota",
  "rules": [
    {
      "id": "cards",
      "match": {
        "methods": [
          "card"
        ]
      },
      "targets": [
        {
          "psp_id": "3KWPqtgPmeCaYplKVRxyJiriQOG",
          "weight": 100
        }
      ]
    }
  ]
}'
202 Response application/json
{
  "approval": {
    "created_at": "2026-10-10T22:15:44.905300473Z",
    "id": "3KWPqpGgl5DVi0nsBOTijTV7GWK",
    "kind": "routing.activate",
    "prepared_by": "key_accounts",
    "reason": "routing rules v1: cards to Rota",
    "status": "pending",
    "subject_id": "3KWPqrCkfWzVjEDju03YNzH5ntR",
    "tenant_id": "platform"
  },
  "message": "routing version prepared; a second person must approve it",
  "version": {
    "approval_id": "3KWPqpGgl5DVi0nsBOTijTV7GWK",
    "created_at": "2026-10-10T22:15:44.903403745Z",
    "created_by": "key_accounts",
    "id": "3KWPqrCkfWzVjEDju03YNzH5ntR",
    "reason": "cards to Rota",
    "status": "pending",
    "version": 1
  }
}
400 Error · invalid_request application/problem+json
{
  "code": "invalid_request",
  "detail": "at least one rule is required",
  "error": {
    "code": "invalid_request",
    "message": "at least one rule is required"
  },
  "status": 400,
  "title": "Bad Request",
  "type": "about:blank"
}

GET /v1/admin/routing/versions/{version}

One routing rules version

Parameters

NameInTypeDescription
version requiredpathinteger

Responses

  • 200 The version and its rules.
  • 404 No such version. not_found

Request

curl -X GET "$KULPAY_URL/v1/admin/routing/versions/{version}" \
  -H "Authorization: Bearer $KULPAY_KEY"
200 Response application/json
{
  "rules": {
    "rules": [
      {
        "id": "cards",
        "match": {
          "methods": [
            "card"
          ]
        },
        "targets": [
          {
            "psp_id": "3KWPqtgPmeCaYplKVRxyJiriQOG",
            "weight": 100
          }
        ]
      }
    ]
  },
  "version": {
    "activated_at": "2026-10-10T22:15:44.945952516Z",
    "approval_id": "3KWPqpGgl5DVi0nsBOTijTV7GWK",
    "created_at": "2026-10-10T22:15:44.903403745Z",
    "created_by": "key_accounts",
    "id": "3KWPqrCkfWzVjEDju03YNzH5ntR",
    "reason": "cards to Rota",
    "status": "active",
    "version": 1
  }
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no such routing version",
  "error": {
    "code": "not_found",
    "message": "no such routing version"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}

POST /v1/admin/routing/versions/{version}/rollback

Roll back to a routing rules version

Prepares a copy of an earlier version, through the same approval.

Parameters

NameInTypeDescription
version requiredpathinteger

Request body application/json required

FieldTypeDescription
reason requiredstring

Responses

  • 202 Prepared.
  • 404 No such version. not_found

Request

curl -X POST "$KULPAY_URL/v1/admin/routing/versions/{version}/rollback" \
  -H "Authorization: Bearer $KULPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "Pagamento de energia, outubro"
}'
202 Response application/json
{
  "approval": {
    "created_at": "2026-10-11T07:48:20Z",
    "id": "3Kh8Qx4H3yHpjhVVX645vFcRXuU",
    "kind": "routing.activate",
    "prepared_by": "3KWTqydMTGykEIsWuGEP683gqgc",
    "reason": "routing rules v8: Repor as regras da versão 5 após falhas no novo conector",
    "status": "pending",
    "subject_id": "3KymVIVtt0XsYONnValT0cGW9OX",
    "tenant_id": "platform"
  },
  "version": {
    "approval_id": "3Kh8Qx4H3yHpjhVVX645vFcRXuU",
    "based_on": 5,
    "created_at": "2026-10-11T07:48:20Z",
    "created_by": "3KWTqydMTGykEIsWuGEP683gqgc",
    "id": "3KymVIVtt0XsYONnValT0cGW9OX",
    "reason": "Repor as regras da versão 5 após falhas no novo conector",
    "status": "pending",
    "version": 8
  }
}
404 Error · not_found application/problem+json
{
  "code": "not_found",
  "detail": "no such routing version",
  "error": {
    "code": "not_found",
    "message": "no such routing version"
  },
  "status": 404,
  "title": "Not Found",
  "type": "about:blank"
}