diff --git a/.github/workflows/stainless-action.yml b/.github/workflows/stainless-action.yml index 0de4a72b..895b84bb 100644 --- a/.github/workflows/stainless-action.yml +++ b/.github/workflows/stainless-action.yml @@ -37,6 +37,7 @@ jobs: org: ${{ env.STAINLESS_ORG }} project: ${{ env.STAINLESS_PROJECT }} oas_path: ${{ env.OAS_PATH }} + guess_config: true merge: if: github.event.action == 'closed' && github.event.pull_request.merged == true diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml index 5817a8ab..c99d3bf3 100644 --- a/.stainless/stainless.yml +++ b/.stainless/stainless.yml @@ -10,7 +10,7 @@ organization: # and headings. name: grid # Link to your API documentation. - docs: '' + docs: 'grid.lightspark.com' # Contact email for bug reports, questions, and support requests. contact: support@lightspark.com @@ -229,6 +229,11 @@ resources: list: get /tokens retrieve: get /tokens/{tokenId} delete: delete /tokens/{tokenId} + exchange_rates: + methods: + list: + endpoint: get /exchange-rates + paginated: false settings: # All generated integration tests that hit the prism mock http server are marked @@ -326,6 +331,86 @@ openapi: args: unionPath: AllErrors enumProperty: code + # ── customerType: IndividualCustomerFields / BusinessCustomerFields ── + - command: remove + reason: >- + Remove inline customerType enums from customer fields schemas to avoid + conflicting types when allOf merges them with base schemas (Customer, + CustomerCreateRequest, CustomerUpdateRequest) that define customerType + via the shared CustomerType $ref + args: + target: + - "$.components.schemas.IndividualCustomerFields.properties" + - "$.components.schemas.BusinessCustomerFields.properties" + keys: [ "customerType" ] + + # ── accountType: common account info schemas ── + - command: remove + reason: >- + Remove inline accountType enums from common account info schemas to + avoid conflicting types when allOf merges them with + BaseExternalAccountInfo or BasePaymentAccountInfo, which define + accountType via shared $ref enums + args: + target: + - "$.components.schemas.UsAccountInfo.properties" + - "$.components.schemas.ClabeAccountInfo.properties" + - "$.components.schemas.PixAccountInfo.properties" + - "$.components.schemas.IbanAccountInfo.properties" + - "$.components.schemas.UpiAccountInfo.properties" + - "$.components.schemas.NgnAccountInfo.properties" + - "$.components.schemas.CadAccountInfo.properties" + - "$.components.schemas.GbpAccountInfo.properties" + - "$.components.schemas.PhpAccountInfo.properties" + - "$.components.schemas.SgdAccountInfo.properties" + - "$.components.schemas.SparkWalletInfo.properties" + - "$.components.schemas.LightningInfo.properties" + - "$.components.schemas.SolanaWalletInfo.properties" + - "$.components.schemas.TronWalletInfo.properties" + - "$.components.schemas.PolygonWalletInfo.properties" + - "$.components.schemas.BaseWalletInfo.properties" + keys: [ "accountType" ] + + # ── sourceType: transaction and quote source schemas ── + - command: remove + reason: >- + Remove inline sourceType enums from transaction and quote source + allOf variants to avoid conflicting types with their base schemas + which define sourceType via shared $ref enums + args: + target: + - "$.components.schemas.AccountTransactionSource.allOf[1].properties" + - "$.components.schemas.UmaAddressTransactionSource.allOf[1].properties" + - "$.components.schemas.AccountQuoteSource.allOf[1].properties" + - "$.components.schemas.RealtimeFundingQuoteSource.allOf[1].properties" + keys: [ "sourceType" ] + + # ── destinationType: transaction and quote destination schemas ── + - command: remove + reason: >- + Remove inline destinationType enums from transaction and quote + destination allOf variants to avoid conflicting types with their + base schemas which define destinationType via shared $ref enums + args: + target: + - "$.components.schemas.AccountTransactionDestination.allOf[1].properties" + - "$.components.schemas.UmaAddressTransactionDestination.allOf[1].properties" + - "$.components.schemas.AccountDestination.allOf[1].properties" + - "$.components.schemas.UmaAddressDestination.allOf[1].properties" + - "$.components.schemas.ExternalAccountDetailsDestination.allOf[1].properties" + keys: [ "destinationType" ] + + # ── beneficiaryType: beneficiary schemas ── + - command: remove + reason: >- + Remove inline beneficiaryType enums from beneficiary allOf variants + to avoid conflicting types with BaseBeneficiary which defines + beneficiaryType via a shared $ref enum + args: + target: + - "$.components.schemas.IndividualBeneficiary.allOf[1].properties" + - "$.components.schemas.BusinessBeneficiary.allOf[1].properties" + keys: [ "beneficiaryType" ] errors: union: @@ -339,3 +424,4 @@ diagnostics: ignored: Schema/ObjectHasNoProperties: - pagination.0.response.data.items + Schema/EnumHasOneMember: true diff --git a/.stainless/workspace.json b/.stainless/workspace.json index 1af98546..7cdd3c13 100644 --- a/.stainless/workspace.json +++ b/.stainless/workspace.json @@ -5,6 +5,9 @@ "targets": { "typescript": { "output_path": "../sdks/grid-typescript" + }, + "kotlin": { + "output_path": "../sdks/grid-kotlin" } } } diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index 5a660fe2..21c98521 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -38,6 +38,8 @@ tags: description: Endpoints to trigger test cases in sandbox - name: API Tokens description: Endpoints to programmatically manage API tokens + - name: Exchange Rates + description: Endpoints for retrieving cached foreign exchange rates. Rates are cached for approximately 5 minutes and include platform-specific fees. paths: /config: get: @@ -140,6 +142,126 @@ paths: application/json: schema: $ref: '#/components/schemas/Error501' + /exchange-rates: + get: + summary: Get exchange rates + description: | + Retrieve cached exchange rates for currency corridors. Returns FX rates that are cached + for approximately 5 minutes. Rates include fees specific to your platform for authenticated requests. + + **Filtering Options:** + - Filter by source currency to get all available destination corridors + - Filter by specific destination currency or currencies + - Provide a sending amount to get calculated receiving amounts + operationId: getExchangeRates + tags: + - Exchange Rates + security: + - BasicAuth: [] + parameters: + - name: sourceCurrency + in: query + description: Filter by source currency code (e.g., USD) + required: false + schema: + type: string + example: USD + - name: destinationCurrency + in: query + description: Filter by destination currency code(s). Can be repeated for multiple currencies (e.g., &destinationCurrency=INR&destinationCurrency=GBP) + required: false + style: form + explode: true + schema: + type: array + items: + type: string + example: + - INR + - name: sendingAmount + in: query + description: Sending amount in the smallest unit of the source currency (e.g., cents for USD). If no amount is provided, the default is 10000 in the sending currency smallest unit. + required: false + schema: + type: integer + format: int64 + minimum: 0 + default: 10000 + example: 10000 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + description: List of exchange rates matching the filter criteria + items: + $ref: '#/components/schemas/ExchangeRate' + examples: + allRatesFromUSD: + summary: All exchange rates from USD + value: + data: + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: $ + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: INR + decimals: 2 + name: Indian Rupee + symbol: ₹ + destinationPaymentRail: UPI + receivingAmount: 1650000 + exchangeRate: 82.5 + fees: + fixed: 100 + updatedAt: '2025-02-05T12:00:00Z' + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: $ + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: EUR + decimals: 2 + name: Euro + symbol: € + destinationPaymentRail: SEPA_INSTANT + receivingAmount: 18500 + exchangeRate: 0.925 + fees: + fixed: 10 + updatedAt: '2025-02-05T12:00:00Z' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /customers: post: summary: Add a new customer @@ -4113,6 +4235,84 @@ components: type: object description: Additional error details additionalProperties: true + Currency: + type: object + properties: + code: + type: string + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: + type: string + description: Full name of the currency + example: United States Dollar + symbol: + type: string + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + ExchangeRateFees: + type: object + description: Fees associated with an exchange rate + properties: + fixed: + type: integer + format: int64 + description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) + minimum: 0 + example: 100 + ExchangeRate: + type: object + description: Exchange rate information for a currency corridor + required: + - sourceCurrency + - destinationCurrency + - destinationPaymentRail + - receivingAmount + - exchangeRate + - fees + - updatedAt + properties: + sourceCurrency: + $ref: '#/components/schemas/Currency' + sourcePaymentRail: + type: string + description: The payment rail used for the source (e.g., ACCOUNT, RTP) + example: ACCOUNT + sendingAmount: + type: integer + format: int64 + description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. + minimum: 0 + example: 10000 + destinationCurrency: + $ref: '#/components/schemas/Currency' + destinationPaymentRail: + type: string + description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) + example: UPI + receivingAmount: + type: integer + format: int64 + description: The receiving amount in the smallest unit of the destination currency + minimum: 0 + example: 1650000 + exchangeRate: + type: number + description: Number of destination currency units per sending currency unit + exclusiveMinimum: 0 + example: 82.5 + fees: + $ref: '#/components/schemas/ExchangeRateFees' + updatedAt: + type: string + format: date-time + description: Timestamp when this exchange rate was last refreshed + example: '2025-02-05T12:00:00Z' CustomerType: type: string enum: @@ -4537,26 +4737,6 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 CurrencyAmount: type: object required: @@ -4811,6 +4991,10 @@ components: required: - invoice properties: + accountType: + type: string + enum: + - LIGHTNING invoice: type: string description: Invoice for the payment @@ -5376,31 +5560,33 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletInfo' + LightningInfo: + type: object + description: | + Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. + required: + - accountType + properties: + accountType: + type: string + enum: + - LIGHTNING + invoice: + type: string + description: 1-time use lightning bolt11 invoice payout destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + bolt12: + type: string + description: A bolt12 offer which can be reused as a payment destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + lightningAddress: + type: string + description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. + example: john.doe@lightningwallet.com LightningExternalAccountInfo: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - - type: object - description: | - Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. - required: - - accountType - properties: - accountType: - type: string - enum: - - LIGHTNING - invoice: - type: string - description: 1-time use lightning bolt11 invoice payout destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - bolt12: - type: string - description: A bolt12 offer which can be reused as a payment destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - lightningAddress: - type: string - description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. - example: john.doe@lightningwallet.com + - $ref: '#/components/schemas/LightningInfo' SolanaWalletExternalAccountInfo: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' diff --git a/openapi.yaml b/openapi.yaml index 5a660fe2..21c98521 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -38,6 +38,8 @@ tags: description: Endpoints to trigger test cases in sandbox - name: API Tokens description: Endpoints to programmatically manage API tokens + - name: Exchange Rates + description: Endpoints for retrieving cached foreign exchange rates. Rates are cached for approximately 5 minutes and include platform-specific fees. paths: /config: get: @@ -140,6 +142,126 @@ paths: application/json: schema: $ref: '#/components/schemas/Error501' + /exchange-rates: + get: + summary: Get exchange rates + description: | + Retrieve cached exchange rates for currency corridors. Returns FX rates that are cached + for approximately 5 minutes. Rates include fees specific to your platform for authenticated requests. + + **Filtering Options:** + - Filter by source currency to get all available destination corridors + - Filter by specific destination currency or currencies + - Provide a sending amount to get calculated receiving amounts + operationId: getExchangeRates + tags: + - Exchange Rates + security: + - BasicAuth: [] + parameters: + - name: sourceCurrency + in: query + description: Filter by source currency code (e.g., USD) + required: false + schema: + type: string + example: USD + - name: destinationCurrency + in: query + description: Filter by destination currency code(s). Can be repeated for multiple currencies (e.g., &destinationCurrency=INR&destinationCurrency=GBP) + required: false + style: form + explode: true + schema: + type: array + items: + type: string + example: + - INR + - name: sendingAmount + in: query + description: Sending amount in the smallest unit of the source currency (e.g., cents for USD). If no amount is provided, the default is 10000 in the sending currency smallest unit. + required: false + schema: + type: integer + format: int64 + minimum: 0 + default: 10000 + example: 10000 + responses: + '200': + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + description: List of exchange rates matching the filter criteria + items: + $ref: '#/components/schemas/ExchangeRate' + examples: + allRatesFromUSD: + summary: All exchange rates from USD + value: + data: + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: $ + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: INR + decimals: 2 + name: Indian Rupee + symbol: ₹ + destinationPaymentRail: UPI + receivingAmount: 1650000 + exchangeRate: 82.5 + fees: + fixed: 100 + updatedAt: '2025-02-05T12:00:00Z' + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: $ + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: EUR + decimals: 2 + name: Euro + symbol: € + destinationPaymentRail: SEPA_INSTANT + receivingAmount: 18500 + exchangeRate: 0.925 + fees: + fixed: 10 + updatedAt: '2025-02-05T12:00:00Z' + '400': + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error400' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error401' + '500': + description: Internal service error + content: + application/json: + schema: + $ref: '#/components/schemas/Error500' /customers: post: summary: Add a new customer @@ -4113,6 +4235,84 @@ components: type: object description: Additional error details additionalProperties: true + Currency: + type: object + properties: + code: + type: string + description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) + example: USD + name: + type: string + description: Full name of the currency + example: United States Dollar + symbol: + type: string + description: Symbol of the currency + example: $ + decimals: + type: integer + description: Number of decimal places for the currency + minimum: 0 + example: 2 + ExchangeRateFees: + type: object + description: Fees associated with an exchange rate + properties: + fixed: + type: integer + format: int64 + description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) + minimum: 0 + example: 100 + ExchangeRate: + type: object + description: Exchange rate information for a currency corridor + required: + - sourceCurrency + - destinationCurrency + - destinationPaymentRail + - receivingAmount + - exchangeRate + - fees + - updatedAt + properties: + sourceCurrency: + $ref: '#/components/schemas/Currency' + sourcePaymentRail: + type: string + description: The payment rail used for the source (e.g., ACCOUNT, RTP) + example: ACCOUNT + sendingAmount: + type: integer + format: int64 + description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. + minimum: 0 + example: 10000 + destinationCurrency: + $ref: '#/components/schemas/Currency' + destinationPaymentRail: + type: string + description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) + example: UPI + receivingAmount: + type: integer + format: int64 + description: The receiving amount in the smallest unit of the destination currency + minimum: 0 + example: 1650000 + exchangeRate: + type: number + description: Number of destination currency units per sending currency unit + exclusiveMinimum: 0 + example: 82.5 + fees: + $ref: '#/components/schemas/ExchangeRateFees' + updatedAt: + type: string + format: date-time + description: Timestamp when this exchange rate was last refreshed + example: '2025-02-05T12:00:00Z' CustomerType: type: string enum: @@ -4537,26 +4737,6 @@ components: mapping: INDIVIDUAL: '#/components/schemas/IndividualCustomerUpdateRequest' BUSINESS: '#/components/schemas/BusinessCustomerUpdateRequest' - Currency: - type: object - properties: - code: - type: string - description: Three-letter currency code (ISO 4217) for fiat currencies. Some cryptocurrencies may use their own ticker symbols (e.g. "BTC" for Bitcoin, "USDC" for USDC, etc.) - example: USD - name: - type: string - description: Full name of the currency - example: United States Dollar - symbol: - type: string - description: Symbol of the currency - example: $ - decimals: - type: integer - description: Number of decimal places for the currency - minimum: 0 - example: 2 CurrencyAmount: type: object required: @@ -4811,6 +4991,10 @@ components: required: - invoice properties: + accountType: + type: string + enum: + - LIGHTNING invoice: type: string description: Invoice for the payment @@ -5376,31 +5560,33 @@ components: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - $ref: '#/components/schemas/SparkWalletInfo' + LightningInfo: + type: object + description: | + Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. + required: + - accountType + properties: + accountType: + type: string + enum: + - LIGHTNING + invoice: + type: string + description: 1-time use lightning bolt11 invoice payout destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + bolt12: + type: string + description: A bolt12 offer which can be reused as a payment destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + lightningAddress: + type: string + description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. + example: john.doe@lightningwallet.com LightningExternalAccountInfo: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' - - type: object - description: | - Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. - required: - - accountType - properties: - accountType: - type: string - enum: - - LIGHTNING - invoice: - type: string - description: 1-time use lightning bolt11 invoice payout destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - bolt12: - type: string - description: A bolt12 offer which can be reused as a payment destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - lightningAddress: - type: string - description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. - example: john.doe@lightningwallet.com + - $ref: '#/components/schemas/LightningInfo' SolanaWalletExternalAccountInfo: allOf: - $ref: '#/components/schemas/BaseExternalAccountInfo' diff --git a/openapi/README.md b/openapi/README.md index 60d201b9..c2e6f03e 100644 --- a/openapi/README.md +++ b/openapi/README.md @@ -7,8 +7,13 @@ This readme is a continually evolving document meant to provide API design best 3. Make new APIs easy to define --- + ## OpenAPI schema version + This guide uses [OpenAPI schema 3.1](https://spec.openapis.org/oas/v3.1.0.html). + +## Design Guidelines + - Imagine you're teaching a customer how to use our API. How might you structure the API to make it easy to explain and understand? - Since we don't know exactly how customers will use the API, how might we make it flexible? - Can integrators guess how your API works based on how other features work? @@ -49,11 +54,13 @@ openapi/ We version by dates but SDKs still use semver. ### Version Format + - **API Version**: `YYYY-MM-DD` format (e.g., `2025-10-13`) - **Server URL**: Version is included in the path: `https://api.lightspark.com/grid/2025-10-13` - **SDK Version**: Follows semver (`1.0.0`, `1.1.0`, `2.0.0`) ### What's Considered a Breaking Change + - New required field on request - Removing a field from response - Changing a field name @@ -64,6 +71,7 @@ We version by dates but SDKs still use semver. When you release an SDK, Stainless will flag breaking changes. ### What's Not a Breaking Change + - Making a required field optional - Adding a new optional field - Adding a new enum value @@ -71,13 +79,16 @@ When you release an SDK, Stainless will flag breaking changes. - Adding a new response field ### Deprecation Policy + *TBD! BUT initial thoughts* + 1. Mark deprecated endpoints with `deprecated: true` in OpenAPI spec 2. Document deprecation in changelog and SDK release notes -3. Maintain deprecated endpoints for at least X months +3. Maintain deprecated endpoints for at least 6 months 4. Communicate migration path in documentation ### Instead of a breaking change, you can + - Add new optional fields instead of modifying existing ones - Create a new endpoint if behavior must change significantly @@ -86,10 +97,12 @@ When you release an SDK, Stainless will flag breaking changes. ## Naming Conventions ### Resources + - Use **plural nouns** for resource names: `/customers` not `/customer` - Exception: Use singular when there can only be one (e.g., `/config`) ### Identifiers + ID values should be prefixed with the resource type to help users identify the resource type in their system. | Resource | ID Format | Example | @@ -124,6 +137,7 @@ Use a type hint where it makes sense eg startDate, customerId. ### States Resources with lifecycle states (e.g., transactions, quotes, invitations) should document: + 1. All possible states 2. Valid state transitions 3. What triggers each transition @@ -261,9 +275,77 @@ discriminator: BUSINESS: '#/components/schemas/BusinessCustomer' ``` -Also: -- Define the discriminator property as a separate schema with an enum so generated client libraries include an enum class (see `openapi/components/schemas/customers/CustomerType.yaml`, used by `customerType` in `Customer` and `CustomerCreateRequest`). -- Each discriminated schema must include the discriminator field with a single-value enum, because the OpenAPI generator does not reliably infer it from the discriminator (see `IndividualCustomerFields` included by `IndividualCustomer.yaml` and `IndividualCustomerCreateRequest.yaml`, where `customerType` is `enum: [INDIVIDUAL]`). +#### Three-layer discriminator pattern + +We use a three-layer pattern for discriminated unions: + +1. **Shared enum schema** — A standalone schema defining all possible discriminator values (e.g., `CustomerType.yaml` with `enum: [INDIVIDUAL, BUSINESS]`). This generates a reusable enum class in SDKs and is used by query parameters, filters, etc. +2. **Base schema with `$ref` and discriminator** — The base schema references the shared enum and declares the discriminator mapping (e.g., `Customer.yaml` has `customerType: $ref: ./CustomerType.yaml` and a `discriminator` block). +3. **Variant schemas with single-value inline enums** — Each variant redefines the discriminator property with a single-value `enum` (e.g., `IndividualCustomerFields.yaml` has `customerType: enum: [INDIVIDUAL]`). Variants are composed with the base via `allOf`. + +``` +CustomerType.yaml → enum: [INDIVIDUAL, BUSINESS] (shared type) +Customer.yaml → customerType: $ref CustomerType (base, with discriminator) +IndividualCustomerFields → customerType: enum: [INDIVIDUAL] (variant, single-value) +IndividualCustomer.yaml → allOf: [Customer, IndividualCustomerFields] +``` + +#### Why variants need single-value inline enums + +The inline single-value enums in variant schemas serve two purposes: + +- **Python openapi-generator**: Does not reliably use the `discriminator` when deserializing JSON responses. The inline enum ensures each variant is self-describing and can be distinguished without discriminator logic. +- **Stainless Kotlin SDK**: Uses structural matching (`tryDeserialize` in sequence) rather than discriminator-based deserialization. Variants that are structurally identical (e.g., wallet types that all have `{ accountType, address }`) can only be distinguished if each has its own single-value enum that rejects other values during deserialization. + +#### Why this causes `allOf` type conflicts in Stainless + +When `allOf` merges a base schema (where the discriminator uses a `$ref` to the shared enum) with a variant schema (where the discriminator is an inline single-value enum), Stainless generates two different types for the same property. This causes Kotlin compilation errors like: + +``` +None of the following functions can be called with the arguments supplied: + customerType(JsonField): Builder + customerType(BusinessCustomerFields.CustomerType): Builder +``` + +#### The fix: Stainless transforms remove the property from base schemas + +To resolve the conflict, Stainless transforms in `.stainless/stainless.yml` remove the discriminator property from the **base** schemas' `properties` (not from the variants). This makes the variant's inline enum the sole definition of the property in the `allOf` composition, eliminating the type conflict. + +```yaml +# In .stainless/stainless.yml +openapi: + transformations: + - command: remove + reason: Remove accountType $ref from base to avoid allOf type conflicts + args: + target: + - "$.components.schemas.BaseExternalAccountInfo.properties" + keys: ["accountType"] +``` + +**Important**: We remove from the **base**, not the variant, because: + +- Removing from variants would strip the single-value enum, making structurally identical schemas (e.g., all wallet types) indistinguishable during Stainless deserialization. +- Removing from the base preserves each variant's distinct enum value for structural matching. + +The original OpenAPI spec is unchanged — transforms only affect what Stainless sees during SDK generation. The Python openapi-generator and documentation continue to use the full spec with both definitions. + +#### Required fields for discriminator properties + +The discriminator property must be listed in `required` in the **variant** schemas (or their fields/info schemas). Since the Stainless transform removes the property from the base, the variant is the sole owner — if it's not `required` there, the generated SDK treats it as optional, causing test failures where the discriminator value is missing from roundtripped objects. + +```yaml +# In the variant schema — discriminator MUST be in required +- type: object + required: + - accountId + - destinationType # ← must be here + properties: + destinationType: + type: string + enum: + - ACCOUNT +``` ### Composition @@ -317,6 +399,7 @@ code: | 204 | Resource deleted successfully (no content) | ### Error Status Codes + Generally 4xx errors indicate an issue with the integrators request and 5xx errors indicate an issue with our services. Our client libraries will automatically retry 5xx responses. | Code | Meaning | When to Use | @@ -446,8 +529,36 @@ try { } } ``` + Stainless also generates [API reference SDK examples](https://www.stainless.com/docs/guides/integrate-docs#how-stainless-generates-sdk-code-snippets) for our Mintlify documentation +### Stainless OpenAPI Transforms + +Stainless transforms modify the OpenAPI spec during SDK generation without editing the source file. They are configured in `.stainless/stainless.yml` under `openapi.transformations`. + +We use transforms to resolve `allOf` type conflicts caused by the [three-layer discriminator pattern](#three-layer-discriminator-pattern). See that section for the full explanation. + +#### When adding a new `oneOf` with discriminator + +If you add a new discriminated `oneOf` that follows the three-layer pattern (base with `$ref` + variants with inline enums composed via `allOf`), you must also add a `remove` transform in `.stainless/stainless.yml` to strip the discriminator property from the base schema: + +1. Add the base schema to the appropriate `remove` transform target list (or create a new transform) +2. Ensure variant schemas have the discriminator property in both `properties` (with single-value enum) and `required` +3. Re-generate the SDK and verify tests pass + +#### Available transform commands + + +| Command | Use case | +| -------------------- | ----------------------------------------------------------------------- | +| `remove` with `keys` | Remove a property from a schema's `properties` object | +| `update` | Replace a property definition (e.g., inline enum → `$ref`) | +| `append` | Add a new field (fails if it already exists — good for temporary fixes) | +| `merge` | Add permanent metadata like `x-stainless-naming` | + + +See [Stainless transforms documentation](https://www.stainless.com/docs/guides/transforms/) for the full reference. + --- ## Redocly CLI @@ -465,3 +576,4 @@ npx @redocly/cli bundle openapi/openapi.yaml -o openapi.yaml - [Hide APIs for internal use](https://redocly.com/docs/cli/guides/hide-apis) - Lint rules configured in `.redocly.yaml` - Ignore specific lint rules in `.redocly.lint-ignore.yaml` + diff --git a/openapi/components/schemas/common/LightningInfo.yaml b/openapi/components/schemas/common/LightningInfo.yaml new file mode 100644 index 00000000..ecdc9b6d --- /dev/null +++ b/openapi/components/schemas/common/LightningInfo.yaml @@ -0,0 +1,22 @@ +type: object +description: > + Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. +required: + - accountType +properties: + accountType: + type: string + enum: + - LIGHTNING + invoice: + type: string + description: 1-time use lightning bolt11 invoice payout destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + bolt12: + type: string + description: A bolt12 offer which can be reused as a payment destination + example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs + lightningAddress: + type: string + description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. + example: john.doe@lightningwallet.com diff --git a/openapi/components/schemas/common/PaymentLightningInvoiceInfo.yaml b/openapi/components/schemas/common/PaymentLightningInvoiceInfo.yaml index 2750adf3..82d60122 100644 --- a/openapi/components/schemas/common/PaymentLightningInvoiceInfo.yaml +++ b/openapi/components/schemas/common/PaymentLightningInvoiceInfo.yaml @@ -4,6 +4,10 @@ allOf: required: - invoice properties: + accountType: + type: string + enum: + - LIGHTNING invoice: type: string description: Invoice for the payment diff --git a/openapi/components/schemas/exchange_rates/ExchangeRate.yaml b/openapi/components/schemas/exchange_rates/ExchangeRate.yaml new file mode 100644 index 00000000..1f20276d --- /dev/null +++ b/openapi/components/schemas/exchange_rates/ExchangeRate.yaml @@ -0,0 +1,47 @@ +type: object +description: Exchange rate information for a currency corridor +required: + - sourceCurrency + - destinationCurrency + - destinationPaymentRail + - receivingAmount + - exchangeRate + - fees + - updatedAt +properties: + sourceCurrency: + $ref: ../common/Currency.yaml + sourcePaymentRail: + type: string + description: The payment rail used for the source (e.g., ACCOUNT, RTP) + example: ACCOUNT + sendingAmount: + type: integer + format: int64 + description: The sending amount in the smallest unit of the source currency (e.g., cents for USD). Echoed back from the request if provided. + minimum: 0 + example: 10000 + destinationCurrency: + $ref: ../common/Currency.yaml + destinationPaymentRail: + type: string + description: The payment rail used for the destination (e.g., UPI, SEPA_INSTANT, MOBILE_MONEY, FASTER_PAYMENTS) + example: UPI + receivingAmount: + type: integer + format: int64 + description: The receiving amount in the smallest unit of the destination currency + minimum: 0 + example: 1650000 + exchangeRate: + type: number + description: Number of destination currency units per sending currency unit + exclusiveMinimum: 0 + example: 82.50 + fees: + $ref: ./ExchangeRateFees.yaml + updatedAt: + type: string + format: date-time + description: Timestamp when this exchange rate was last refreshed + example: "2025-02-05T12:00:00Z" diff --git a/openapi/components/schemas/exchange_rates/ExchangeRateFees.yaml b/openapi/components/schemas/exchange_rates/ExchangeRateFees.yaml new file mode 100644 index 00000000..3c58d7c5 --- /dev/null +++ b/openapi/components/schemas/exchange_rates/ExchangeRateFees.yaml @@ -0,0 +1,9 @@ +type: object +description: Fees associated with an exchange rate +properties: + fixed: + type: integer + format: int64 + description: Fixed fee in the smallest unit of the sending currency (e.g., cents for USD) + minimum: 0 + example: 100 diff --git a/openapi/components/schemas/external_accounts/LightningExternalAccountInfo.yaml b/openapi/components/schemas/external_accounts/LightningExternalAccountInfo.yaml index 754c07c1..9af277bb 100644 --- a/openapi/components/schemas/external_accounts/LightningExternalAccountInfo.yaml +++ b/openapi/components/schemas/external_accounts/LightningExternalAccountInfo.yaml @@ -1,24 +1,3 @@ allOf: - $ref: ./BaseExternalAccountInfo.yaml - - type: object - description: > - Lightning payment destination. Exactly one of `invoice`, `bolt12`, or `lightningAddress` must be provided. - required: - - accountType - properties: - accountType: - type: string - enum: - - LIGHTNING - invoice: - type: string - description: 1-time use lightning bolt11 invoice payout destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - bolt12: - type: string - description: A bolt12 offer which can be reused as a payment destination - example: lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs - lightningAddress: - type: string - description: A lightning address which can be used as a payment destination. Note that for UMA addresses, no external account is needed. You can use the UMA address directly as a destination. - example: john.doe@lightningwallet.com + - $ref: ../common/LightningInfo.yaml diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 822bbce7..45d66a13 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -35,6 +35,10 @@ tags: description: Endpoints to trigger test cases in sandbox - name: API Tokens description: Endpoints to programmatically manage API tokens + - name: Exchange Rates + description: >- + Endpoints for retrieving cached foreign exchange rates. Rates are cached + for approximately 5 minutes and include platform-specific fees. servers: - url: https://api.lightspark.com/grid/2025-10-13 description: Production server @@ -84,6 +88,8 @@ components: paths: /config: $ref: paths/platform/config.yaml + /exchange-rates: + $ref: paths/exchange-rates/exchange_rates.yaml /customers: $ref: paths/customers/customers.yaml /customers/{customerId}: diff --git a/openapi/paths/exchange-rates/exchange_rates.yaml b/openapi/paths/exchange-rates/exchange_rates.yaml new file mode 100644 index 00000000..8fdb97f3 --- /dev/null +++ b/openapi/paths/exchange-rates/exchange_rates.yaml @@ -0,0 +1,122 @@ +get: + summary: Get exchange rates + description: | + Retrieve cached exchange rates for currency corridors. Returns FX rates that are cached + for approximately 5 minutes. Rates include fees specific to your platform for authenticated requests. + + **Filtering Options:** + - Filter by source currency to get all available destination corridors + - Filter by specific destination currency or currencies + - Provide a sending amount to get calculated receiving amounts + + operationId: getExchangeRates + tags: + - Exchange Rates + security: + - BasicAuth: [] + parameters: + - name: sourceCurrency + in: query + description: Filter by source currency code (e.g., USD) + required: false + schema: + type: string + example: USD + - name: destinationCurrency + in: query + description: >- + Filter by destination currency code(s). Can be repeated for multiple currencies + (e.g., &destinationCurrency=INR&destinationCurrency=GBP) + required: false + style: form + explode: true + schema: + type: array + items: + type: string + example: + - INR + - name: sendingAmount + in: query + description: Sending amount in the smallest unit of the source currency (e.g., cents for USD). If no amount is provided, the default is 10000 in the sending currency smallest unit. + required: false + schema: + type: integer + format: int64 + minimum: 0 + default: 10000 + example: 10000 + responses: + "200": + description: Successful operation + content: + application/json: + schema: + type: object + required: + - data + properties: + data: + type: array + description: List of exchange rates matching the filter criteria + items: + $ref: ../../components/schemas/exchange_rates/ExchangeRate.yaml + examples: + allRatesFromUSD: + summary: All exchange rates from USD + value: + data: + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: "$" + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: INR + decimals: 2 + name: Indian Rupee + symbol: "₹" + destinationPaymentRail: UPI + receivingAmount: 1650000 + exchangeRate: 82.50 + fees: + fixed: 100 + updatedAt: "2025-02-05T12:00:00Z" + - sourceCurrency: + code: USD + decimals: 2 + name: US Dollar + symbol: "$" + sourcePaymentRail: RTP + sendingAmount: 10000 + destinationCurrency: + code: EUR + decimals: 2 + name: Euro + symbol: "€" + destinationPaymentRail: SEPA_INSTANT + receivingAmount: 18500 + exchangeRate: 0.925 + fees: + fixed: 10 + updatedAt: "2025-02-05T12:00:00Z" + "400": + description: Bad request - Invalid parameters + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error400.yaml + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error401.yaml + "500": + description: Internal service error + content: + application/json: + schema: + $ref: ../../components/schemas/errors/Error500.yaml