{
  "openapi": "3.1.0",
  "info": {
    "title": "JamiDev API",
    "version": "1.3.0",
    "description": "Jami's payments API for Ethiopia: create products and checkout sessions paid with Telebirr, M-Pesa or CBE Birr, poll their status, list orders, read your balance and withdraw earnings.\n\nAll amounts are integer santim (ETB minor units): `10000` = 100.00 ETB. The environment (sandbox or production) comes from the bearer token, not the host.\n\nThe official TypeScript client is [`jami-sdk`](https://www.npmjs.com/package/jami-sdk); the MCP server is [`jami-mcp`](https://www.npmjs.com/package/jami-mcp). Webhooks are documented at https://dev.jami.bio/docs/webhooks.",
    "contact": {
      "url": "https://dev.jami.bio/docs",
      "email": "support@jami.bio"
    }
  },
  "servers": [
    {
      "url": "https://jami.bio/api/jamidev",
      "description": "Sandbox and production; the token decides which"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Products",
      "description": "Create and manage the products you sell"
    },
    {
      "name": "Checkout",
      "description": "Create and poll checkout sessions"
    },
    {
      "name": "Orders",
      "description": "Paid checkouts"
    },
    {
      "name": "Withdrawals",
      "description": "Balance and payouts"
    }
  ],
  "paths": {
    "/products": {
      "post": {
        "tags": [
          "Products"
        ],
        "summary": "Create a product",
        "operationId": "createProduct",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProductParams"
              },
              "examples": {
                "fixed": {
                  "summary": "2,500 ETB course",
                  "value": {
                    "title": "Ge’ez Calligraphy Course",
                    "pricingModel": "fixed",
                    "price": {
                      "amount": 250000
                    },
                    "status": "published"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed; the message names the field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "No token and no dashboard session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "List products",
        "operationId": "listProducts",
        "description": "Newest first.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "archived"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of products",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductsPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "No token and no dashboard session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/products/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Product id or p_… token.",
          "schema": {
            "type": "string",
            "pattern": "^(p_[A-Za-z0-9_-]+|[a-f0-9]{24})$"
          }
        }
      ],
      "get": {
        "tags": [
          "Products"
        ],
        "summary": "Get a product",
        "operationId": "getProduct",
        "responses": {
          "200": {
            "description": "The product",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "No token and no dashboard session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Products"
        ],
        "summary": "Update a product",
        "operationId": "updateProduct",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProductParams"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated product",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProductResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "No token and no dashboard session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A locked field was changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Products"
        ],
        "summary": "Archive a product",
        "operationId": "archiveProduct",
        "description": "Sets status to archived. Orders keep pointing at it.",
        "responses": {
          "200": {
            "description": "The archived product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "product"
                  ],
                  "properties": {
                    "product": {
                      "$ref": "#/components/schemas/Product"
                    },
                    "url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "403": {
            "description": "No token and no dashboard session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkout": {
      "post": {
        "tags": [
          "Checkout"
        ],
        "summary": "Create a checkout session",
        "operationId": "createCheckout",
        "description": "Works with a bearer token (scoped to your organization) or without one (used by hosted checkout pages, published products only). Custom amounts, `metadata` and payment links need a token.\n\nTwo ways to collect: with `gateway` and the buyer's email and phone, production checkouts push a payment prompt to the buyer's phone (`mode: direct`); sandbox redirects to the simulator. With a token and no `gateway`, you get a payment link (`mode: redirect`, `checkoutUrl` = https://jami.bio/jamidev/pay/cs_…) and `customer` is optional. Without a token and no `gateway`, ArifPay's hosted page is returned. Sessions and links expire after 30 minutes.\n\nRate limits: with a token, a per-token bucket (burst 200, refill 20/s); without one, 40 requests per 5 minutes per IP.",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckoutParams"
              },
              "examples": {
                "customAmount": {
                  "summary": "Charge 1,237 ETB, no product",
                  "value": {
                    "amount": 123700,
                    "description": "Order #1042 — 3 prints",
                    "metadata": {
                      "orderId": "1042"
                    },
                    "customer": {
                      "email": "buyer@example.com",
                      "phone": "251911223344"
                    },
                    "gateway": "telebirr"
                  }
                },
                "product": {
                  "summary": "Sell a product",
                  "value": {
                    "productId": "p_x7Kf3mQz9aBc",
                    "customer": {
                      "email": "buyer@example.com",
                      "phone": "251911223344"
                    },
                    "gateway": "telebirr"
                  }
                },
                "paymentLink": {
                  "summary": "Payment link for 1,237 ETB",
                  "value": {
                    "amount": 123700,
                    "description": "Order #1042",
                    "metadata": {
                      "orderId": "1042"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout created",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/FreeCheckoutResult"
                    },
                    {
                      "$ref": "#/components/schemas/PaidCheckoutResult"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation failed; the message names the field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "404": {
            "description": "Product not found, not published, or not in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many checkout requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "The payment provider refused to start the payment",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/checkout/{sessionId}": {
      "get": {
        "tags": [
          "Checkout"
        ],
        "summary": "Get checkout session status",
        "operationId": "getCheckoutStatus",
        "description": "No authentication. While the session is open, each call also checks the payment provider (throttled per session), so polling resolves even when notifications are late.",
        "security": [
          {}
        ],
        "parameters": [
          {
            "name": "sessionId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Session status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckoutStatus"
                }
              }
            }
          },
          "404": {
            "description": "Session not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/orders": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "List orders",
        "operationId": "listOrders",
        "description": "Newest first.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number, from 1."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/OrderStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of orders",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrdersPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "tags": [
          "Withdrawals"
        ],
        "summary": "Get withdrawable balance",
        "operationId": "getBalance",
        "description": "This organization's earnings not yet withdrawn: the most its token can withdraw.",
        "responses": {
          "200": {
            "description": "Balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                },
                "example": {
                  "success": true,
                  "eligible": true,
                  "balanceMinor": 250000,
                  "currency": "ETB"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        }
      }
    },
    "/withdrawals": {
      "post": {
        "tags": [
          "Withdrawals"
        ],
        "summary": "Create a withdrawal",
        "operationId": "createWithdrawal",
        "description": "Pays out to a phone wallet. The JamiDev fee (7% by default, or the account's custom rate) and any government tax are cut from `amount`; `netAmount` reaches the account. Requires a production organization, Developer Mode and approved KYC. Minimum 10000 (100 ETB). Up to 1000000 (10,000 ETB) pays out automatically; more waits for manual review. Per organization: 10000000 (100,000 ETB) per rolling 24 hours.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Reusing a key returns the original withdrawal (200) instead of creating another."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWithdrawalParams"
              },
              "example": {
                "amount": 10000,
                "gateway": "telebirr",
                "account": "251911223344"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Withdrawal created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "200": {
            "description": "Idempotency key matched an existing withdrawal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input, not a production organization, Developer Mode off, KYC not approved, or more than the organization's earnings",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "402": {
            "description": "The owner's Jami wallet can't cover the payout right now",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Daily payout limit reached for this organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Withdrawals"
        ],
        "summary": "List withdrawals",
        "operationId": "listWithdrawals",
        "description": "Newest first.",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number, from 1."
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Page size."
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/WithdrawalStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of withdrawals",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WithdrawalsPage"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          }
        }
      }
    },
    "/withdrawals/{id}": {
      "get": {
        "tags": [
          "Withdrawals"
        ],
        "summary": "Get a withdrawal",
        "operationId": "getWithdrawal",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[a-f0-9]{24}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The withdrawal",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Withdrawal"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or wrong-environment token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthError"
                }
              }
            }
          },
          "404": {
            "description": "Not found in your organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "jamidev_test_… (sandbox) or jamidev_live_… (production), from Developer → API Tokens."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "AuthError": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string",
            "description": "Says to create a new token when the token belongs to the organization's other environment."
          }
        }
      },
      "Gateway": {
        "type": "string",
        "enum": [
          "telebirr",
          "mpesa",
          "cbe"
        ]
      },
      "Customer": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "phone": {
            "type": "string",
            "description": "Required for a direct push (gateway set) on a paid checkout. 2519XXXXXXXX, 09XXXXXXXX or 9XXXXXXXX."
          },
          "name": {
            "type": "string"
          },
          "externalId": {
            "type": "string",
            "description": "Your own user id; returned in webhooks as customerExternalId."
          }
        },
        "description": "Required, except for a paid payment link (bearer token, no gateway), where the buyer enters their details on the page."
      },
      "CreateCheckoutParams": {
        "description": "Send productId for a product checkout, or amount + description (and no productId) for a custom amount.",
        "oneOf": [
          {
            "$ref": "#/components/schemas/ProductCheckoutParams"
          },
          {
            "$ref": "#/components/schemas/CustomAmountCheckoutParams"
          }
        ]
      },
      "FreeCheckoutResult": {
        "type": "object",
        "required": [
          "sessionId",
          "orderId"
        ],
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "orderId": {
            "type": "string"
          }
        }
      },
      "PaidCheckoutResult": {
        "type": "object",
        "required": [
          "sessionId",
          "checkoutUrl",
          "mode"
        ],
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "checkoutUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where to send the buyer. With a token and no gateway: a Jami payment link (https://jami.bio/jamidev/pay/cs_…) where the buyer picks the method and can retry until the session expires. In sandbox with a gateway: the simulator. null when mode is direct (the prompt went to the phone)."
          },
          "mode": {
            "type": "string",
            "enum": [
              "redirect",
              "direct"
            ]
          }
        }
      },
      "CheckoutFailure": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "payment_rejected, payment_failed, payment_canceled, payment_timed_out, initiate_failed or session_expired. New codes may be added; treat unknown ones like payment_failed.",
            "examples": [
              "payment_rejected",
              "payment_failed",
              "payment_canceled",
              "payment_timed_out",
              "initiate_failed",
              "session_expired"
            ]
          },
          "message": {
            "type": "string",
            "description": "Safe to show the buyer."
          }
        }
      },
      "CheckoutStatus": {
        "type": "object",
        "required": [
          "sessionId",
          "status",
          "expiresAt",
          "amount",
          "currency",
          "orderId",
          "orderStatus"
        ],
        "properties": {
          "sessionId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "completed",
              "expired"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "amount": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Charged amount in santim."
          },
          "currency": {
            "type": "string",
            "const": "ETB"
          },
          "orderId": {
            "type": [
              "string",
              "null"
            ]
          },
          "orderStatus": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/OrderStatus"
              },
              {
                "type": "null"
              }
            ]
          },
          "failure": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CheckoutFailure"
              },
              {
                "type": "null"
              }
            ],
            "description": "Why an expired session has no order."
          },
          "attempt": {
            "type": "object",
            "description": "Payment links only. The session stays open between attempts.",
            "required": [
              "inFlight",
              "lastFailure"
            ],
            "properties": {
              "inFlight": {
                "type": "boolean",
                "description": "true while a payment prompt is out."
              },
              "lastFailure": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CheckoutFailure"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Why the last attempt failed."
              }
            }
          }
        }
      },
      "OrderStatus": {
        "type": "string",
        "enum": [
          "pending",
          "paid",
          "failed"
        ]
      },
      "Order": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "_id",
          "status",
          "amount",
          "currency"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "checkoutSessionId": {
            "type": "string"
          },
          "productId": {
            "type": "object",
            "properties": {
              "_id": {
                "type": "string"
              },
              "title": {
                "type": "string"
              }
            }
          },
          "customerId": {
            "type": "object",
            "properties": {
              "_id": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "amount": {
            "type": "integer",
            "description": "What the buyer paid. Integer santim (ETB minor units)."
          },
          "currency": {
            "type": "string",
            "const": "ETB"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "production"
            ]
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Snapshot at checkout: product metadata, then checkout link metadata, then the metadata sent to POST /checkout."
          },
          "paidAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "description": {
            "type": "string",
            "description": "Set only on custom-amount orders. Their productId is an internal placeholder titled \"Custom payment\"; show this instead."
          }
        }
      },
      "OrdersPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "eligible",
          "balanceMinor",
          "currency"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "eligible": {
            "type": "boolean",
            "description": "false, with balanceMinor 0, when the organization isn't in production."
          },
          "balanceMinor": {
            "type": "integer",
            "description": "Earnings not yet withdrawn. Integer santim (ETB minor units)."
          },
          "currency": {
            "type": "string",
            "const": "ETB"
          }
        }
      },
      "CreateWithdrawalParams": {
        "type": "object",
        "required": [
          "amount",
          "gateway",
          "account"
        ],
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 10000,
            "description": "Gross santim; the fee and tax are cut from this."
          },
          "gateway": {
            "$ref": "#/components/schemas/Gateway"
          },
          "account": {
            "type": "string",
            "description": "Destination phone, e.g. 251911223344."
          }
        }
      },
      "WithdrawalStatus": {
        "type": "string",
        "enum": [
          "pending",
          "reviewing",
          "approved",
          "rejected",
          "processing",
          "completed"
        ]
      },
      "Withdrawal": {
        "type": "object",
        "required": [
          "_id",
          "status",
          "amount",
          "feeAmount",
          "taxAmount",
          "netAmount",
          "currency",
          "gateway",
          "account"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/WithdrawalStatus"
          },
          "amount": {
            "type": "integer",
            "description": "Gross amount debited. Integer santim (ETB minor units)."
          },
          "feeAmount": {
            "type": "integer",
            "description": "JamiDev fee. Integer santim (ETB minor units)."
          },
          "taxAmount": {
            "type": "integer",
            "description": "Government tax. Integer santim (ETB minor units)."
          },
          "netAmount": {
            "type": "integer",
            "description": "What reaches the account. Integer santim (ETB minor units)."
          },
          "currency": {
            "type": "string",
            "const": "ETB"
          },
          "gateway": {
            "$ref": "#/components/schemas/Gateway"
          },
          "account": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "note": {
            "type": "string"
          },
          "idempotencyKey": {
            "type": "string"
          }
        }
      },
      "WithdrawalsPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Withdrawal"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          }
        }
      },
      "ProductCheckoutParams": {
        "type": "object",
        "title": "Product checkout",
        "required": [
          "productId"
        ],
        "properties": {
          "productId": {
            "type": "string",
            "description": "The product's public p_… token or its id. Must be published."
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "gateway": {
            "$ref": "#/components/schemas/Gateway"
          },
          "successUrl": {
            "type": "string",
            "format": "uri",
            "description": "{CHECKOUT_ID} is replaced with the session id. Always honored with a token; without one, only if its host matches a registered redirect URL."
          },
          "metadata": {
            "type": "object",
            "maxProperties": 20,
            "propertyNames": {
              "minLength": 1,
              "maxLength": 40
            },
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "description": "Your own key/values. Token requests only. Values are stored as strings of up to 500 characters, saved on the order and sent in order webhooks. Wins over product and checkout link metadata on a key conflict."
          },
          "amount": {
            "type": "integer",
            "description": "Required for pay-what-you-want products, at or above the minimum. Ignored otherwise. Integer santim (ETB minor units)."
          },
          "collectedFields": {
            "type": "object",
            "additionalProperties": true,
            "description": "Checkout field answers keyed by field id."
          },
          "checkoutLink": {
            "type": "string",
            "pattern": "^cl_",
            "description": "Applies the link's metadata and success URL when it's active, yours, and includes the product."
          }
        }
      },
      "CustomAmountCheckoutParams": {
        "type": "object",
        "title": "Custom amount",
        "required": [
          "amount",
          "description"
        ],
        "description": "Charge any amount with no product. Requires a bearer token.",
        "properties": {
          "amount": {
            "type": "integer",
            "minimum": 100,
            "description": "Integer santim (ETB minor units), at least 100 (1.00 ETB). 123700 is 1,237.00 ETB."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "What the buyer is paying for. Shown on the payment prompt, receipt and result pages instead of a product title."
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "gateway": {
            "$ref": "#/components/schemas/Gateway"
          },
          "successUrl": {
            "type": "string",
            "format": "uri",
            "description": "{CHECKOUT_ID} is replaced with the session id. Always honored with a token; without one, only if its host matches a registered redirect URL."
          },
          "metadata": {
            "type": "object",
            "maxProperties": 20,
            "propertyNames": {
              "minLength": 1,
              "maxLength": 40
            },
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean"
              ]
            },
            "description": "Your own key/values. Token requests only. Values are stored as strings of up to 500 characters, saved on the order and sent in order webhooks. Wins over product and checkout link metadata on a key conflict."
          }
        },
        "not": {
          "required": [
            "productId"
          ],
          "properties": {
            "productId": {
              "type": "string"
            }
          }
        }
      },
      "Price": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "fixed",
              "pwyw",
              "free"
            ]
          },
          "amount": {
            "type": "integer",
            "description": "Fixed price in santim."
          },
          "minimumAmount": {
            "type": "integer",
            "description": "Flexible price (pwyw): the lowest amount a checkout may charge, in santim."
          },
          "defaultAmount": {
            "type": "integer",
            "description": "Flexible price: pre-filled on the product page, in santim."
          },
          "currency": {
            "type": "string",
            "const": "ETB"
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "_id",
          "checkoutToken",
          "title",
          "pricingModel",
          "status"
        ],
        "properties": {
          "_id": {
            "type": "string"
          },
          "checkoutToken": {
            "type": "string",
            "description": "Public p_… token; use it as productId and in links."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": "string",
            "maxLength": 5000
          },
          "pricingModel": {
            "type": "string",
            "enum": [
              "fixed",
              "pwyw",
              "free"
            ],
            "description": "Locked after creation."
          },
          "productType": {
            "type": "string",
            "const": "one_time"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProductResponse": {
        "type": "object",
        "required": [
          "product",
          "url"
        ],
        "properties": {
          "product": {
            "$ref": "#/components/schemas/Product"
          },
          "price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Price"
              },
              {
                "type": "null"
              }
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The product’s public page, https://jami.bio/jamidev/p_…."
          }
        }
      },
      "ProductListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Product"
          },
          {
            "type": "object",
            "properties": {
              "price": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/Price"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "url": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          }
        ]
      },
      "ProductsPage": {
        "type": "object",
        "required": [
          "items",
          "total",
          "page",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductListItem"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          }
        }
      },
      "CreateProductParams": {
        "type": "object",
        "required": [
          "title",
          "pricingModel"
        ],
        "description": "price.amount is required for fixed; price.minimumAmount for pwyw.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "pricingModel": {
            "type": "string",
            "enum": [
              "fixed",
              "pwyw",
              "free"
            ]
          },
          "price": {
            "type": "object",
            "properties": {
              "amount": {
                "type": "integer",
                "minimum": 0
              },
              "minimumAmount": {
                "type": "integer",
                "minimum": 0
              },
              "defaultAmount": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "description": {
            "type": "string",
            "maxLength": 5000
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published"
            ],
            "default": "draft"
          }
        }
      },
      "UpdateProductParams": {
        "type": "object",
        "description": "Send only what changes. pricingModel, productType and price.type are locked (422).",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": "string",
            "maxLength": 5000
          },
          "media": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "price": {
            "type": "object",
            "properties": {
              "amount": {
                "type": "integer",
                "minimum": 0,
                "description": "Fixed products only."
              },
              "minimumAmount": {
                "type": "integer",
                "minimum": 0
              },
              "defaultAmount": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        }
      }
    }
  }
}
