> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paybridgenp.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Use hosted checkout for a new payment integration unless the user explicitly needs an SDK or Direct QR flow.
> Keep secret API keys on the server. Never place an sk_test_ or sk_live_ key in browser or mobile code.
> Never treat a browser return URL as proof of payment. Verify a signed webhook and confirm status, amount, and currency on the server before fulfillment.
> Fonepay has no sandbox. A Fonepay payment made from a sandbox project uses the merchant's real Fonepay account and moves real money within the documented caps.
> Do not recommend direct integration with Mobile SDK device endpoints. Use the PayBridgeNP Mobile SDK and the merchant-facing session creation API.

# Create a Fonepay Direct-QR session

> Mints a Fonepay Dynamic QR server-side, skipping the hosted checkout page. Returns the QR string, a base64 PNG, and an SSE URL to listen for live status. Requires the `payments:write` scope.

In live mode, Direct-QR requires Pro or higher. In sandbox, it is available on every plan, but Fonepay sandbox payments can move real money and remain capped. A `403 forbidden` with `requiredPlan` is returned when the mode-aware entitlement check fails.

Fonepay must be configured for the project (in the request's mode). Subscribe to `events_url` to receive `qr.scanned` / `qr.paid` / `qr.expired` events.



## OpenAPI

````yaml POST /v1/qr/fonepay
openapi: 3.1.0
info:
  title: PayBridgeNP API
  version: 1.0.0
  description: >-
    PayBridgeNP is Nepal's payment gateway - accept eSewa, Khalti, and Fonepay
    through a single API.


    **Base URL:** `https://api.paybridgenp.com`


    **Dashboard:**
    [dashboard.paybridgenp.com](https://dashboard.paybridgenp.com)


    All monetary amounts are in **paisa** (1 NPR = 100 paisa). An amount of
    `10000` represents Rs. 100.00.
  contact:
    name: PayBridgeNP Support
    url: https://paybridgenp.com
servers:
  - url: https://api.paybridgenp.com
    description: Production
security:
  - BearerAuth: []
tags:
  - name: Authentication
    description: >-
      ## API Keys


      All merchant-facing API endpoints require a Bearer API key in the
      `Authorization` header:


      ```

      Authorization: Bearer sk_live_your_api_key_here

      ```


      You can find and rotate your API keys in the [PayBridgeNP
      Dashboard](https://dashboard.paybridgenp.com) under **Settings → API
      Keys**.


      ### Key prefixes


      | Prefix | Mode | Use |

      |--------|------|-----|

      | `sk_test_` | Sandbox | eSewa and Khalti test flows; see the Fonepay
      warning below |

      | `sk_live_` | Live | Production - real payments |


      The mode is fixed per key. A sandbox key creates sandbox records; a live
      key creates live payments. **Never expose a secret key in client-side
      code.**
  - name: Sandbox & Live Modes
    description: >-
      ## Sandbox Mode


      Sandbox mode lets you test eSewa and Khalti without real money. To use
      sandbox mode:


      1. Generate a sandbox key (`sk_test_...`) from the dashboard.

      2. Use it exactly like a live key - the API behaves identically.

      3. On the hosted checkout page, use the built-in test credentials below.


      ### Built-in test credentials


      | Provider | Test credential |

      |----------|-----------------|

      | eSewa | ID `9806800001`, password `Nepal@123`, MPIN `1122`, OTP `123456`
      |

      | Khalti | Phone `9800000001`, MPIN `1111`, OTP `987654` |


      Sandbox payments appear in your dashboard under the **Sandbox** tab.


      **Fonepay has no sandbox.** If you enable Fonepay on a sandbox project,
      PayBridgeNP uses your real Fonepay merchant account and the payment moves
      real money. Sandbox Fonepay is capped at NPR 1,000 per payment and NPR
      5,000 per month. Use a small amount such as NPR 10.


      ### Switching to live


      Configure your live provider credentials, replace the `sk_test_` key with
      a `sk_live_` key, and run the go-live checks before accepting customers.
  - name: Errors
    description: >-
      ## Error Responses


      All errors return a single nested envelope:


      ```json

      {
        "error": {
          "message": "amount must be a positive integer (in paisa)",
          "type": "invalid_request_error",
          "code": "invalid_request_error",
          "request_id": "req_2Je91NlWKuXkdXUJOK9gaHNW"
        }
      }

      ```


      Read `error.message` for the human-readable string, `error.type` for the
      broad category (SDKs map it to a typed exception), `error.code` to branch
      on, and `error.request_id` (matches `X-Request-Id`) when contacting
      support.


      ### HTTP status codes


      | Status | Meaning |

      |--------|---------|

      | `400` | Bad request - check the `error` field for details |

      | `401` | Unauthorized - missing or invalid API key |

      | `404` | Resource not found |

      | `409` | Conflict - e.g. concurrent idempotent request, customer has
      active subscriptions |

      | `422` | Unprocessable - business logic violation |

      | `429` | Rate limit exceeded - back off and retry |

      | `500` | Internal server error |


      ### Common error types and codes


      | `error.type` | Example `error.code` | Meaning |

      |------|------|-------------|

      | `authentication_error` | `api_key_invalid` | Missing or invalid API key
      |

      | `invalid_request_error` | `not_found` | Validation failure, or the
      resource does not exist or belongs to another project |

      | `idempotency_error` | `idempotency_conflict` | Two concurrent requests
      sent the same `Idempotency-Key` |

      | `rate_limit_error` | `rate_limited` | Rate-limit window exhausted |

      | `api_error` | `api_error` | Server-side failure - safe to retry |


      For the full list of `type` and `code` values, see the [Errors
      reference](https://docs.paybridgenp.com/api-reference/errors).
  - name: Payments
  - name: Refunds
  - name: Webhooks
  - name: Billing
  - name: Account
  - name: Sessions
    description: Read-only access to the checkout sessions created via `POST /v1/checkout`.
  - name: Payment Links
    description: Create and manage reusable hosted payment pages through the API.
  - name: Analytics
    description: >-
      Aggregated KPIs for the authenticated project - payment totals, success
      rate, provider breakdown, and the checkout funnel.
  - name: Providers
    description: >-
      Lists the payment providers enabled and configured for the authenticated
      project, so an integrator can decide which "Pay with X" options to render.
  - name: QR
    description: >-
      Direct-QR API for Fonepay - mint a Dynamic QR server-side and listen for
      scan/pay events over SSE, bypassing the hosted checkout page. Pro.
  - name: SMS
    description: >-
      Send transactional SMS on the merchant's behalf with the merchant's plan
      limits, audit trail, and anti-abuse guards applied. Live SMS is
      plan-metered: Free 0/month, Growth 200/month, Pro 1,000/month (sandbox is
      always logged, never delivered).
  - name: Mobile
    description: >-
      Native mobile SDK endpoints. Create a checkout session that returns
      provider native-SDK params, then verify the result after the native flow
      completes.
  - name: Public
paths:
  /v1/qr/fonepay:
    post:
      tags:
        - QR
      summary: Create a Fonepay Direct-QR session
      description: >-
        Mints a Fonepay Dynamic QR server-side, skipping the hosted checkout
        page. Returns the QR string, a base64 PNG, and an SSE URL to listen for
        live status. Requires the `payments:write` scope.


        In live mode, Direct-QR requires Pro or higher. In sandbox, it is
        available on every plan, but Fonepay sandbox payments can move real
        money and remain capped. A `403 forbidden` with `requiredPlan` is
        returned when the mode-aware entitlement check fails.


        Fonepay must be configured for the project (in the request's mode).
        Subscribe to `events_url` to receive `qr.scanned` / `qr.paid` /
        `qr.expired` events.
      operationId: createFonepayQrSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - amount
                - customer
              properties:
                amount:
                  type: integer
                  description: Amount in paisa.
                  example: 10000
                currency:
                  type: string
                  enum:
                    - NPR
                  default: NPR
                customer:
                  type: object
                  required:
                    - name
                    - email
                  properties:
                    name:
                      type: string
                      maxLength: 100
                    email:
                      type: string
                      format: email
                      maxLength: 200
                    phone:
                      type: string
                      maxLength: 30
                      nullable: true
                    address:
                      type: object
                      nullable: true
                      required:
                        - line1
                        - city
                      properties:
                        line1:
                          type: string
                          maxLength: 200
                        line2:
                          type: string
                          maxLength: 200
                          nullable: true
                        city:
                          type: string
                          maxLength: 100
                        state:
                          type: string
                          maxLength: 200
                          nullable: true
                        postalCode:
                          type: string
                          maxLength: 200
                          nullable: true
                        country:
                          type: string
                          maxLength: 200
                          nullable: true
                metadata:
                  type: object
                  nullable: true
                  additionalProperties: true
      responses:
        '201':
          description: Direct-QR session created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QrSession'
        '400':
          description: Validation error, or Fonepay not configured for this project.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '402':
          description: >-
            The merchant's monthly processing cap is exceeded
            (`quota_exceeded`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            API key lacks the `payments:write` scope, a spend cap blocked the
            amount, or the Direct-QR feature is not on the merchant's plan
            (`forbidden`, with `requiredPlan`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Platform maintenance mode.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      x-codeSamples:
        - lang: Shell
          label: curl
          source: |-
            curl -X POST https://api.paybridgenp.com/v1/qr/fonepay \
              -H "Authorization: Bearer sk_live_your_key" \
              -H "Content-Type: application/json" \
              -d '{
                "amount": 10000,
                "customer": { "name": "Aarav Sharma", "email": "aarav@example.com" }
              }'
components:
  schemas:
    QrSession:
      type: object
      description: >-
        A Direct-QR session for Fonepay. Embed `qr_image` in your UI and
        subscribe to `events_url` for live status.
      properties:
        id:
          type: string
          example: cs_01j9x2k3m4n5p6q7r8s9t0u1v2
          description: Session ID. Also the listener token for the SSE events stream.
        livemode:
          type: boolean
          description: '`true` when created with a live key, `false` for sandbox.'
        amount:
          type: integer
          description: Amount in paisa.
          example: 10000
        currency:
          type: string
          example: NPR
        provider:
          type: string
          enum:
            - fonepay
        status:
          type: string
          enum:
            - initiated
          description: Always `initiated` on creation.
        qr_message:
          type: string
          description: Raw Fonepay QR payload string. Encode this if rendering your own QR.
        qr_image:
          type: string
          description: Base64-encoded PNG data URL of the QR (320px).
          example: data:image/png;base64,iVBORw0KGgo...
        events_url:
          type: string
          format: uri
          description: >-
            Server-Sent Events URL streaming `qr.scanned` / `qr.paid` /
            `qr.expired` events. No API key required - the session ID is the
            listener token.
          example: >-
            https://api.paybridgenp.com/v1/qr/cs_01j9x2k3m4n5p6q7r8s9t0u1v2/events
        expires_at:
          type: string
          format: date-time
          description: When the Fonepay QR expires.
    Error:
      type: object
      description: >-
        All error responses share a single nested envelope:


        ```json

        {
          "error": {
            "message": "Human-readable explanation.",
            "type": "authentication_error",
            "code": "api_key_invalid",
            "request_id": "req_..."
          }
        }

        ```


        The `type` field gives a broad category useful for SDK error class
        hierarchies. The `code` field is fine-grained and stable for
        programmatic branching. `request_id` is the same value as the
        `X-Request-Id` response header - quote it in support requests so we can
        find the exact request in logs.


        Some error types include extra detail objects (e.g. `error.suspension`
        on `account_suspended`, `error.pause` on `token_paused`).
      required:
        - error
      properties:
        error:
          type: object
          required:
            - message
            - type
          properties:
            message:
              type: string
              description: >-
                Human-readable error message - safe to surface to a developer,
                may include the action they need to take.
            type:
              type: string
              enum:
                - authentication_error
                - account_error
                - permission_error
                - invalid_request_error
                - idempotency_error
                - rate_limit_error
                - api_error
              description: >-
                Broad category used by SDKs to instantiate a typed exception
                (e.g. `AuthenticationError`, `InvalidRequestError`).
            code:
              type: string
              description: >-
                Specific, stable identifier for programmatic handling. Common
                values:


                - `api_key_missing` - Authorization header absent or empty
                (401).

                - `api_key_invalid` - key not recognised (401).

                - `api_key_revoked` - key was explicitly revoked (401).

                - `api_key_expired` - key passed its expiration timestamp (401).

                - `api_key_must_be_secret` - endpoint requires `sk_...`; a
                publishable `pk_...` was sent (403).

                - `account_suspended` - merchant account is suspended (403); see
                `error.suspension`.

                - `token_paused` - MCP token paused (423); see `error.pause`.

                - `forbidden` - permission check failed (403).

                - `not_found` - resource not found (404).

                - `idempotency_conflict` - concurrent request with same
                Idempotency-Key (409).

                - `rate_limited` - rate-limit window exhausted (429).
            request_id:
              type: string
              description: >-
                Same value as the `X-Request-Id` response header. Quote in
                support requests for fastest triage.
              example: req_2Je91NlWKuXkdXUJOK9gaHNW
            suspension:
              type: object
              description: Present only when `code === 'account_suspended'`.
              properties:
                suspended_at:
                  type: string
                  format: date-time
                reason:
                  type: string
                  nullable: true
            pause:
              type: object
              description: Present only when `code === 'token_paused'`.
              properties:
                paused_at:
                  type: string
                  format: date-time
                reason:
                  type: string
                  nullable: true
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        Your PayBridgeNP API key. Obtain one from the dashboard under Settings →
        API Keys. Prefix: `sk_test_` for testing, `sk_live_` for production.

````