# Charge Token

Charge a `pay` or `kya-pay` token as a seller agent or seller service.

Use this API after delivering a service, tool, or resource to collect payment from a buyer-issued token.

Before charging, seller services should validate tokens when requests are received and reject tokens that have already expired.

# Token Requirements

Only `pay` and `kya-pay` tokens can be charged. `kya` tokens cannot be charged.

# Charging After Expiration

To support long-running operations, Skyfire allows charges to be submitted for up to 24 hours after a token expires.

This grace period applies only when the token was accepted and validated before expiration. Seller services should not accept expired tokens from buyers, but may complete charges after expiration for services that began while the token was still valid.

For more information on pay token validation, see [here](https://docs.skyfire.xyz/reference/verify-and-extract-data-from-tokens#pay-tokens-typ--payjwt).

# Settlement

Charges are not settled immediately. Settlement may take up to 51 hours depending on token expiration and charging activity. For details, see [Settlement of Payments](https://docs.skyfire.xyz/reference/settlement-of-payments).

# OpenAPI definition

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "skyfire-agent-and-token-apis",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "https://api.skyfire.xyz"
    }
  ],
  "components": {
    "securitySchemes": {
      "sec0": {
        "type": "apiKey",
        "name": "skyfire-api-key",
        "in": "header"
      }
    }
  },
  "security": [
    {
      "sec0": []
    }
  ],
  "paths": {
    "/api/v1/tokens/charge": {
      "post": {
        "summary": "Charge Token",
        "description": "Charge a `pay` or `kya-pay` token as a seller agent or seller service.",
        "operationId": "charge-token",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "The complete, signed JWT string received from the buyer agent.\n"
                  },
                  "chargeAmount": {
                    "type": "string",
                    "description": "The amount to charge from the token. Required unless the seller service uses the `pay_per_use` pricing model, in which case Skyfire automatically charges the configured service price when omitted.\nTokens may be charged multiple times until their balance is exhausted.\nWhen provided, `chargeAmount` must:\n- Be greater than 0\n- Be less than or equal to the token's original `tokenAmount`\n- Be less than or equal to the token's remaining balance\n\nA charge that exceeds the remaining balance will fail."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "200",
            "content": {
              "application/json": {
                "examples": {
                  "Result": {
                    "value": "{\n    \"amountCharged\": \"0.01\",\n    \"remainingBalance\": \"0.01\"\n}"
                  }
                },
                "schema": {
                  "type": "object",
                  "properties": {
                    "amountCharged": {
                      "type": "string",
                      "example": "0.01",
                      "description": "The amount successfully charged from the token."
                    },
                    "remainingBalance": {
                      "type": "string",
                      "example": "0.01",
                      "description": "The amount remaining on the token after the charge is processed."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "400",
            "content": {
              "text/plain": {
                "examples": {
                  "Result": {
                    "value": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"utf-8\">\n    <title>Error</title>\n</head>\n\n<body>\n    <pre>Bad Request</pre>\n</body>\n\n</html>"
                  }
                }
              }
            }
          },
          "401": {
            "description": "401",
            "content": {
              "application/json": {
                "examples": {
                  "Result": {
                    "value": "{\n    \"code\": \"NOT_AUTHORIZED\",\n    \"message\": \"invalid or expired jwt\"\n}"
                  }
                },
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "example": "NOT_AUTHORIZED"
                    },
                    "message": {
                      "type": "string",
                      "example": "invalid or expired jwt"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "402",
            "content": {
              "application/json": {
                "examples": {
                  "Result": {
                    "value": "{\n    \"code\": \"PAYMENT_ERROR\",\n    \"message\": \"Invalid charge amount (must be less than token value)\"\n}"
                  }
                },
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "example": "PAYMENT_ERROR"
                    },
                    "message": {
                      "type": "string",
                      "example": "Invalid charge amount (must be less than token value)"
                    }
                  }
                }
              }
            }
          }
        },
        "x-readme": {
          "code-samples": [
            {
              "language": "node",
              "code": "  // JWT verification\n  const JWKS = createRemoteJWKSet(new URL(JWKS_URL));\n  try {\n    const { protectedHeader } = await jwtVerify(skyfireToken, JWKS, {\n      issuer: JWT_ISSUER,\n      audience: JWT_AUDIENCE,\n      algorithms: [JWT_ALGORITHM]\n    });\n    decodedHeader = protectedHeader;\n  } catch (err) {\n    console.error('Error while verifying token: ' + err);\n\n    if (err instanceof errors.JOSEError) {\n      return Response.json(\n        {\n          error: `Your JWT token is invalid`,\n          errorCode: err.code,\n          message: err.message\n        },\n        { status: 401 }\n      );\n    }\n\n    return Response.json(\n      { error: `Something went wrong while verifying your JWT token` },\n      { status: 401 }\n    );\n  }\n\n  let decodedToken;\n  try {\n    decodedToken = jwtDecode(skyfireToken);\n  } catch (err) {\n    console.error('Error while decoding token: ' + err);\n\n    return Response.json({ error: 'Invalid JWT token' }, { status: 401 });\n  }\n\n  /**\n   * Charge Token\n   */\n  if (decodedHeader.typ === 'kya-pay+JWT' || decodedHeader.typ === 'pay+JWT') {\n    try {\n      const response = await chargeToken(skyfireToken);\n      responseMessage.push(response);\n    } catch (err) {\n      console.error('Error while charging token: ' + err);\n\n      return Response.json(\n        {\n          error: `Couldn't charge your ${getTokenInfo(skyfireToken)} token`,\n          message: err instanceof Error ? err.message : 'Unknown error'\n        },\n        { status: 400 }\n      );\n    }\n  }\n\n  const chargeToken = async (skyfireToken: string) => {\n  let response;\n\n  try {\n    response = await fetch(`${SKYFIRE_API_URL}/api/v1/tokens/charge`, {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application/json',\n        'skyfire-api-key': <Skyfire Seller Agent API Key>,\n      },\n      body: JSON.stringify({\n        token: skyfireToken,\n        chargeAmount: AMOUNT_TO_CHARGE\n      })\n    });\n  } catch (err) {\n    console.error('Error while charging token: ' + err);\n    throw new Error(\n      err instanceof Error ? err.message : 'Unknown error while charging token'\n    );\n  }\n\n  const res: {\n    amountCharged: string;\n    remainingBalance: string;\n  } = await response.json();\n\n};\n"
            },
            {
              "language": "curl",
              "code": "curl --location 'https://api.skyfire.xyz/api/v1/tokens/charge' \\\n--header 'skyfire-api-key: 72fa67b7-961e-4535-8bf5-b8c44bf4a155' \\\n--header 'Content-Type: application/json' \\\n--data '{\n  \"token\": \"eyJhbGciOiJFUzI1NiIsImtpZCI6IjAiLCJ0eXAiOiJreWErcGF5K0pXVCJ9.eyJlbnYiOiJxYSIsImJ0ZyI6ImVmZDhhNmVlLWM3YjYtNGQyNi05ZTVjLTNiNWU5YzE4MGRmNiIsInNzaSI6IjM1ZGZjMjc3LTMwMWQtNDQ3Yy04YzQ5LTU5NGM5ZDU5MjMyZiIsImJpZCI6eyJiaXJ0aGRhdGUiOiIxOTc3LTA3LTE3Iiwic2t5ZmlyZUVtYWlsIjoiYW5raXRAc2t5ZmlyZS54eXoifSwiYWlkIjpudWxsLCJ2YWx1ZSI6IjIwMDAwIiwiYW1vdW50IjoiMC4wMiIsInNwcyI6IlBBWV9QRVJfVVNFIiwic3ByIjoiMC4wMiIsIm1uciI6MSwiY3VyIjoiVVNEIiwiaWF0IjoxNzUyODQzNjMwLCJpc3MiOiJodHRwczovL2FwcC1xYS5za3lmaXJlLnh5eiIsImp0aSI6IjU1YWVkZDQ1LTQzMTktNDM0MS1iYTJmLTkwODYyNDI2ODFiZiIsImF1ZCI6ImE4ZTQ5ZWVhLWFjN2ItNDFmMi1hYTc0LTg0ZmI4NmFkNDBiYyIsInN1YiI6IjQyNWEzOTRiLTVlYTUtNGY2NS05NTY1LTM2MGVhODU0NTY1YSIsImV4cCI6MTc1MjkxOTIwMH0.zHvsiQr1xbBFAkiQ_wOGM1aBFEbRxL14GvNHoDUCjkZN6TUN4h1NDrtvF02yqbRlDyoQcx4voaZbRIG0Ji32_g\",\n  \"chargeAmount\": \"0.01\"\n}'"
            },
            {
              "language": "node",
              "code": "var https = require('follow-redirects').https;\nvar fs = require('fs');\n\nvar options = {\n  'method': 'POST',\n  'hostname': 'api.skyfire.xyz',\n  'path': '/api/v1/tokens/charge',\n  'headers': {\n    'skyfire-api-key': '72fa67b7-961e-4535-8bf5-b8c44bf4a155',\n    'Content-Type': 'application/json'\n  },\n  'maxRedirects': 20\n};\n\nvar req = https.request(options, function (res) {\n  var chunks = [];\n\n  res.on(\"data\", function (chunk) {\n    chunks.push(chunk);\n  });\n\n  res.on(\"end\", function (chunk) {\n    var body = Buffer.concat(chunks);\n    console.log(body.toString());\n  });\n\n  res.on(\"error\", function (error) {\n    console.error(error);\n  });\n});\n\nvar postData = JSON.stringify({\n  \"token\": \"eyJhbGciOiJFUzI1NiIsImtpZCI6IjAiLCJ0eXAiOiJreWErcGF5K0pXVCJ9.eyJlbnYiOiJxYSIsImJ0ZyI6ImVmZDhhNmVlLWM3YjYtNGQyNi05ZTVjLTNiNWU5YzE4MGRmNiIsInNzaSI6IjM1ZGZjMjc3LTMwMWQtNDQ3Yy04YzQ5LTU5NGM5ZDU5MjMyZiIsImJpZCI6eyJiaXJ0aGRhdGUiOiIxOTc3LTA3LTE3Iiwic2t5ZmlyZUVtYWlsIjoiYW5raXRAc2t5ZmlyZS54eXoifSwiYWlkIjpudWxsLCJ2YWx1ZSI6IjIwMDAwIiwiYW1vdW50IjoiMC4wMiIsInNwcyI6IlBBWV9QRVJfVVNFIiwic3ByIjoiMC4wMiIsIm1uciI6MSwiY3VyIjoiVVNEIiwiaWF0IjoxNzUyODQzNjMwLCJpc3MiOiJodHRwczovL2FwcC1xYS5za3lmaXJlLnh5eiIsImp0aSI6IjU1YWVkZDQ1LTQzMTktNDM0MS1iYTJmLTkwODYyNDI2ODFiZiIsImF1ZCI6ImE4ZTQ5ZWVhLWFjN2ItNDFmMi1hYTc0LTg0ZmI4NmFkNDBiYyIsInN1YiI6IjQyNWEzOTRiLTVlYTUtNGY2NS05NTY1LTM2MGVhODU0NTY1YSIsImV4cCI6MTc1MjkxOTIwMH0.zHvsiQr1xbBFAkiQ_wOGM1aBFEbRxL14GvNHoDUCjkZN6TUN4h1NDrtvF02yqbRlDyoQcx4voaZbRIG0Ji32_g\",\n  \"chargeAmount\": \"0.01\"\n});\n\nreq.write(postData);\n\nreq.end();"
            },
            {
              "language": "python",
              "code": "import http.client\nimport json\n\nconn = http.client.HTTPSConnection(\"api.skyfire.xyz\")\npayload = json.dumps({\n  \"token\": \"eyJhbGciOiJFUzI1NiIsImtpZCI6IjAiLCJ0eXAiOiJreWErcGF5K0pXVCJ9.eyJlbnYiOiJxYSIsImJ0ZyI6ImVmZDhhNmVlLWM3YjYtNGQyNi05ZTVjLTNiNWU5YzE4MGRmNiIsInNzaSI6IjM1ZGZjMjc3LTMwMWQtNDQ3Yy04YzQ5LTU5NGM5ZDU5MjMyZiIsImJpZCI6eyJiaXJ0aGRhdGUiOiIxOTc3LTA3LTE3Iiwic2t5ZmlyZUVtYWlsIjoiYW5raXRAc2t5ZmlyZS54eXoifSwiYWlkIjpudWxsLCJ2YWx1ZSI6IjIwMDAwIiwiYW1vdW50IjoiMC4wMiIsInNwcyI6IlBBWV9QRVJfVVNFIiwic3ByIjoiMC4wMiIsIm1uciI6MSwiY3VyIjoiVVNEIiwiaWF0IjoxNzUyODQzNjMwLCJpc3MiOiJodHRwczovL2FwcC1xYS5za3lmaXJlLnh5eiIsImp0aSI6IjU1YWVkZDQ1LTQzMTktNDM0MS1iYTJmLTkwODYyNDI2ODFiZiIsImF1ZCI6ImE4ZTQ5ZWVhLWFjN2ItNDFmMi1hYTc0LTg0ZmI4NmFkNDBiYyIsInN1YiI6IjQyNWEzOTRiLTVlYTUtNGY2NS05NTY1LTM2MGVhODU0NTY1YSIsImV4cCI6MTc1MjkxOTIwMH0.zHvsiQr1xbBFAkiQ_wOGM1aBFEbRxL14GvNHoDUCjkZN6TUN4h1NDrtvF02yqbRlDyoQcx4voaZbRIG0Ji32_g\",\n  \"chargeAmount\": \"0.01\"\n})\nheaders = {\n  'skyfire-api-key': '72fa67b7-961e-4535-8bf5-b8c44bf4a155',\n  'Content-Type': 'application/json'\n}\nconn.request(\"POST\", \"/api/v1/tokens/charge\", payload, headers)\nres = conn.getresponse()\ndata = res.read()\nprint(data.decode(\"utf-8\"))"
            }
          ],
          "samples-languages": [
            "node",
            "curl",
            "python"
          ]
        }
      }
    }
  },
  "x-readme": {
    "headers": [
      {
        "key": "skyfire-api-key",
        "value": "<Your Skyfire Agent Account API Key>"
      }
    ],
    "explorer-enabled": false,
    "proxy-enabled": false
  },
  "x-readme-fauxas": true
}
```