{
  "openapi": "3.1.0",
  "info": {
    "title": "C2C Merchant API",
    "version": "1.0.0",
    "description": "Create deposit and withdrawal orders, query order status and wallet balance. Every request is authenticated with a merchant API key in the C2C-API-Key header (Authorization: Bearer is also accepted). Deposit and withdrawal requests also carry an MD5 'signature' field in the JSON body; order creation always enforces it. The Merchant API has no request rate limits. See the Signature Generation Guide.",
    "contact": {
      "email": "support@c2cplatform.com"
    }
  },
  "servers": [
    {
      "url": "https://api.c2cpayment.net",
      "description": "Production"
    },
    {
      "url": "https://ca-c2c-dev-api.calmsea-c64629ed.uaenorth.azurecontainerapps.io",
      "description": "Development / integration"
    }
  ],
  "security": [
    {
      "ApiKey": []
    },
    {
      "BearerApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Orders"
    },
    {
      "name": "Account Balance"
    }
  ],
  "paths": {
    "/api/merchant/orders": {
      "post": {
        "tags": [
          "Orders"
        ],
        "operationId": "createOrder",
        "summary": "Create a deposit or withdrawal order",
        "description": "orderType=Deposit returns a hosted paymentPageUrl for the customer, who is given a receiving account of exactly the requested walletType (payment method); orderType=Withdrawal pays the customer's account (customerAccountNumber, or entered by the customer on the hosted page when omitted) and accepts walletType JazzCash, Easypaisa, TillID or BankAccount only. Orders are always accepted: when no eligible member is available the order is created with status Awaiting (stage Awaiting) and assigned later. merchantOrderRef allows one live (in progress or completed) order per merchant. The body must carry a valid 'signature' (always enforced). Retrying the same signed body with the same Idempotency-Key is safe; the same signed body under a different Idempotency-Key → 401 SIGNATURE_REPLAYED.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              },
              "examples": {
                "deposit": {
                  "summary": "Deposit",
                  "value": {
                    "orderType": "Deposit",
                    "amount": 1500,
                    "currency": "PKR",
                    "walletType": "JazzCash",
                    "merchantOrderRef": "INV-10045",
                    "customerReference": "player_12345",
                    "callbackUrl": "https://merchant.example.com/webhooks/c2c",
                    "signature": "56eff91b5ab0351471a2a8d457320230"
                  }
                },
                "withdrawal": {
                  "summary": "Withdrawal",
                  "value": {
                    "orderType": "Withdrawal",
                    "amount": 2000,
                    "currency": "PKR",
                    "walletType": "Easypaisa",
                    "merchantOrderRef": "PAYOUT-20017",
                    "customerReference": "player_12345",
                    "customerAccountNumber": "03001234567",
                    "callbackUrl": "https://merchant.example.com/webhooks/c2c",
                    "signature": "8dddc4abc8aa78744bb29624ac583434"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order created (status Queued/AwaitingMemberPayout, or Awaiting when no eligible member is available yet), or the existing order for a repeated Idempotency-Key with the same terms.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreateOrderResponse"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed ({error}), e.g. amount with more than 4 decimal places or not less than 100,000,000,000,000, or a withdrawal on a deposit-only walletType (JazzCashTillID, JazzCashQR, EasypaisaQR); or request could not be bound, e.g. missing Idempotency-Key / unknown enum (application/problem+json).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid or revoked (empty body); or the body's signature is missing, malformed, invalid or replayed (SignatureError body).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureError"
                }
              }
            }
          },
          "403": {
            "description": "Merchant account not active, or caller IP not whitelisted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "IP not in whitelist."
                }
              }
            }
          },
          "409": {
            "description": "merchantOrderRef is already used by an order of this merchant that is in progress or completed. References of failed orders (Rejected, Expired, CancelledTimeout, Cancelled, CancelledAuto) can be reused.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "merchantOrderRef 'INV-10045' is already used by order ORD-20261008120620-3f6b. Reuse the same Idempotency-Key to retry a request, or use a new reference for a new payment."
                }
              }
            }
          },
          "413": {
            "description": "Request body larger than 64 KB (code PAYLOAD_TOO_LARGE).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignatureError"
                }
              }
            }
          },
          "422": {
            "description": "Withdrawal's amount + fee not covered by the merchant's available balance (which excludes withdrawals in progress, including Awaiting), payment method (walletType) disabled for the merchant and order type, or Idempotency-Key already used with different terms (orderType, amount, walletType, currency, merchantOrderRef or customerAccountNumber).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "This Idempotency-Key was already used for a different order request."
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/orders/{id}/status": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrderStatus",
        "summary": "Query order status",
        "description": "Returns the status, lifecycle stage and milestone timestamps of one of the merchant's orders, looked up by either C2C identifier: the orderId or the orderReference. Scoped to the merchant: works with any of the merchant's API keys, including after key rotation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The orderId (UUID) or the orderReference (ORD-...) returned at creation and in webhooks.",
            "examples": {
              "orderId": {
                "value": "788fcae0-5214-403c-baa2-f318c0eb1b5b"
              },
              "orderReference": {
                "value": "ORD-20261008120620-3f6b"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Order status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrderStatusResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid or revoked. Empty body."
          },
          "404": {
            "description": "No order with this orderId or orderReference for this merchant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Order not found."
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/orders/status": {
      "get": {
        "tags": [
          "Orders"
        ],
        "operationId": "getOrderStatusByReference",
        "summary": "Query order status by reference",
        "description": "Returns the latest order the merchant created with the given merchantOrderRef, or the order with the given C2C orderReference, with its lifecycle stage and milestone timestamps. Send exactly one of the two query parameters. Authenticated by the API key alone; no signature.",
        "parameters": [
          {
            "name": "merchantOrderRef",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "description": "The merchantOrderRef sent at order creation (URL-encoded). Send this or orderReference.",
            "example": "INV-10045"
          },
          {
            "name": "orderReference",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "C2C's orderReference (ORD-...). Send this or merchantOrderRef.",
            "example": "ORD-20261008120620-3f6b"
          }
        ],
        "responses": {
          "200": {
            "description": "Order status.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrderStatusResponse"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Neither merchantOrderRef nor orderReference sent, or both sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "missing": {
                    "summary": "No reference",
                    "value": {
                      "error": "Provide an orderId, an orderReference or a merchantOrderRef."
                    }
                  },
                  "both": {
                    "summary": "Both references",
                    "value": {
                      "error": "Provide either an orderReference or a merchantOrderRef, not both."
                    }
                  }
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid or revoked. Empty body."
          },
          "404": {
            "description": "No order with this merchantOrderRef or orderReference for this merchant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "Order not found."
                }
              }
            }
          }
        }
      }
    },
    "/api/merchant/orders/balance": {
      "get": {
        "tags": [
          "Account Balance"
        ],
        "operationId": "getBalance",
        "summary": "Query wallet balance",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Merchant wallet balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BalanceResponse"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid or revoked. Empty body."
          },
          "404": {
            "description": "API key not found or inactive.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "apiKey",
        "in": "header",
        "name": "C2C-API-Key",
        "description": "Merchant API key (mk_live_<32 alphanumeric>) sent as C2C-API-Key: mk_live_..."
      },
      "BearerApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "mk_live_<32 alphanumeric>",
        "description": "Alternative for existing integrations: Authorization: Bearer mk_live_... (C2C-API-Key wins when both are sent)."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "maxLength": 100
        },
        "description": "Unique per order and API key; reuse it for every retry of that order. A repeat with the same terms returns the original order; a repeat with different terms (orderType, amount, walletType, currency, merchantOrderRef, customerAccountNumber) returns 422. The same signed body sent with a different key within 24 hours returns 401 SIGNATURE_REPLAYED."
      }
    },
    "schemas": {
      "OrderType": {
        "type": "string",
        "enum": [
          "Deposit",
          "Withdrawal"
        ]
      },
      "WalletType": {
        "type": "string",
        "enum": [
          "JazzCash",
          "Easypaisa",
          "TillID",
          "BankAccount",
          "JazzCashTillID",
          "JazzCashQR",
          "EasypaisaQR"
        ],
        "description": "The payment method. JazzCash = JazzCash account number; JazzCashTillID = JazzCash Till ID; JazzCashQR = JazzCash QR code; Easypaisa = Easypaisa account number; TillID = Easypaisa Till ID; EasypaisaQR = Easypaisa QR code; BankAccount = bank account. Deposits: the customer is only ever given a receiving account of exactly the requested method (JazzCash and Easypaisa mean the account number only; request JazzCashTillID or TillID for a Till ID). BankAccount deposits are accepted but bank accounts are not offered on the hosted payment page for now. Withdrawals: JazzCash, Easypaisa, TillID or BankAccount only; JazzCashTillID, JazzCashQR and EasypaisaQR are deposit-only and rejected with 400."
      },
      "Currency": {
        "type": "string",
        "enum": [
          "PKR",
          "INR",
          "BDT"
        ],
        "default": "PKR"
      },
      "OrderStatus": {
        "type": "string",
        "enum": [
          "Awaiting",
          "Queued",
          "AwaitingCustomerPayment",
          "AwaitingMemberConfirmation",
          "AwaitingMemberPayout",
          "ManualReview",
          "Completed",
          "Rejected",
          "Expired",
          "CancelledTimeout",
          "Cancelled",
          "CancelledAuto",
          "Assigned",
          "Frozen",
          "Confirmed"
        ],
        "description": "Final success: Completed (legacy Confirmed). Final failure: Rejected, Expired, CancelledTimeout, Cancelled, CancelledAuto. Others are in progress (Awaiting = accepted, waiting for an eligible member)."
      },
      "OrderStage": {
        "type": "string",
        "enum": [
          "Awaiting",
          "Assigned",
          "Sent",
          "Approved",
          "Rejected",
          "Completed",
          "Failed"
        ],
        "description": "Canonical lifecycle stage: Awaiting → Assigned → Sent → Approved / Rejected → Completed / Failed. Approved/Rejected currently happen in the same step as Completed/Failed, so the current stage is normally one of the other five."
      },
      "OrderLifecycle": {
        "type": "object",
        "description": "Current stage and when each milestone was reached (ISO 8601 UTC; null = not reached).",
        "required": [
          "stage",
          "createdAt"
        ],
        "properties": {
          "stage": {
            "$ref": "#/components/schemas/OrderStage"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "awaitingAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the order started waiting for an eligible member."
          },
          "assignedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a member was assigned."
          },
          "sentAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Deposit: customer confirmed the payment on the hosted page. Withdrawal: member sent the payout."
          },
          "approvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the member approved."
          },
          "rejectedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the member rejected."
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the order settled."
          },
          "failedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the order ended without settlement."
          }
        }
      },
      "CreateOrderRequest": {
        "type": "object",
        "required": [
          "orderType",
          "amount",
          "walletType",
          "signature"
        ],
        "properties": {
          "orderType": {
            "$ref": "#/components/schemas/OrderType"
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "example": 1500,
            "description": "At most 4 decimal places and less than 100,000,000,000,000."
          },
          "walletType": {
            "$ref": "#/components/schemas/WalletType"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "merchantOrderRef": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Optional. One live (in progress or completed) order per reference per merchant; a reference whose earlier order failed can be reused. Queryable via GET /api/merchant/orders/status."
          },
          "customerReference": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "customerAccountNumber": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 50,
            "description": "Withdrawals: the customer's receiving account. Optional — when omitted the order is accepted as Awaiting and the customer enters their account on the hosted page (paymentPageUrl) before a member is assigned. Ignored for deposits."
          },
          "customerAccountName": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Withdrawals: the name the customer's account is held in, shown to the member who pays it. Only accepted together with customerAccountNumber."
          },
          "callbackUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 500,
            "description": "Absolute http(s) URL for this order's webhooks; defaults to the Merchant Portal setting. Must resolve to a public internet address (loopback, private and link-local addresses are refused at delivery); redirects are not followed."
          },
          "successUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 500,
            "description": "Absolute http(s) URL. The hosted payment page sends the customer here when the order completes, appending c2c_order_id, c2c_order_reference and c2c_status."
          },
          "failureUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "maxLength": 500,
            "description": "Absolute http(s) URL. The hosted payment page sends the customer here when the order is rejected, expires or is cancelled (same parameters appended)."
          },
          "metadata": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "txnRefDigitsRequired": {
            "type": "integer",
            "minimum": 4,
            "maximum": 8,
            "default": 4,
            "description": "Deposits on account number and Till ID methods (JazzCash, JazzCashTillID, Easypaisa, TillID) only: how many trailing digits of the wallet transaction ID the customer enters on the hosted payment page. No effect on a payment made with JazzCashQR or EasypaisaQR, where no transaction ID is asked for: the customer gives their JazzCash account number (JazzCashQR) or the last 4 digits of their Easypaisa IBAN (EasypaisaQR) instead. No effect on withdrawals."
          },
          "maxRetryCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "default": 3,
            "description": "Withdrawals: reassignment attempts after the payout window passes."
          },
          "signature": {
            "type": "string",
            "example": "f2eb65603d36507621fdfd64aba78974",
            "description": "Lowercase hex MD5 (32 characters) of the other top-level fields, excluding null, empty-string, object and array values: keys lowercased, sorted in ASCII order, joined as key=value with '&', followed directly by the API key's signing secret (sksec_...). Values are signed as written in the body."
          }
        }
      },
      "CreateOrderResponse": {
        "type": "object",
        "required": [
          "orderId",
          "paymentPageUrl",
          "status",
          "orderReference",
          "expiresAt"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "paymentPageUrl": {
            "type": "string",
            "format": "uri",
            "description": "Hosted page for the order. Deposits: the customer pays there. Withdrawals: the customer enters their receiving account there when the order was created without one, and can follow the payout. Pre-signed; expires at expiresAt."
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "orderReference": {
            "type": "string",
            "example": "ORD-20261008120620-3f6b"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "stage": {
            "$ref": "#/components/schemas/OrderStage"
          },
          "merchantOrderRef": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "OrderStatusResponse": {
        "type": "object",
        "required": [
          "orderId",
          "orderReference",
          "status",
          "amount",
          "queuedAt"
        ],
        "properties": {
          "orderId": {
            "type": "string",
            "format": "uuid"
          },
          "orderReference": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/OrderStatus"
          },
          "amount": {
            "type": "number"
          },
          "queuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "confirmedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelledAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "merchantOrderRef": {
            "type": [
              "string",
              "null"
            ]
          },
          "orderType": {
            "$ref": "#/components/schemas/OrderType"
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          },
          "fee": {
            "type": [
              "number",
              "null"
            ],
            "description": "Merchant fee, fixed when the order was accepted (deposit: re-quoted if the customer pays with a different payment method than requested; capped at the deposit amount)."
          },
          "stage": {
            "$ref": "#/components/schemas/OrderStage"
          },
          "lifecycle": {
            "$ref": "#/components/schemas/OrderLifecycle"
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "availableBalance",
          "reservedBalance",
          "frozenBalance",
          "currency"
        ],
        "properties": {
          "availableBalance": {
            "type": "number",
            "description": "Funds usable now. Withdrawals in progress are already taken out."
          },
          "reservedBalance": {
            "type": "number",
            "description": "Amount + fee of the withdrawals still in progress."
          },
          "frozenBalance": {
            "type": "number",
            "description": "Funds held for settlement requests."
          },
          "currency": {
            "$ref": "#/components/schemas/Currency"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "SignatureError": {
        "type": "object",
        "required": [
          "success",
          "error",
          "code",
          "traceId"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "enum": [
              "SIGNATURE_REQUIRED",
              "SIGNATURE_MALFORMED",
              "SIGNATURE_INVALID",
              "SIGNATURE_REPLAYED",
              "PAYLOAD_TOO_LARGE"
            ]
          },
          "traceId": {
            "type": "string"
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "traceId": {
            "type": "string"
          }
        }
      }
    }
  }
}
