{
  "openapi": "3.0.3",
  "info": {
    "title": "Zippy API",
    "version": "2.0.0",
    "description": "Integrate pay-ins, payouts and transaction queries for your merchant through the Zippy API.\n\n**API v2 preview.** Confirm endpoint availability and access requirements with Zippy before starting your integration.\n\n## Environments\n\nObtain your **merchantId**, **API key** and **integration sandbox URL** from Zippy. Set **ZIPPY_API_BASE_URL** to that URL and **ZIPPY_API_KEY** to your merchant secret. The example domain `api.example.com` is a placeholder.\n\nUse the sandbox to verify requests and payment outcomes. Use the production URL and credentials assigned to your merchant when your integration is ready. Keep credentials separate for each environment.\n\n## Authentication\n\nKeep your API key on your server. Never include it in browser code, redirect URLs or customer-facing pages.\n\n| Operations | Access requirements |\n| --- | --- |\n| GET bank catalog and transaction queries | Send an HMAC-SHA256 signature in `X-Zippy-Signature`, as described below. |\n| POST pay-ins and payouts | Confirm the required authentication with Zippy before sending creation requests. This preview does not yet specify POST authentication. |\n\nThe signature described below applies to the two GET operations. The **merchantId** identifies the merchant; it is not a secret credential.\n\n## Signing GET requests\n\nGenerate an HMAC-SHA256 signature with the API key as the secret. Send the lowercase hexadecimal signature in **X-Zippy-Signature**. Generate the signature on your server and keep the API key there.\n\n| Query | Exact string to sign |\n| --- | --- |\n| Transaction | `merchantId/transactionId` |\n| Banks | `merchantId/country` |\n\nUse UTF-8, the literal **/** separator and the URL values before percent-encoding. Send the country in uppercase. Do not send a body with GET requests.\n\nIn **Generate signature**, select Node.js, Go, Python, PHP or Java. Both examples sign the values used for bank and transaction queries. Each endpoint includes a complete request example in the same five languages.\n\nSet **ZIPPY_API_KEY** to your secret and **ZIPPY_API_BASE_URL** to the **integration sandbox** URL provided by Zippy.\n\n## Getting started\n\nStart with one pay-in in your integration sandbox. The sample values below are illustrative; use your merchant details and a payment method available for your country. Confirm POST authentication with Zippy before sending the request.\n\n### 1. Create a pay-in\n\nSend `POST /payins` with `Content-Type: application/json` and a **requestId** unique within your merchant. See [Create a pay-in](#tag/pay-ins/POST/payins) for complete request examples in Node.js, Go, Python, PHP and Java.\n\n```json\n{\n  \"requestId\": \"order-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"expiresAt\": \"2026-10-01T16:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"redirectUrls\": {\n    \"success\": \"https://shop.example.com/payment/success\",\n    \"failure\": \"https://shop.example.com/payment/failure\"\n  },\n  \"metadata\": {\n    \"orderNumber\": \"ZP-1001\"\n  }\n}\n```\n\n### 2. Keep the transaction identifier\n\nA successful creation response can look like this:\n\n```json\n{\n  \"requestId\": \"order-20261001-001\",\n  \"zippyId\": \"zp_20261001_0001\",\n  \"status\": \"processing\",\n  \"nextAction\": {\n    \"type\": \"redirect\",\n    \"url\": \"https://checkout.example.com/pay/zp_20261001_0001\"\n  }\n}\n```\n\nStore **requestId** and **zippyId** together. If **nextAction.type** is `redirect`, send the customer to **nextAction.url**. A browser redirect does not confirm payment.\n\n### 3. Confirm the payment outcome\n\nUse the returned **zippyId** as **transactionId** in `GET /merchants/merchant-demo/transactions/zp_20261001_0001`. Sign `merchant-demo/zp_20261001_0001` and send the signature in **X-Zippy-Signature**. See [Get a transaction](#tag/transactions/GET/merchants/{merchantId}/transactions/{transactionId}) for the complete signed request.\n\nA completed transaction response can look like this:\n\n```json\n{\n  \"requestId\": \"order-20261001-001\",\n  \"zippyId\": \"zp_20261001_0001\",\n  \"operation\": \"payin\",\n  \"status\": \"completed\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\"\n}\n```\n\nRead **status** to decide the payment outcome. If it is `processing`, the payment is still in progress. HTTP 200 confirms that the API request succeeded; it does not by itself confirm payment. See [Errors and recovery](#tag/errors-and-recovery) if you receive an error or no response.\n\n### Next: create a payout\n\nFor a bank payout, [list the banks](#tag/payouts/GET/merchants/{merchantId}/banks/{country}) available for your merchant and country first. Choose the bank, account type and customer document type from the same catalog option. Then create the payout and query its status using the returned **zippyId**.\n\n## Common concepts\n\n| Field | Purpose |\n| --- | --- |\n| requestId | Your request identifier. Unique within your merchant; preserve it for reconciliation. |\n| zippyId | Transaction identifier assigned by Zippy. Store it when creation returns a response. |\n| transactionId in a query URL | The **zippyId** you want to query, not your requestId. |\n| amount | String with exactly two decimal places, for example `\"1290.00\"`. |\n| country / currency | Uppercase two-letter ISO country code and three-letter ISO currency code. |\n| requestedAt / expiresAt | ISO 8601 timestamps with a time zone, for example `2026-10-01T12:00:00Z`. |\n\nFields use **camelCase**. Request objects reject unknown fields; put merchant-defined data in **metadata**. Keep account numbers as strings to preserve leading zeros.\n\nA duplicate **requestId** for your merchant returns HTTP **409**. This preview does not promise that repeating a creation request returns the original response. Follow [Errors and recovery](#tag/errors-and-recovery) when the outcome is uncertain.\n\n## Transaction states\n\n| status | Meaning | Your next step |\n| --- | --- | --- |\n| processing | The transaction is still in progress. | Query its current status using zippyId. |\n| completed | The payment is confirmed. | Record the completed outcome in your system. |\n| failed | The transaction failed. | Record the failed outcome; do not mark the payment as completed. |\n\nCreation returns **processing** or **failed**. Transaction queries can also return **completed**. Read the response body even when the HTTP status is **200**. A failed transaction and an HTTP error are different outcomes; [Errors and recovery](#tag/errors-and-recovery) explains how to handle each.\n",
    "x-scalar-sdk-installation": [
      {
        "lang": "Node.js",
        "description": "Node.js 20 or later, no dependencies.\n\nUse the merchant API key as the secret. Send the result in **X-Zippy-Signature**.\n\n```javascript\nimport { createHmac } from 'node:crypto';\n\nconst apiKey = process.env.ZIPPY_API_KEY;\nif (!apiKey) throw new Error('Set ZIPPY_API_KEY');\n\nfunction sign(merchantId, value) {\n  return createHmac('sha256', apiKey)\n    .update(merchantId + '/' + value, 'utf8')\n    .digest('hex');\n}\n\nconst merchantId = 'merchant-demo';\nconsole.log(sign(merchantId, 'CL')); // Banks\nconsole.log(sign(merchantId, 'zp_20261001_0001')); // Transaction\n```"
      },
      {
        "lang": "Go",
        "description": "Go, using the standard library.\n\nUse the merchant API key as the secret. Send the result in **X-Zippy-Signature**.\n\n```go\npackage main\n\nimport (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"os\"\n)\n\nfunc sign(apiKey, merchantID, value string) string {\n    mac := hmac.New(sha256.New, []byte(apiKey))\n    mac.Write([]byte(merchantID + \"/\" + value))\n    return hex.EncodeToString(mac.Sum(nil))\n}\n\nfunc main() {\n    apiKey := os.Getenv(\"ZIPPY_API_KEY\")\n    if apiKey == \"\" { panic(\"Set ZIPPY_API_KEY\") }\n    merchantID := \"merchant-demo\"\n    fmt.Println(sign(apiKey, merchantID, \"CL\")) // Banks\n    fmt.Println(sign(apiKey, merchantID, \"zp_20261001_0001\")) // Transaction\n}\n```"
      },
      {
        "lang": "Python",
        "description": "Python 3, using the standard library.\n\nUse the merchant API key as the secret. Send the result in **X-Zippy-Signature**.\n\n```python\nimport hashlib\nimport hmac\nimport os\n\napi_key = os.environ['ZIPPY_API_KEY']\n\ndef sign(merchant_id, value):\n    message = (merchant_id + '/' + value).encode('utf-8')\n    return hmac.new(api_key.encode('utf-8'), message, hashlib.sha256).hexdigest()\n\nmerchant_id = 'merchant-demo'\nprint(sign(merchant_id, 'CL')) # Banks\nprint(sign(merchant_id, 'zp_20261001_0001')) # Transaction\n```"
      },
      {
        "lang": "PHP",
        "description": "PHP with hash_hmac; HTTP examples require the cURL extension.\n\nUse the merchant API key as the secret. Send the result in **X-Zippy-Signature**.\n\n```php\n<?php\n$apiKey = getenv('ZIPPY_API_KEY');\nif ($apiKey === false || $apiKey === '') {\n    throw new RuntimeException('Set ZIPPY_API_KEY');\n}\n\nfunction sign(string $merchantId, string $value, string $apiKey): string {\n    return hash_hmac('sha256', $merchantId . '/' . $value, $apiKey);\n}\n\n$merchantId = 'merchant-demo';\necho sign($merchantId, 'CL', $apiKey) . PHP_EOL; // Banks\necho sign($merchantId, 'zp_20261001_0001', $apiKey) . PHP_EOL; // Transaction\n```"
      },
      {
        "lang": "Java",
        "description": "Java 17 or later, using the standard library. Save this example as ZippySignature.java.\n\nUse the merchant API key as the secret. Send the result in **X-Zippy-Signature**.\n\n```java\nimport java.nio.charset.StandardCharsets;\nimport java.util.HexFormat;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic class ZippySignature {\n    static String sign(String apiKey, String merchantId, String value) throws Exception {\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n        byte[] message = (merchantId + \"/\" + value).getBytes(StandardCharsets.UTF_8);\n        return HexFormat.of().formatHex(mac.doFinal(message));\n    }\n\n    public static void main(String[] args) throws Exception {\n        String apiKey = System.getenv(\"ZIPPY_API_KEY\");\n        if (apiKey == null || apiKey.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_KEY\");\n        String merchantId = \"merchant-demo\";\n        System.out.println(sign(apiKey, merchantId, \"CL\")); // Banks\n        System.out.println(sign(apiKey, merchantId, \"zp_20261001_0001\")); // Transaction\n    }\n}\n```"
      }
    ]
  },
  "servers": [
    {
      "url": "https://api.example.com",
      "description": "Illustrative URL. Use the integration sandbox URL provided by Zippy."
    }
  ],
  "tags": [
    {
      "name": "Pay-ins",
      "description": "Create a pay-in and direct the customer to complete the payment. Query the transaction to confirm the outcome."
    },
    {
      "name": "Payouts",
      "description": "Send funds to a bank account or wallet. For bank payouts, first query the bank catalog and use a compatible combination of bank, account type and customer document type."
    },
    {
      "name": "Transactions",
      "description": "Check the current status of a pay-in or payout using the zippyId returned during creation."
    },
    {
      "name": "Notifications",
      "description": "This API v2 preview does not yet define merchant payment notifications. Confirm notification availability and requirements with Zippy before relying on webhook delivery.\n\nUse [transaction queries](#tag/transactions) to check the outcome when you have a zippyId. A customer returning to your success or failure URL is not a payment confirmation."
    },
    {
      "name": "Errors and recovery",
      "description": "Read the HTTP status and the response body together. HTTP 200 with `status: failed` means the API request succeeded and the transaction failed. An HTTP error uses the error response documented for that operation.\n\n### HTTP errors\n\n| HTTP status | Meaning | What to do |\n| --- | --- | --- |\n| 400 | Required data is missing, unexpected fields are present, or a value has an invalid format. | Correct the request against the operation schema. |\n| 401 | The GET signature is missing or invalid. | Check your environment credentials, canonical string and lowercase hexadecimal signature. |\n| 403 | Your merchant cannot access the resource. | Check the merchantId and access assigned to your credentials. |\n| 404 | The transaction was not found for this merchant. | Check that the URL contains the returned zippyId and the correct merchantId. |\n| 409 | The requestId already exists for this merchant. | Reconcile the existing transaction. Do not change the requestId just to bypass the duplicate check. |\n| 422 | The supplied payment method or destination cannot be processed. | Check availability and, for bank payouts, the selected catalog combination. |\n| 502 / 503 | Zippy could not complete the API request or is temporarily unavailable. | For queries, try again later. For creation requests, reconcile the outcome before attempting another payment. |\n\nEach operation lists its applicable error statuses and JSON examples. Error bodies contain **statusCode** and **message**.\n\n### Timeouts and uncertain outcomes\n\nA creation timeout, interrupted connection or technical error does not prove that the payment failed.\n\n- If you have **zippyId**, [query the transaction](#tag/transactions) before attempting another payment.\n- If creation returned no **zippyId**, this preview has no lookup by **requestId**. Preserve the requestId and contact Zippy to reconcile the outcome.\n- Avoid automatically creating another payment with a new requestId while the original outcome is unknown.\n\nA **409** also requires reconciliation. This preview does not define automatic replay of the original creation response."
    },
    {
      "name": "Migration from API 1",
      "description": "Use this mapping when moving an existing integration to API v2. Start new integrations with [Getting started](#description/getting-started).\n\n| API 1 | Zippy API |\n| --- | --- |\n| POST /pay | POST /payins |\n| POST /payOut | POST /payouts |\n| POST /getPayOutParams | GET /merchants/{merchantId}/banks/{country} |\n| transactionId in the body | requestId |\n| payMethod / payoutMethod | paymentMethod |\n| name, email, documentId | customer and customer.document |\n| url_OK / url_ERROR | redirectUrls.success / redirectUrls.failure |\n| timestamp | requestedAt in ISO 8601 format |\n| payinExpirationTime | expiresAt in ISO 8601 format |\n| numAccount / typeAccountId | destination.accountNumber / destination.accountTypeId |\n\nCatalog identifiers are Zippy IDs and must come from the current bank list. The bank, document and currency examples illustrate the structure; availability depends on your merchant."
    }
  ],
  "paths": {
    "/payins": {
      "post": {
        "tags": [
          "Pay-ins"
        ],
        "summary": "Create a pay-in",
        "operationId": "createPayIn",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayInRequest"
              },
              "examples": {
                "bankTransfer": {
                  "summary": "Bank transfer pay-in",
                  "value": {
                    "requestId": "order-20261001-001",
                    "merchantId": "merchant-demo",
                    "country": "CL",
                    "currency": "CLP",
                    "amount": "1290.00",
                    "paymentMethod": "bankTransfer",
                    "requestedAt": "2026-10-01T12:00:00Z",
                    "expiresAt": "2026-10-01T16:00:00Z",
                    "customer": {
                      "name": "Camila Soto",
                      "email": "camila@example.com",
                      "phone": "+56912345678",
                      "document": {
                        "type": "RUT",
                        "number": "12345678K"
                      }
                    },
                    "redirectUrls": {
                      "success": "https://shop.example.com/payment/success",
                      "failure": "https://shop.example.com/payment/failure"
                    },
                    "metadata": {
                      "orderNumber": "ZP-1001"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transaction returns processing or failed. HTTP 200 does not confirm that the payment has completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayInResponse"
                },
                "examples": {
                  "processing": {
                    "summary": "Pay-in in progress",
                    "value": {
                      "requestId": "order-20261001-001",
                      "zippyId": "zp_20261001_0001",
                      "status": "processing",
                      "nextAction": {
                        "type": "redirect",
                        "url": "https://checkout.example.com/pay/zp_20261001_0001"
                      }
                    }
                  },
                  "failed": {
                    "summary": "Failed transaction",
                    "value": {
                      "requestId": "order-20261001-001",
                      "zippyId": "zp_20261001_0001",
                      "status": "failed"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableError"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableError"
          }
        },
        "description": "Create a pay-in with the customer details in customer. If nextAction.type = redirect, send the customer to nextAction.url to complete the payment.\n\nQuery the transaction to confirm the outcome. A browser redirect alone does not confirm payment. Available payment methods depend on the country and your merchant configuration.",
        "x-codeSamples": [
          {
            "label": "Node.js",
            "lang": "javascript",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "const baseUrl = process.env.ZIPPY_API_BASE_URL;\nif (!baseUrl) throw new Error('Set ZIPPY_API_BASE_URL');\nconst body = {\n  \"requestId\": \"order-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"expiresAt\": \"2026-10-01T16:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"redirectUrls\": {\n    \"success\": \"https://shop.example.com/payment/success\",\n    \"failure\": \"https://shop.example.com/payment/failure\"\n  },\n  \"metadata\": {\n    \"orderNumber\": \"ZP-1001\"\n  }\n};\n\nconst response = await fetch(baseUrl.replace(/\\/$/, '') + '/payins', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(body),\n});\nconsole.log(response.status, await response.text());\n"
          },
          {
            "label": "Go",
            "lang": "go",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "package main\n\nimport (\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"strings\"\n    \"time\"\n)\n\nfunc main() {\n    baseURL := os.Getenv(\"ZIPPY_API_BASE_URL\")\n    if baseURL == \"\" { panic(\"Set ZIPPY_API_BASE_URL\") }\n    payload := `{\n  \"requestId\": \"order-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"expiresAt\": \"2026-10-01T16:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"redirectUrls\": {\n    \"success\": \"https://shop.example.com/payment/success\",\n    \"failure\": \"https://shop.example.com/payment/failure\"\n  },\n  \"metadata\": {\n    \"orderNumber\": \"ZP-1001\"\n  }\n}`\n    req, err := http.NewRequest(http.MethodPost, strings.TrimRight(baseURL, \"/\") + \"/payins\", strings.NewReader(payload))\n    if err != nil { panic(err) }\n    req.Header.Set(\"Content-Type\", \"application/json\")\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err != nil { panic(err) }\n    defer resp.Body.Close()\n    body, err := io.ReadAll(resp.Body)\n    if err != nil { panic(err) }\n    fmt.Println(resp.StatusCode, string(body))\n}\n"
          },
          {
            "label": "Python",
            "lang": "python",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "import os\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError\n\nbase_url = os.environ['ZIPPY_API_BASE_URL'].rstrip('/')\npayload = r'''{\n  \"requestId\": \"order-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"expiresAt\": \"2026-10-01T16:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"redirectUrls\": {\n    \"success\": \"https://shop.example.com/payment/success\",\n    \"failure\": \"https://shop.example.com/payment/failure\"\n  },\n  \"metadata\": {\n    \"orderNumber\": \"ZP-1001\"\n  }\n}'''.encode('utf-8')\nrequest = Request(base_url + '/payins', data=payload,\n    headers={'Content-Type': 'application/json'}, method='POST')\ntry:\n    with urlopen(request, timeout=30) as response:\n        print(response.status, response.read().decode('utf-8'))\nexcept HTTPError as error:\n    print(error.code, error.read().decode('utf-8'))\n"
          },
          {
            "label": "PHP",
            "lang": "php",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "<?php\n$baseUrl = getenv('ZIPPY_API_BASE_URL');\nif ($baseUrl === false || $baseUrl === '') throw new RuntimeException('Set ZIPPY_API_BASE_URL');\n$payload = <<<'JSON'\n{\n  \"requestId\": \"order-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"1290.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"expiresAt\": \"2026-10-01T16:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"redirectUrls\": {\n    \"success\": \"https://shop.example.com/payment/success\",\n    \"failure\": \"https://shop.example.com/payment/failure\"\n  },\n  \"metadata\": {\n    \"orderNumber\": \"ZP-1001\"\n  }\n}\nJSON;\n$curl = curl_init(rtrim($baseUrl, '/') . '/payins');\ncurl_setopt_array($curl, [\n    CURLOPT_POST => true,\n    CURLOPT_POSTFIELDS => $payload,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n]);\n$body = curl_exec($curl);\nif ($body === false) throw new RuntimeException(curl_error($curl));\n$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);\ncurl_close($curl);\necho $status . PHP_EOL . $body . PHP_EOL;\n"
          },
          {
            "label": "Java",
            "lang": "java",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "import java.nio.charset.StandardCharsets;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\npublic class ZippyExample {\n    public static void main(String[] args) throws Exception {\n        String baseUrl = System.getenv(\"ZIPPY_API_BASE_URL\");\n        if (baseUrl == null || baseUrl.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_BASE_URL\");\n        String payload = \"\"\"\n            {\n              \"requestId\": \"order-20261001-001\",\n              \"merchantId\": \"merchant-demo\",\n              \"country\": \"CL\",\n              \"currency\": \"CLP\",\n              \"amount\": \"1290.00\",\n              \"paymentMethod\": \"bankTransfer\",\n              \"requestedAt\": \"2026-10-01T12:00:00Z\",\n              \"expiresAt\": \"2026-10-01T16:00:00Z\",\n              \"customer\": {\n                \"name\": \"Camila Soto\",\n                \"email\": \"camila@example.com\",\n                \"phone\": \"+56912345678\",\n                \"document\": {\n                  \"type\": \"RUT\",\n                  \"number\": \"12345678K\"\n                }\n              },\n              \"redirectUrls\": {\n                \"success\": \"https://shop.example.com/payment/success\",\n                \"failure\": \"https://shop.example.com/payment/failure\"\n              },\n              \"metadata\": {\n                \"orderNumber\": \"ZP-1001\"\n              }\n            }\n            \"\"\";\n        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl.replaceAll(\"/+$\", \"\") + \"/payins\"))\n            .timeout(Duration.ofSeconds(30))\n            .header(\"Content-Type\", \"application/json\")\n            .POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8)).build();\n        HttpResponse<String> response = HttpClient.newHttpClient()\n            .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));\n        System.out.println(response.statusCode());\n        System.out.println(response.body());\n    }\n}\n"
          }
        ]
      }
    },
    "/merchants/{merchantId}/banks/{country}": {
      "get": {
        "tags": [
          "Payouts"
        ],
        "summary": "List merchant banks",
        "description": "Before creating a bank payout, query the banks available for your merchant and country. Include both values in the URL and generate the signature from merchantId/country.\n\nThe catalog uses Zippy identifiers. Each bank lists compatible combinations of account type and customer document type. Choose bankId, accountTypeId and customer.document.type from the same catalog option when creating a payout.\n\nAn empty banks array means that no banks are available for this merchant and country. The banks and IDs in the examples are illustrative.",
        "operationId": "listMerchantBanks",
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          },
          {
            "name": "country",
            "in": "path",
            "required": true,
            "description": "Country to query. This value is included in the signature.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Z]{2}$"
            },
            "example": "CL"
          }
        ],
        "responses": {
          "200": {
            "description": "Banks and compatible options for the merchant and country.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BankCatalog"
                },
                "examples": {
                  "available": {
                    "summary": "Available bank",
                    "value": {
                      "country": "CL",
                      "banks": [
                        {
                          "bankId": "7c0c3410-4a64-4ae4-8c12-8e0249f2a001",
                          "name": "Example Bank",
                          "options": [
                            {
                              "accountType": {
                                "id": "b2b69246-bcd1-44b3-83d0-17a28cbcd001",
                                "name": "Checking account"
                              },
                              "customerDocumentTypes": [
                                {
                                  "id": "7289cd02-06fe-4a36-903c-8aad134dd001",
                                  "name": "RUT"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  },
                  "empty": {
                    "summary": "No available banks",
                    "value": {
                      "country": "CL",
                      "banks": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableError"
          }
        },
        "security": [
          {
            "MerchantSignature": []
          }
        ],
        "x-codeSamples": [
          {
            "label": "Node.js",
            "lang": "javascript",
            "source": "import { createHmac } from 'node:crypto';\n\nconst apiKey = process.env.ZIPPY_API_KEY;\nif (!apiKey) throw new Error('Set ZIPPY_API_KEY');\n\nfunction sign(merchantId, value) {\n  return createHmac('sha256', apiKey)\n    .update(merchantId + '/' + value, 'utf8')\n    .digest('hex');\n}\n\nconst merchantId = 'merchant-demo';\nconst country = 'CL';\nconst baseUrl = process.env.ZIPPY_API_BASE_URL;\nif (!baseUrl) throw new Error('Set ZIPPY_API_BASE_URL');\nconst path = '/merchants/' + encodeURIComponent(merchantId)\n  + '/banks/' + encodeURIComponent(country);\nconst response = await fetch(baseUrl.replace(/\\/$/, '') + path, {\n  headers: { 'X-Zippy-Signature': sign(merchantId, country) },\n});\nconsole.log(response.status, await response.text());\n"
          },
          {
            "label": "Go",
            "lang": "go",
            "source": "package main\n\nimport (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"net/url\"\n    \"os\"\n    \"strings\"\n    \"time\"\n)\n\nfunc sign(apiKey, merchantID, value string) string {\n    mac := hmac.New(sha256.New, []byte(apiKey))\n    mac.Write([]byte(merchantID + \"/\" + value))\n    return hex.EncodeToString(mac.Sum(nil))\n}\n\nfunc main() {\n    apiKey := os.Getenv(\"ZIPPY_API_KEY\")\n    baseURL := os.Getenv(\"ZIPPY_API_BASE_URL\")\n    if apiKey == \"\" || baseURL == \"\" { panic(\"Set ZIPPY_API_KEY and ZIPPY_API_BASE_URL\") }\n    merchantID := \"merchant-demo\"\n    country := \"CL\"\n    path := \"/merchants/\" + url.PathEscape(merchantID) + \"/banks/\" + url.PathEscape(country)\n    req, err := http.NewRequest(http.MethodGet, strings.TrimRight(baseURL, \"/\") + path, nil)\n    if err != nil { panic(err) }\n    req.Header.Set(\"X-Zippy-Signature\", sign(apiKey, merchantID, country))\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err != nil { panic(err) }\n    defer resp.Body.Close()\n    body, err := io.ReadAll(resp.Body)\n    if err != nil { panic(err) }\n    fmt.Println(resp.StatusCode, string(body))\n}\n"
          },
          {
            "label": "Python",
            "lang": "python",
            "source": "import hashlib\nimport hmac\nimport os\n\napi_key = os.environ['ZIPPY_API_KEY']\n\ndef sign(merchant_id, value):\n    message = (merchant_id + '/' + value).encode('utf-8')\n    return hmac.new(api_key.encode('utf-8'), message, hashlib.sha256).hexdigest()\n\nfrom urllib.parse import quote\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError\n\nmerchant_id = 'merchant-demo'\ncountry = 'CL'\nbase_url = os.environ['ZIPPY_API_BASE_URL'].rstrip('/')\npath = '/merchants/' + quote(merchant_id, safe='') + '/banks/' + quote(country, safe='')\nrequest = Request(base_url + path, headers={\n    'X-Zippy-Signature': sign(merchant_id, country),\n}, method='GET')\ntry:\n    with urlopen(request, timeout=30) as response:\n        print(response.status, response.read().decode('utf-8'))\nexcept HTTPError as error:\n    print(error.code, error.read().decode('utf-8'))\n"
          },
          {
            "label": "PHP",
            "lang": "php",
            "source": "<?php\n$apiKey = getenv('ZIPPY_API_KEY');\nif ($apiKey === false || $apiKey === '') {\n    throw new RuntimeException('Set ZIPPY_API_KEY');\n}\n\nfunction sign(string $merchantId, string $value, string $apiKey): string {\n    return hash_hmac('sha256', $merchantId . '/' . $value, $apiKey);\n}\n\n$merchantId = 'merchant-demo';\n$country = 'CL';\n$baseUrl = getenv('ZIPPY_API_BASE_URL');\nif ($baseUrl === false || $baseUrl === '') throw new RuntimeException('Set ZIPPY_API_BASE_URL');\n$path = '/merchants/' . rawurlencode($merchantId) . '/banks/' . rawurlencode($country);\n$curl = curl_init(rtrim($baseUrl, '/') . $path);\ncurl_setopt_array($curl, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => ['X-Zippy-Signature: ' . sign($merchantId, $country, $apiKey)],\n]);\n$body = curl_exec($curl);\nif ($body === false) throw new RuntimeException(curl_error($curl));\n$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);\ncurl_close($curl);\necho $status . PHP_EOL . $body . PHP_EOL;\n"
          },
          {
            "label": "Java",
            "lang": "java",
            "source": "import java.nio.charset.StandardCharsets;\nimport java.util.HexFormat;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\npublic class ZippyExample {\n    static String sign(String apiKey, String merchantId, String value) throws Exception {\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n        byte[] message = (merchantId + \"/\" + value).getBytes(StandardCharsets.UTF_8);\n        return HexFormat.of().formatHex(mac.doFinal(message));\n    }\n\n    static String encode(String value) {\n        return URLEncoder.encode(value, StandardCharsets.UTF_8).replace(\"+\", \"%20\");\n    }\n\n    public static void main(String[] args) throws Exception {\n        String apiKey = System.getenv(\"ZIPPY_API_KEY\");\n        if (apiKey == null || apiKey.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_KEY\");\n        String baseUrl = System.getenv(\"ZIPPY_API_BASE_URL\");\n        if (baseUrl == null || baseUrl.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_BASE_URL\");\n        String merchantId = \"merchant-demo\";\n        String country = \"CL\";\n        String path = \"/merchants/\" + encode(merchantId) + \"/banks/\" + encode(country);\n        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl.replaceAll(\"/+$\", \"\") + path))\n            .timeout(Duration.ofSeconds(30))\n            .header(\"X-Zippy-Signature\", sign(apiKey, merchantId, country))\n            .GET().build();\n        HttpResponse<String> response = HttpClient.newHttpClient()\n            .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));\n        System.out.println(response.statusCode());\n        System.out.println(response.body());\n    }\n}\n"
          }
        ]
      }
    },
    "/payouts": {
      "post": {
        "tags": [
          "Payouts"
        ],
        "summary": "Create a payout",
        "operationId": "createPayout",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayoutRequest"
              },
              "examples": {
                "bankTransfer": {
                  "summary": "Bank transfer",
                  "value": {
                    "requestId": "withdrawal-20261001-001",
                    "merchantId": "merchant-demo",
                    "country": "CL",
                    "currency": "CLP",
                    "amount": "15000.00",
                    "paymentMethod": "bankTransfer",
                    "requestedAt": "2026-10-01T12:00:00Z",
                    "customer": {
                      "name": "Camila Soto",
                      "email": "camila@example.com",
                      "phone": "+56912345678",
                      "document": {
                        "type": "7289cd02-06fe-4a36-903c-8aad134dd001",
                        "number": "12345678K"
                      }
                    },
                    "destination": {
                      "type": "bankAccount",
                      "bankId": "7c0c3410-4a64-4ae4-8c12-8e0249f2a001",
                      "accountTypeId": "b2b69246-bcd1-44b3-83d0-17a28cbcd001",
                      "accountNumber": "00123456789"
                    },
                    "metadata": {
                      "withdrawalNumber": "WD-1001"
                    }
                  }
                },
                "wallet": {
                  "summary": "Wallet identified by phone",
                  "value": {
                    "requestId": "withdrawal-20261001-002",
                    "merchantId": "merchant-demo",
                    "country": "CL",
                    "currency": "CLP",
                    "amount": "15000.00",
                    "paymentMethod": "wallet",
                    "requestedAt": "2026-10-01T12:00:00Z",
                    "customer": {
                      "name": "Camila Soto",
                      "email": "camila@example.com",
                      "phone": "+56912345678",
                      "document": {
                        "type": "RUT",
                        "number": "12345678K"
                      }
                    },
                    "destination": {
                      "type": "wallet",
                      "identifierType": "phone",
                      "identifier": "+56912345678"
                    },
                    "metadata": {
                      "withdrawalNumber": "WD-1001"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transaction returns processing or failed. HTTP 200 does not confirm that the payment has completed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayoutResponse"
                },
                "examples": {
                  "processing": {
                    "summary": "Payout in progress",
                    "value": {
                      "requestId": "withdrawal-20261001-001",
                      "zippyId": "zp_20261001_0002",
                      "status": "processing"
                    }
                  },
                  "failed": {
                    "summary": "Failed transaction",
                    "value": {
                      "requestId": "withdrawal-20261001-001",
                      "zippyId": "zp_20261001_0002",
                      "status": "failed"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableError"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableError"
          }
        },
        "description": "Send funds to a beneficiary.\n\n- bankTransfer requires destination.type = bankAccount. First list the banks for your merchant and country, then select bankId, accountTypeId and customer.document.type from the same catalog option.\n- wallet requires destination.type = wallet, identifierType = phone and customer.phone.\n\nSend amounts as strings with two decimal places. Keep account numbers as strings to preserve leading zeros. Store the returned zippyId and query the transaction to confirm its outcome.",
        "x-codeSamples": [
          {
            "label": "Node.js",
            "lang": "javascript",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "const baseUrl = process.env.ZIPPY_API_BASE_URL;\nif (!baseUrl) throw new Error('Set ZIPPY_API_BASE_URL');\nconst body = {\n  \"requestId\": \"withdrawal-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"7289cd02-06fe-4a36-903c-8aad134dd001\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"bankAccount\",\n    \"bankId\": \"7c0c3410-4a64-4ae4-8c12-8e0249f2a001\",\n    \"accountTypeId\": \"b2b69246-bcd1-44b3-83d0-17a28cbcd001\",\n    \"accountNumber\": \"00123456789\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n};\n\nconst response = await fetch(baseUrl.replace(/\\/$/, '') + '/payouts', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(body),\n});\nconsole.log(response.status, await response.text());\n"
          },
          {
            "label": "Node.js",
            "lang": "javascript",
            "example": "wallet",
            "contentType": "application/json",
            "source": "const baseUrl = process.env.ZIPPY_API_BASE_URL;\nif (!baseUrl) throw new Error('Set ZIPPY_API_BASE_URL');\nconst body = {\n  \"requestId\": \"withdrawal-20261001-002\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"wallet\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"wallet\",\n    \"identifierType\": \"phone\",\n    \"identifier\": \"+56912345678\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n};\n\nconst response = await fetch(baseUrl.replace(/\\/$/, '') + '/payouts', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify(body),\n});\nconsole.log(response.status, await response.text());\n"
          },
          {
            "label": "Go",
            "lang": "go",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "package main\n\nimport (\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"strings\"\n    \"time\"\n)\n\nfunc main() {\n    baseURL := os.Getenv(\"ZIPPY_API_BASE_URL\")\n    if baseURL == \"\" { panic(\"Set ZIPPY_API_BASE_URL\") }\n    payload := `{\n  \"requestId\": \"withdrawal-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"7289cd02-06fe-4a36-903c-8aad134dd001\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"bankAccount\",\n    \"bankId\": \"7c0c3410-4a64-4ae4-8c12-8e0249f2a001\",\n    \"accountTypeId\": \"b2b69246-bcd1-44b3-83d0-17a28cbcd001\",\n    \"accountNumber\": \"00123456789\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}`\n    req, err := http.NewRequest(http.MethodPost, strings.TrimRight(baseURL, \"/\") + \"/payouts\", strings.NewReader(payload))\n    if err != nil { panic(err) }\n    req.Header.Set(\"Content-Type\", \"application/json\")\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err != nil { panic(err) }\n    defer resp.Body.Close()\n    body, err := io.ReadAll(resp.Body)\n    if err != nil { panic(err) }\n    fmt.Println(resp.StatusCode, string(body))\n}\n"
          },
          {
            "label": "Go",
            "lang": "go",
            "example": "wallet",
            "contentType": "application/json",
            "source": "package main\n\nimport (\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"strings\"\n    \"time\"\n)\n\nfunc main() {\n    baseURL := os.Getenv(\"ZIPPY_API_BASE_URL\")\n    if baseURL == \"\" { panic(\"Set ZIPPY_API_BASE_URL\") }\n    payload := `{\n  \"requestId\": \"withdrawal-20261001-002\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"wallet\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"wallet\",\n    \"identifierType\": \"phone\",\n    \"identifier\": \"+56912345678\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}`\n    req, err := http.NewRequest(http.MethodPost, strings.TrimRight(baseURL, \"/\") + \"/payouts\", strings.NewReader(payload))\n    if err != nil { panic(err) }\n    req.Header.Set(\"Content-Type\", \"application/json\")\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err != nil { panic(err) }\n    defer resp.Body.Close()\n    body, err := io.ReadAll(resp.Body)\n    if err != nil { panic(err) }\n    fmt.Println(resp.StatusCode, string(body))\n}\n"
          },
          {
            "label": "Python",
            "lang": "python",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "import os\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError\n\nbase_url = os.environ['ZIPPY_API_BASE_URL'].rstrip('/')\npayload = r'''{\n  \"requestId\": \"withdrawal-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"7289cd02-06fe-4a36-903c-8aad134dd001\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"bankAccount\",\n    \"bankId\": \"7c0c3410-4a64-4ae4-8c12-8e0249f2a001\",\n    \"accountTypeId\": \"b2b69246-bcd1-44b3-83d0-17a28cbcd001\",\n    \"accountNumber\": \"00123456789\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}'''.encode('utf-8')\nrequest = Request(base_url + '/payouts', data=payload,\n    headers={'Content-Type': 'application/json'}, method='POST')\ntry:\n    with urlopen(request, timeout=30) as response:\n        print(response.status, response.read().decode('utf-8'))\nexcept HTTPError as error:\n    print(error.code, error.read().decode('utf-8'))\n"
          },
          {
            "label": "Python",
            "lang": "python",
            "example": "wallet",
            "contentType": "application/json",
            "source": "import os\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError\n\nbase_url = os.environ['ZIPPY_API_BASE_URL'].rstrip('/')\npayload = r'''{\n  \"requestId\": \"withdrawal-20261001-002\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"wallet\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"wallet\",\n    \"identifierType\": \"phone\",\n    \"identifier\": \"+56912345678\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}'''.encode('utf-8')\nrequest = Request(base_url + '/payouts', data=payload,\n    headers={'Content-Type': 'application/json'}, method='POST')\ntry:\n    with urlopen(request, timeout=30) as response:\n        print(response.status, response.read().decode('utf-8'))\nexcept HTTPError as error:\n    print(error.code, error.read().decode('utf-8'))\n"
          },
          {
            "label": "PHP",
            "lang": "php",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "<?php\n$baseUrl = getenv('ZIPPY_API_BASE_URL');\nif ($baseUrl === false || $baseUrl === '') throw new RuntimeException('Set ZIPPY_API_BASE_URL');\n$payload = <<<'JSON'\n{\n  \"requestId\": \"withdrawal-20261001-001\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"bankTransfer\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"7289cd02-06fe-4a36-903c-8aad134dd001\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"bankAccount\",\n    \"bankId\": \"7c0c3410-4a64-4ae4-8c12-8e0249f2a001\",\n    \"accountTypeId\": \"b2b69246-bcd1-44b3-83d0-17a28cbcd001\",\n    \"accountNumber\": \"00123456789\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}\nJSON;\n$curl = curl_init(rtrim($baseUrl, '/') . '/payouts');\ncurl_setopt_array($curl, [\n    CURLOPT_POST => true,\n    CURLOPT_POSTFIELDS => $payload,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n]);\n$body = curl_exec($curl);\nif ($body === false) throw new RuntimeException(curl_error($curl));\n$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);\ncurl_close($curl);\necho $status . PHP_EOL . $body . PHP_EOL;\n"
          },
          {
            "label": "PHP",
            "lang": "php",
            "example": "wallet",
            "contentType": "application/json",
            "source": "<?php\n$baseUrl = getenv('ZIPPY_API_BASE_URL');\nif ($baseUrl === false || $baseUrl === '') throw new RuntimeException('Set ZIPPY_API_BASE_URL');\n$payload = <<<'JSON'\n{\n  \"requestId\": \"withdrawal-20261001-002\",\n  \"merchantId\": \"merchant-demo\",\n  \"country\": \"CL\",\n  \"currency\": \"CLP\",\n  \"amount\": \"15000.00\",\n  \"paymentMethod\": \"wallet\",\n  \"requestedAt\": \"2026-10-01T12:00:00Z\",\n  \"customer\": {\n    \"name\": \"Camila Soto\",\n    \"email\": \"camila@example.com\",\n    \"phone\": \"+56912345678\",\n    \"document\": {\n      \"type\": \"RUT\",\n      \"number\": \"12345678K\"\n    }\n  },\n  \"destination\": {\n    \"type\": \"wallet\",\n    \"identifierType\": \"phone\",\n    \"identifier\": \"+56912345678\"\n  },\n  \"metadata\": {\n    \"withdrawalNumber\": \"WD-1001\"\n  }\n}\nJSON;\n$curl = curl_init(rtrim($baseUrl, '/') . '/payouts');\ncurl_setopt_array($curl, [\n    CURLOPT_POST => true,\n    CURLOPT_POSTFIELDS => $payload,\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],\n]);\n$body = curl_exec($curl);\nif ($body === false) throw new RuntimeException(curl_error($curl));\n$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);\ncurl_close($curl);\necho $status . PHP_EOL . $body . PHP_EOL;\n"
          },
          {
            "label": "Java",
            "lang": "java",
            "example": "bankTransfer",
            "contentType": "application/json",
            "source": "import java.nio.charset.StandardCharsets;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\npublic class ZippyExample {\n    public static void main(String[] args) throws Exception {\n        String baseUrl = System.getenv(\"ZIPPY_API_BASE_URL\");\n        if (baseUrl == null || baseUrl.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_BASE_URL\");\n        String payload = \"\"\"\n            {\n              \"requestId\": \"withdrawal-20261001-001\",\n              \"merchantId\": \"merchant-demo\",\n              \"country\": \"CL\",\n              \"currency\": \"CLP\",\n              \"amount\": \"15000.00\",\n              \"paymentMethod\": \"bankTransfer\",\n              \"requestedAt\": \"2026-10-01T12:00:00Z\",\n              \"customer\": {\n                \"name\": \"Camila Soto\",\n                \"email\": \"camila@example.com\",\n                \"phone\": \"+56912345678\",\n                \"document\": {\n                  \"type\": \"7289cd02-06fe-4a36-903c-8aad134dd001\",\n                  \"number\": \"12345678K\"\n                }\n              },\n              \"destination\": {\n                \"type\": \"bankAccount\",\n                \"bankId\": \"7c0c3410-4a64-4ae4-8c12-8e0249f2a001\",\n                \"accountTypeId\": \"b2b69246-bcd1-44b3-83d0-17a28cbcd001\",\n                \"accountNumber\": \"00123456789\"\n              },\n              \"metadata\": {\n                \"withdrawalNumber\": \"WD-1001\"\n              }\n            }\n            \"\"\";\n        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl.replaceAll(\"/+$\", \"\") + \"/payouts\"))\n            .timeout(Duration.ofSeconds(30))\n            .header(\"Content-Type\", \"application/json\")\n            .POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8)).build();\n        HttpResponse<String> response = HttpClient.newHttpClient()\n            .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));\n        System.out.println(response.statusCode());\n        System.out.println(response.body());\n    }\n}\n"
          },
          {
            "label": "Java",
            "lang": "java",
            "example": "wallet",
            "contentType": "application/json",
            "source": "import java.nio.charset.StandardCharsets;\nimport java.net.URI;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\npublic class ZippyExample {\n    public static void main(String[] args) throws Exception {\n        String baseUrl = System.getenv(\"ZIPPY_API_BASE_URL\");\n        if (baseUrl == null || baseUrl.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_BASE_URL\");\n        String payload = \"\"\"\n            {\n              \"requestId\": \"withdrawal-20261001-002\",\n              \"merchantId\": \"merchant-demo\",\n              \"country\": \"CL\",\n              \"currency\": \"CLP\",\n              \"amount\": \"15000.00\",\n              \"paymentMethod\": \"wallet\",\n              \"requestedAt\": \"2026-10-01T12:00:00Z\",\n              \"customer\": {\n                \"name\": \"Camila Soto\",\n                \"email\": \"camila@example.com\",\n                \"phone\": \"+56912345678\",\n                \"document\": {\n                  \"type\": \"RUT\",\n                  \"number\": \"12345678K\"\n                }\n              },\n              \"destination\": {\n                \"type\": \"wallet\",\n                \"identifierType\": \"phone\",\n                \"identifier\": \"+56912345678\"\n              },\n              \"metadata\": {\n                \"withdrawalNumber\": \"WD-1001\"\n              }\n            }\n            \"\"\";\n        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl.replaceAll(\"/+$\", \"\") + \"/payouts\"))\n            .timeout(Duration.ofSeconds(30))\n            .header(\"Content-Type\", \"application/json\")\n            .POST(HttpRequest.BodyPublishers.ofString(payload, StandardCharsets.UTF_8)).build();\n        HttpResponse<String> response = HttpClient.newHttpClient()\n            .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));\n        System.out.println(response.statusCode());\n        System.out.println(response.body());\n    }\n}\n"
          }
        ]
      }
    },
    "/merchants/{merchantId}/transactions/{transactionId}": {
      "get": {
        "tags": [
          "Transactions"
        ],
        "summary": "Get a transaction",
        "description": "Include merchantId and transactionId in the URL and generate the signature from merchantId/transactionId. transactionId is the Zippy transaction identifier.\n\nHTTP 200 confirms that the query succeeded. Read status for the payment outcome: processing, completed or failed.",
        "operationId": "getMerchantTransaction",
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          },
          {
            "name": "transactionId",
            "in": "path",
            "required": true,
            "description": "The zippyId returned when the transaction was created. This value is included in the signature.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "example": "zp_20261001_0001"
          }
        ],
        "responses": {
          "200": {
            "description": "Merchant transaction summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionSummary"
                },
                "examples": {
                  "completed": {
                    "summary": "Completed payment",
                    "value": {
                      "requestId": "order-20261001-001",
                      "zippyId": "zp_20261001_0001",
                      "operation": "payin",
                      "status": "completed",
                      "country": "CL",
                      "currency": "CLP",
                      "amount": "1290.00"
                    }
                  },
                  "processing": {
                    "summary": "Payment in progress",
                    "value": {
                      "requestId": "order-20261001-001",
                      "zippyId": "zp_20261001_0001",
                      "operation": "payin",
                      "status": "processing",
                      "country": "CL",
                      "currency": "CLP",
                      "amount": "1290.00"
                    }
                  },
                  "failed": {
                    "summary": "Failed payment",
                    "value": {
                      "requestId": "order-20261001-001",
                      "zippyId": "zp_20261001_0001",
                      "operation": "payin",
                      "status": "failed",
                      "country": "CL",
                      "currency": "CLP",
                      "amount": "1290.00"
                    }
                  }
                }
              }
            },
            "headers": {
              "Cache-Control": {
                "description": "Returns the current status. Do not cache this response.",
                "schema": {
                  "type": "string",
                  "example": "no-store"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "502": {
            "$ref": "#/components/responses/BadGatewayError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailableError"
          }
        },
        "security": [
          {
            "MerchantSignature": []
          }
        ],
        "x-codeSamples": [
          {
            "label": "Node.js",
            "lang": "javascript",
            "source": "import { createHmac } from 'node:crypto';\n\nconst apiKey = process.env.ZIPPY_API_KEY;\nif (!apiKey) throw new Error('Set ZIPPY_API_KEY');\n\nfunction sign(merchantId, value) {\n  return createHmac('sha256', apiKey)\n    .update(merchantId + '/' + value, 'utf8')\n    .digest('hex');\n}\n\nconst merchantId = 'merchant-demo';\nconst transactionId = 'zp_20261001_0001';\nconst baseUrl = process.env.ZIPPY_API_BASE_URL;\nif (!baseUrl) throw new Error('Set ZIPPY_API_BASE_URL');\nconst path = '/merchants/' + encodeURIComponent(merchantId)\n  + '/transactions/' + encodeURIComponent(transactionId);\nconst response = await fetch(baseUrl.replace(/\\/$/, '') + path, {\n  headers: { 'X-Zippy-Signature': sign(merchantId, transactionId) },\n});\nconsole.log(response.status, await response.text());\n"
          },
          {
            "label": "Go",
            "lang": "go",
            "source": "package main\n\nimport (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"net/url\"\n    \"os\"\n    \"strings\"\n    \"time\"\n)\n\nfunc sign(apiKey, merchantID, value string) string {\n    mac := hmac.New(sha256.New, []byte(apiKey))\n    mac.Write([]byte(merchantID + \"/\" + value))\n    return hex.EncodeToString(mac.Sum(nil))\n}\n\nfunc main() {\n    apiKey := os.Getenv(\"ZIPPY_API_KEY\")\n    baseURL := os.Getenv(\"ZIPPY_API_BASE_URL\")\n    if apiKey == \"\" || baseURL == \"\" { panic(\"Set ZIPPY_API_KEY and ZIPPY_API_BASE_URL\") }\n    merchantID := \"merchant-demo\"\n    transactionId := \"zp_20261001_0001\"\n    path := \"/merchants/\" + url.PathEscape(merchantID) + \"/transactions/\" + url.PathEscape(transactionId)\n    req, err := http.NewRequest(http.MethodGet, strings.TrimRight(baseURL, \"/\") + path, nil)\n    if err != nil { panic(err) }\n    req.Header.Set(\"X-Zippy-Signature\", sign(apiKey, merchantID, transactionId))\n    client := &http.Client{Timeout: 30 * time.Second}\n    resp, err := client.Do(req)\n    if err != nil { panic(err) }\n    defer resp.Body.Close()\n    body, err := io.ReadAll(resp.Body)\n    if err != nil { panic(err) }\n    fmt.Println(resp.StatusCode, string(body))\n}\n"
          },
          {
            "label": "Python",
            "lang": "python",
            "source": "import hashlib\nimport hmac\nimport os\n\napi_key = os.environ['ZIPPY_API_KEY']\n\ndef sign(merchant_id, value):\n    message = (merchant_id + '/' + value).encode('utf-8')\n    return hmac.new(api_key.encode('utf-8'), message, hashlib.sha256).hexdigest()\n\nfrom urllib.parse import quote\nfrom urllib.request import Request, urlopen\nfrom urllib.error import HTTPError\n\nmerchant_id = 'merchant-demo'\ntransaction_id = 'zp_20261001_0001'\nbase_url = os.environ['ZIPPY_API_BASE_URL'].rstrip('/')\npath = '/merchants/' + quote(merchant_id, safe='') + '/transactions/' + quote(transaction_id, safe='')\nrequest = Request(base_url + path, headers={\n    'X-Zippy-Signature': sign(merchant_id, transaction_id),\n}, method='GET')\ntry:\n    with urlopen(request, timeout=30) as response:\n        print(response.status, response.read().decode('utf-8'))\nexcept HTTPError as error:\n    print(error.code, error.read().decode('utf-8'))\n"
          },
          {
            "label": "PHP",
            "lang": "php",
            "source": "<?php\n$apiKey = getenv('ZIPPY_API_KEY');\nif ($apiKey === false || $apiKey === '') {\n    throw new RuntimeException('Set ZIPPY_API_KEY');\n}\n\nfunction sign(string $merchantId, string $value, string $apiKey): string {\n    return hash_hmac('sha256', $merchantId . '/' . $value, $apiKey);\n}\n\n$merchantId = 'merchant-demo';\n$transactionId = 'zp_20261001_0001';\n$baseUrl = getenv('ZIPPY_API_BASE_URL');\nif ($baseUrl === false || $baseUrl === '') throw new RuntimeException('Set ZIPPY_API_BASE_URL');\n$path = '/merchants/' . rawurlencode($merchantId) . '/transactions/' . rawurlencode($transactionId);\n$curl = curl_init(rtrim($baseUrl, '/') . $path);\ncurl_setopt_array($curl, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_TIMEOUT => 30,\n    CURLOPT_HTTPHEADER => ['X-Zippy-Signature: ' . sign($merchantId, $transactionId, $apiKey)],\n]);\n$body = curl_exec($curl);\nif ($body === false) throw new RuntimeException(curl_error($curl));\n$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);\ncurl_close($curl);\necho $status . PHP_EOL . $body . PHP_EOL;\n"
          },
          {
            "label": "Java",
            "lang": "java",
            "source": "import java.nio.charset.StandardCharsets;\nimport java.util.HexFormat;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI;\nimport java.net.URLEncoder;\nimport java.net.http.HttpClient;\nimport java.net.http.HttpRequest;\nimport java.net.http.HttpResponse;\nimport java.time.Duration;\n\npublic class ZippyExample {\n    static String sign(String apiKey, String merchantId, String value) throws Exception {\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n        byte[] message = (merchantId + \"/\" + value).getBytes(StandardCharsets.UTF_8);\n        return HexFormat.of().formatHex(mac.doFinal(message));\n    }\n\n    static String encode(String value) {\n        return URLEncoder.encode(value, StandardCharsets.UTF_8).replace(\"+\", \"%20\");\n    }\n\n    public static void main(String[] args) throws Exception {\n        String apiKey = System.getenv(\"ZIPPY_API_KEY\");\n        if (apiKey == null || apiKey.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_KEY\");\n        String baseUrl = System.getenv(\"ZIPPY_API_BASE_URL\");\n        if (baseUrl == null || baseUrl.isEmpty()) throw new IllegalStateException(\"Set ZIPPY_API_BASE_URL\");\n        String merchantId = \"merchant-demo\";\n        String transactionId = \"zp_20261001_0001\";\n        String path = \"/merchants/\" + encode(merchantId) + \"/transactions/\" + encode(transactionId);\n        HttpRequest request = HttpRequest.newBuilder(URI.create(baseUrl.replaceAll(\"/+$\", \"\") + path))\n            .timeout(Duration.ofSeconds(30))\n            .header(\"X-Zippy-Signature\", sign(apiKey, merchantId, transactionId))\n            .GET().build();\n        HttpResponse<String> response = HttpClient.newHttpClient()\n            .send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));\n        System.out.println(response.statusCode());\n        System.out.println(response.body());\n    }\n}\n"
          }
        ]
      }
    }
  },
  "components": {
    "parameters": {
      "MerchantId": {
        "name": "merchantId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 50
        },
        "description": "Merchant identifier. This value is included in GET request signatures.",
        "example": "merchant-demo"
      }
    },
    "responses": {
      "ValidationError": {
        "description": "Invalid request: required fields are missing, unknown fields are present, or the format is incorrect.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 400,
              "message": "Invalid payment request"
            }
          }
        }
      },
      "ConflictError": {
        "description": "Duplicate requestId for this merchant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 409,
              "message": "Duplicate requestId for merchant"
            }
          }
        }
      },
      "UnprocessableError": {
        "description": "The transaction cannot be processed with the supplied payment method, destination or configuration.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 422,
              "message": "Payment cannot be processed"
            }
          }
        }
      },
      "NotFoundError": {
        "description": "Transaction not found for this merchant.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 404,
              "message": "Transaction not found"
            }
          }
        }
      },
      "BadGatewayError": {
        "description": "Zippy could not complete the API request. For a transaction creation request, reconcile the outcome before attempting another payment.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 502,
              "message": "Invalid payment service response"
            }
          }
        }
      },
      "ServiceUnavailableError": {
        "description": "Service temporarily unavailable. The outcome of a transaction creation request may be unknown.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 503,
              "message": "Payment service is unavailable"
            }
          }
        }
      },
      "UnauthorizedError": {
        "description": "Missing or invalid signature.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 401,
              "message": "Invalid request signature"
            }
          }
        }
      },
      "ForbiddenError": {
        "description": "The merchant does not have access to the requested resource.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "statusCode": 403,
              "message": "Merchant identity does not match the request"
            }
          }
        }
      }
    },
    "schemas": {
      "PayInRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestId",
          "amount",
          "merchantId",
          "country",
          "currency",
          "paymentMethod",
          "requestedAt",
          "expiresAt",
          "customer",
          "redirectUrls"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "maxLength": 100,
            "description": "Request ID in your system, unique within your merchant."
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d+\\.\\d{2}$",
            "example": "100.00",
            "description": "Decimal amount as a string with exactly two decimal places. Example: \"1290.00\"."
          },
          "merchantId": {
            "type": "string",
            "maxLength": 50,
            "description": "Identifier assigned to your merchant by Zippy."
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO 3166-1 alpha-2 country code, such as CL."
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "description": "ISO 4217 currency code, such as CLP."
          },
          "paymentMethod": {
            "type": "string",
            "maxLength": 50,
            "description": "Payment method enabled for the merchant and country."
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Request date and time in ISO 8601 format."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "Pay-in expiration date and time in ISO 8601 format."
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              }
            ],
            "description": "Customer or beneficiary details."
          },
          "redirectUrls": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RedirectUrls"
              }
            ],
            "description": "URLs to return the customer to after payment."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional merchant-defined data, such as an order reference."
          }
        },
        "example": {
          "requestId": "order-20261001-001",
          "merchantId": "merchant-demo",
          "country": "CL",
          "currency": "CLP",
          "amount": "1290.00",
          "paymentMethod": "bankTransfer",
          "requestedAt": "2026-10-01T12:00:00Z",
          "expiresAt": "2026-10-01T16:00:00Z",
          "customer": {
            "name": "Camila Soto",
            "email": "camila@example.com",
            "phone": "+56912345678",
            "document": {
              "type": "RUT",
              "number": "12345678K"
            }
          },
          "redirectUrls": {
            "success": "https://shop.example.com/payment/success",
            "failure": "https://shop.example.com/payment/failure"
          },
          "metadata": {
            "orderNumber": "ZP-1001"
          }
        }
      },
      "PayInResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestId",
          "zippyId",
          "status"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "zippyId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "failed"
            ],
            "description": "Creation returns processing or failed. Query the transaction to confirm completed."
          },
          "nextAction": {
            "$ref": "#/components/schemas/NextAction"
          }
        }
      },
      "PayoutRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestId",
          "merchantId",
          "country",
          "currency",
          "amount",
          "paymentMethod",
          "requestedAt",
          "customer",
          "destination"
        ],
        "properties": {
          "requestId": {
            "type": "string",
            "maxLength": 100,
            "description": "Request ID in your system, unique within your merchant."
          },
          "merchantId": {
            "type": "string",
            "maxLength": 50,
            "description": "Identifier assigned to your merchant by Zippy."
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO 3166-1 alpha-2 country code, such as CL."
          },
          "currency": {
            "type": "string",
            "minLength": 3,
            "maxLength": 3,
            "description": "ISO 4217 currency code, such as CLP."
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d+\\.\\d{2}$",
            "example": "100.00",
            "description": "Decimal amount as a string with exactly two decimal places. Example: \"1290.00\"."
          },
          "paymentMethod": {
            "type": "string",
            "enum": [
              "bankTransfer",
              "wallet"
            ],
            "description": "Payment method enabled for the merchant and country."
          },
          "requestedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Request date and time in ISO 8601 format."
          },
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              }
            ],
            "description": "Customer or beneficiary details."
          },
          "destination": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/BankAccountDestination"
              },
              {
                "$ref": "#/components/schemas/WalletDestination"
              }
            ],
            "discriminator": {
              "propertyName": "type"
            },
            "description": "Payout destination, compatible with paymentMethod."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true,
            "description": "Optional merchant-defined data, such as an order reference."
          }
        },
        "oneOf": [
          {
            "properties": {
              "paymentMethod": {
                "type": "string",
                "enum": [
                  "bankTransfer"
                ]
              },
              "destination": {
                "$ref": "#/components/schemas/BankAccountDestination"
              }
            }
          },
          {
            "properties": {
              "paymentMethod": {
                "type": "string",
                "enum": [
                  "wallet"
                ]
              },
              "destination": {
                "$ref": "#/components/schemas/WalletDestination"
              },
              "customer": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/Customer"
                  },
                  {
                    "type": "object",
                    "required": [
                      "phone"
                    ],
                    "properties": {
                      "phone": {
                        "type": "string",
                        "minLength": 1,
                        "pattern": "\\S"
                      }
                    }
                  }
                ]
              }
            }
          }
        ],
        "example": {
          "requestId": "withdrawal-20261001-001",
          "merchantId": "merchant-demo",
          "country": "CL",
          "currency": "CLP",
          "amount": "15000.00",
          "paymentMethod": "bankTransfer",
          "requestedAt": "2026-10-01T12:00:00Z",
          "customer": {
            "name": "Camila Soto",
            "email": "camila@example.com",
            "phone": "+56912345678",
            "document": {
              "type": "7289cd02-06fe-4a36-903c-8aad134dd001",
              "number": "12345678K"
            }
          },
          "destination": {
            "type": "bankAccount",
            "bankId": "7c0c3410-4a64-4ae4-8c12-8e0249f2a001",
            "accountTypeId": "b2b69246-bcd1-44b3-83d0-17a28cbcd001",
            "accountNumber": "00123456789"
          },
          "metadata": {
            "withdrawalNumber": "WD-1001"
          }
        }
      },
      "PayoutResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestId",
          "zippyId",
          "status"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "zippyId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "failed"
            ],
            "description": "Creation returns processing or failed. Query the transaction to confirm completed."
          }
        }
      },
      "Customer": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name",
          "email",
          "document"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 255
          },
          "phone": {
            "type": "string",
            "maxLength": 30,
            "description": "Customer phone number. Required for wallet payouts; use international format."
          },
          "document": {
            "$ref": "#/components/schemas/CustomerDocument"
          }
        },
        "example": {
          "name": "Camila Soto",
          "email": "camila@example.com",
          "phone": "+56912345678",
          "document": {
            "type": "RUT",
            "number": "12345678K"
          }
        }
      },
      "CustomerDocument": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "number"
        ],
        "properties": {
          "type": {
            "type": "string",
            "maxLength": 50,
            "description": "Document type. For a bank payout, use customerDocumentTypes[].id from the selected option."
          },
          "number": {
            "type": "string",
            "maxLength": 50
          }
        },
        "example": {
          "type": "RUT",
          "number": "12345678K"
        }
      },
      "RedirectUrls": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "success",
          "failure"
        ],
        "properties": {
          "success": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          },
          "failure": {
            "type": "string",
            "format": "uri",
            "maxLength": 500
          }
        }
      },
      "NextAction": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "url"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "redirect"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "BankAccountDestination": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "bankId",
          "accountTypeId",
          "accountNumber"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "bankAccount"
            ]
          },
          "bankId": {
            "type": "string",
            "maxLength": 100,
            "description": "banks[].bankId from the bank catalog."
          },
          "accountTypeId": {
            "type": "string",
            "maxLength": 50,
            "description": "banks[].options[].accountType.id from the selected option."
          },
          "accountNumber": {
            "type": "string",
            "maxLength": 100,
            "description": "Account number as a string, preserving leading zeros."
          }
        },
        "example": {
          "type": "bankAccount",
          "bankId": "7c0c3410-4a64-4ae4-8c12-8e0249f2a001",
          "accountTypeId": "b2b69246-bcd1-44b3-83d0-17a28cbcd001",
          "accountNumber": "00123456789"
        }
      },
      "WalletDestination": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "identifierType",
          "identifier"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "wallet"
            ]
          },
          "identifierType": {
            "type": "string",
            "enum": [
              "phone"
            ]
          },
          "identifier": {
            "type": "string",
            "maxLength": 100
          }
        },
        "example": {
          "type": "wallet",
          "identifierType": "phone",
          "identifier": "+56912345678"
        }
      },
      "BankPayoutParameter": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "bankId",
          "name",
          "options"
        ],
        "properties": {
          "bankId": {
            "type": "string",
            "maxLength": 100
          },
          "name": {
            "type": "string",
            "maxLength": 255
          },
          "options": {
            "type": "array",
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/BankPayoutOption"
            }
          }
        }
      },
      "BankPayoutOption": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "accountType",
          "customerDocumentTypes"
        ],
        "properties": {
          "accountType": {
            "$ref": "#/components/schemas/PayoutParameterValue"
          },
          "customerDocumentTypes": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/PayoutParameterValue"
            }
          }
        }
      },
      "PayoutParameterValue": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "maxLength": 100
          },
          "name": {
            "type": "string",
            "maxLength": 255
          }
        }
      },
      "TransactionSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "requestId",
          "zippyId",
          "operation",
          "status",
          "country",
          "currency",
          "amount"
        ],
        "properties": {
          "requestId": {
            "type": "string"
          },
          "zippyId": {
            "type": "string"
          },
          "operation": {
            "type": "string",
            "enum": [
              "payin",
              "payout"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ],
            "description": "processing: in progress. completed: confirmed. failed: unsuccessful."
          },
          "country": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "amount": {
            "type": "string",
            "pattern": "^\\d+\\.\\d{2}$"
          }
        },
        "example": {
          "requestId": "order-20261001-001",
          "zippyId": "zp_20261001_0001",
          "operation": "payin",
          "status": "completed",
          "country": "CL",
          "currency": "CLP",
          "amount": "1290.00"
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "statusCode",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "statusCode": {
            "type": "integer"
          },
          "message": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "error": {
            "type": "string"
          }
        }
      },
      "BankCatalog": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "country",
          "banks"
        ],
        "properties": {
          "country": {
            "type": "string",
            "pattern": "^[A-Z]{2}$",
            "description": "Queried country in ISO 3166-1 alpha-2 format."
          },
          "banks": {
            "type": "array",
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/BankPayoutParameter"
            }
          }
        },
        "example": {
          "country": "CL",
          "banks": [
            {
              "bankId": "7c0c3410-4a64-4ae4-8c12-8e0249f2a001",
              "name": "Example Bank",
              "options": [
                {
                  "accountType": {
                    "id": "b2b69246-bcd1-44b3-83d0-17a28cbcd001",
                    "name": "Checking account"
                  },
                  "customerDocumentTypes": [
                    {
                      "id": "7289cd02-06fe-4a36-903c-8aad134dd001",
                      "name": "RUT"
                    }
                  ]
                }
              ]
            }
          ]
        }
      }
    },
    "securitySchemes": {
      "MerchantSignature": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Zippy-Signature",
        "description": "HMAC-SHA256 signature encoded as lowercase hexadecimal, generated with the merchant API key. Sign merchantId/transactionId to query a transaction and merchantId/country to list banks. Send the signature and keep the API key on your server."
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "API reference",
      "tags": [
        "Pay-ins",
        "Payouts",
        "Transactions"
      ]
    },
    {
      "name": "Integration guides",
      "tags": [
        "Notifications",
        "Errors and recovery",
        "Migration from API 1"
      ]
    }
  ]
}
