From c97f7ed6226eb4d76abc06b7e9602e66918e45ad Mon Sep 17 00:00:00 2001 From: Peng Ying Date: Sat, 24 Jan 2026 14:35:45 -0800 Subject: [PATCH] feat: add Stainless SDK config and misc cleanups - Add .stainless/stainless.yml and workspace.json - Add AllErrors schema and remove retry quotes endpoint - Update .redocly.lint-ignore.yaml - Fix UltimateBeneficialOwner phone format - Fix internal_accounts ref path --- .github/workflows/stainless-action.yml | 59 +++ .redocly.lint-ignore.yaml | 5 + .stainless/stainless.yml | 341 ++++++++++++++++++ .stainless/workspace.json | 10 + mintlify/openapi.yaml | 36 +- openapi.yaml | 36 +- .../customers/CustomerCreateRequest.yaml | 1 + .../customers/UltimateBeneficialOwner.yaml | 2 +- openapi/openapi.yaml | 14 +- .../customers/customers_{customerId}.yaml | 2 +- .../internal-accounts/internal_accounts.yaml | 2 +- 11 files changed, 496 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/stainless-action.yml create mode 100644 .stainless/stainless.yml create mode 100644 .stainless/workspace.json diff --git a/.github/workflows/stainless-action.yml b/.github/workflows/stainless-action.yml new file mode 100644 index 0000000..0de4a72 --- /dev/null +++ b/.github/workflows/stainless-action.yml @@ -0,0 +1,59 @@ +name: Build SDKs for pull request + +env: + OAS_PATH: ./openapi.yaml + STAINLESS_ORG: lightspark + STAINLESS_PROJECT: grid + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - closed + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + preview: + if: github.event.action != 'closed' + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Run preview builds + uses: stainless-api/upload-openapi-spec-action/preview@v1 + with: + org: ${{ env.STAINLESS_ORG }} + project: ${{ env.STAINLESS_PROJECT }} + oas_path: ${{ env.OAS_PATH }} + + merge: + if: github.event.action == 'closed' && github.event.pull_request.merged == true + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 2 + + - name: Run merge build + uses: stainless-api/upload-openapi-spec-action/merge@v1 + with: + org: ${{ env.STAINLESS_ORG }} + project: ${{ env.STAINLESS_PROJECT }} + oas_path: ${{ env.OAS_PATH }} diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml index 0329536..5a21589 100644 --- a/.redocly.lint-ignore.yaml +++ b/.redocly.lint-ignore.yaml @@ -1,6 +1,11 @@ # This file instructs Redocly's linter to ignore the rules contained for specific parts of your API. # See https://redocly.com/docs/cli/ for more information. openapi.yaml: + no-unused-components: + - '#/components/schemas/AllErrors' + no-required-schema-properties-undefined: + - '#/components/schemas/QuoteSource/required/0' + - '#/components/schemas/Quote/properties/destination/required/0' no-invalid-media-type-examples: - >- #/paths/~1customers~1external-accounts/post/requestBody/content/application~1json/schema diff --git a/.stainless/stainless.yml b/.stainless/stainless.yml new file mode 100644 index 0000000..5817a8a --- /dev/null +++ b/.stainless/stainless.yml @@ -0,0 +1,341 @@ +# yaml-language-server: $schema=https://app.stainless.com/config.schema.json + +# The main edition for the config, see the [docs] for more information. +# +# [docs]: https://www.stainless.com/docs/reference/editions +edition: 2025-10-10 + +organization: + # Name of your organization or company, used to determine the name of the client + # and headings. + name: grid + # Link to your API documentation. + docs: '' + # Contact email for bug reports, questions, and support requests. + contact: support@lightspark.com + +# `targets` define the output targets and their customization options, such as +# whether to emit the Node SDK and what its package name should be. +targets: + typescript: + # The edition for this target, see the [docs] for more information. + # + # [docs]: https://www.stainless.com/docs/reference/editions + edition: typescript.2025-10-10 + package_name: grid + production_repo: null + publish: + npm: false + kotlin: + edition: kotlin.2025-10-08 + reverse_domain: com.grid.api + production_repo: null + publish: + maven: false + +# `environments` are a map of the name of the environment (e.g. "sandbox", +# "production") to the corresponding url to use. +environments: + production: https://api.lightspark.com/grid/2025-10-13 + +# `resources` define the structure and organization for your API, such as how +# methods and models are grouped together and accessed. See the [configuration +# guide] for more information. +# +# [configuration guide]: https://www.stainless.com/docs/guides/configure#resources +resources: + config: + # Configure the methods defined in this resource. Each key in the object is the + # name of the method and the value is either an endpoint (for example, `get /foo`) + # or an object with more detail. + # + # [reference]: https://www.stainless.com/docs/reference/config#method + # Configure the models--named types--defined in the resource. Each key in the + # object is the name of the model and the value is either the name of a schema in + # `#/components/schemas` or an object with more detail. + # + # [reference]: https://www.stainless.com/docs/reference/config#model + models: + customer_info_field_name: '#/components/schemas/CustomerInfoFieldName' + platform_currency_config: '#/components/schemas/PlatformCurrencyConfig' + platform_config: '#/components/schemas/PlatformConfig' + methods: + retrieve: get /config + update: patch /config + + customers: + models: + customer_type: '#/components/schemas/CustomerType' + individual_customer: '#/components/schemas/IndividualCustomer' + customer: '#/components/schemas/Customer' + address: '#/components/schemas/Address' + ultimate_beneficial_owner: '#/components/schemas/UltimateBeneficialOwner' + business_customer: '#/components/schemas/BusinessCustomer' + business_customer_fields: "#/components/schemas/BusinessCustomerFields" + business_info: "#/components/schemas/BusinessInfo" + individual_customer_fields: "#/components/schemas/IndividualCustomerFields" + customer_one_of: "#/components/schemas/CustomerOneOf" + customer_create: "#/components/schemas/CustomerCreateRequest" + customer_update: "#/components/schemas/CustomerUpdateRequest" + methods: + create: + endpoint: post /customers + body_param_name: CreateCustomerRequest + list: get /customers + retrieve: get /customers/{customerId} + update: + endpoint: patch /customers/{customerId} + body_param_name: UpdateCustomerRequest + delete: delete /customers/{customerId} + get_kyc_link: get /customers/kyc-link + list_internal_accounts: get /customers/internal-accounts + # Subresources define resources that are nested within another for more powerful + # logical groupings, e.g. `cards.payments`. + subresources: + external_accounts: + models: + us_account_info: '#/components/schemas/UsAccountInfo' + pix_account_info: '#/components/schemas/PixAccountInfo' + iban_account_info: '#/components/schemas/IbanAccountInfo' + upi_account_info: '#/components/schemas/UpiAccountInfo' + spark_wallet_info: '#/components/schemas/SparkWalletInfo' + solana_wallet_info: '#/components/schemas/SolanaWalletInfo' + tron_wallet_info: '#/components/schemas/TronWalletInfo' + polygon_wallet_info: '#/components/schemas/PolygonWalletInfo' + clabe_account_info: '#/components/schemas/ClabeAccountInfo' + base_wallet_info: '#/components/schemas/BaseWalletInfo' + individual_beneficiary: '#/components/schemas/IndividualBeneficiary' + business_beneficiary: '#/components/schemas/BusinessBeneficiary' + external_account: '#/components/schemas/ExternalAccount' + external_account_create: '#/components/schemas/ExternalAccountCreateRequest' + lightning_external_account_info: "#/components/schemas/LightningExternalAccountInfo" + ngn_account_external_account_info: "#/components/schemas/NgnAccountExternalAccountInfo" + base_external_account_info: "#/components/schemas/BaseExternalAccountInfo" + base_beneficiary: "#/components/schemas/BaseBeneficiary" + beneficiary_one_of: "#/components/schemas/BeneficiaryOneOf" + external_account_info_one_of: "#/components/schemas/ExternalAccountInfoOneOf" + methods: + list: get /customers/external-accounts + create: post /customers/external-accounts + bulk: + methods: + upload_csv: post /customers/bulk/csv + get_job_status: get /customers/bulk/jobs/{jobId} + + platform: + methods: + list_internal_accounts: + endpoint: get /platform/internal-accounts + paginated: false + subresources: + external_accounts: + methods: + list: + endpoint: get /platform/external-accounts + paginated: false + create: post /platform/external-accounts + + plaid: + methods: + create_link_token: post /plaid/link-tokens + submit_public_token: post /plaid/callback/{plaid_link_token} + + transfer_in: + models: + transaction: '#/components/schemas/Transaction' + base_transaction_destination: "#/components/schemas/BaseTransactionDestination" + methods: + create: post /transfer-in + + transfer_out: + methods: + create: post /transfer-out + + receiver: + models: + counterparty_field_definition: '#/components/schemas/CounterpartyFieldDefinition' + lookup_response: '#/components/schemas/ReceiverLookupResponse' + methods: + lookup_uma: get /receiver/uma/{receiverUmaAddress} + lookup_external_account: get /receiver/external-account/{accountId} + + quotes: + models: + currency: '#/components/schemas/Currency' + payment_instructions: '#/components/schemas/PaymentInstructions' + outgoing_rate_details: '#/components/schemas/OutgoingRateDetails' + quote: '#/components/schemas/Quote' + base_payment_account_info: "#/components/schemas/BasePaymentAccountInfo" + base_quote_source: "#/components/schemas/BaseQuoteSource" + quote_source_one_of: "#/components/schemas/QuoteSourceOneOf" + base_destination: "#/components/schemas/BaseDestination" + quote_destination_one_of: "#/components/schemas/QuoteDestinationOneOf" + methods: + retrieve: get /quotes/{quoteId} + create: post /quotes + list: get /quotes + execute: post /quotes/{quoteId}/execute + + transactions: + models: + transaction_type: '#/components/schemas/TransactionType' + incoming_transaction: '#/components/schemas/IncomingTransaction' + transaction_status: '#/components/schemas/TransactionStatus' + base_transaction_source: "#/components/schemas/BaseTransactionSource" + transaction_source_one_of: "#/components/schemas/TransactionSourceOneOf" + methods: + list: get /transactions + retrieve: get /transactions/{transactionId} + approve: post /transactions/{transactionId}/approve + reject: post /transactions/{transactionId}/reject + + webhooks: + methods: + send_test: post /webhooks/test + + invitations: + models: + currency_amount: '#/components/schemas/CurrencyAmount' + uma_invitation: '#/components/schemas/UmaInvitation' + methods: + create: post /invitations + retrieve: get /invitations/{invitationCode} + claim: post /invitations/{invitationCode}/claim + cancel: post /invitations/{invitationCode}/cancel + + sandbox: + methods: + send_funds: post /sandbox/send + subresources: + uma: + methods: + receive_payment: post /sandbox/uma/receive + internal_accounts: + models: + internal_account: '#/components/schemas/InternalAccount' + methods: + fund: post /sandbox/internal-accounts/{accountId}/fund + + uma_providers: + methods: + list: get /uma-providers + + tokens: + models: + permission: '#/components/schemas/Permission' + api_token: '#/components/schemas/ApiToken' + methods: + create: post /tokens + list: get /tokens + retrieve: get /tokens/{tokenId} + delete: delete /tokens/{tokenId} + +settings: + # All generated integration tests that hit the prism mock http server are marked + # as skipped. Removing this setting or setting it to false enables tests, but + # doing so may result in test failures due to bugs in the test server. + # + # [prism mock http server]: https://stoplight.io/open-source/prism + disable_mock_tests: true + license: Apache-2.0 + +# `client_settings` define settings for the API client, such as extra constructor +# arguments (used for authentication), retry behavior, idempotency, etc. +client_settings: + opts: + username: + type: string + nullable: false + auth: + security_scheme: BasicAuth + role: username + description: API token authentication using format `:` + read_env: GRID_USERNAME + password: + type: string + nullable: false + auth: + security_scheme: BasicAuth + role: password + description: API token authentication using format `:` + read_env: GRID_PASSWORD + webhook_signature: + type: string + nullable: true + auth: + security_scheme: WebhookSignature + description: | + Secp256r1 (P-256) asymmetric signature of the webhook payload, which can be used to verify that the webhook was sent by Grid. + + To verify the signature: + 1. Get the Grid public key provided to you during integration + 2. Decode the base64 signature from the header + 3. Create a SHA-256 hash of the request body + 4. Verify the signature using the public key and the hash + + If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. + read_env: GRID_WEBHOOK_SIGNATURE + +# `readme` is used to configure the code snippets that will be rendered in the +# README.md of various SDKs. In particular, you can change the `headline` +# snippet's endpoint and the arguments to call it with. +readme: + example_requests: + default: + type: request + endpoint: get /config + params: {} + headline: + type: request + endpoint: get /config + params: {} + pagination: + type: request + endpoint: get /customers + params: {} + +pagination: + - name: default_pagination + type: cursor + request: + cursor: + type: string + x-stainless-pagination-property: + purpose: next_cursor_param + response: + data: + type: array + items: + type: object + nextCursor: + type: string + x-stainless-pagination-property: + purpose: next_cursor_field + hasMore: + type: boolean + x-stainless-pagination-property: + purpose: has_next_page + totalCount: + type: integer + +openapi: + code_samples: mintlify + transformations: + - command: splitSchemasByEnumProperty + reason: Split error schemas with multiple enum values into distinct ones + args: + unionPath: AllErrors + enumProperty: code + +errors: + union: + source: AllErrors + discriminator: code + status_property: status + +codeflow: + detect_breaking_changes: true +diagnostics: + ignored: + Schema/ObjectHasNoProperties: + - pagination.0.response.data.items diff --git a/.stainless/workspace.json b/.stainless/workspace.json new file mode 100644 index 0000000..1af9854 --- /dev/null +++ b/.stainless/workspace.json @@ -0,0 +1,10 @@ +{ + "project": "grid", + "openapi_spec": "../mintlify/openapi.yaml", + "stainless_config": "stainless.yml", + "targets": { + "typescript": { + "output_path": "../sdks/grid-typescript" + } + } +} diff --git a/mintlify/openapi.yaml b/mintlify/openapi.yaml index ca5146a..5a660fe 100644 --- a/mintlify/openapi.yaml +++ b/mintlify/openapi.yaml @@ -485,7 +485,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: '#/components/schemas/Error410' '500': description: Internal service error content: @@ -3786,6 +3786,18 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. schemas: + AllErrors: + anyOf: + - $ref: '#/components/schemas/Error400' + - $ref: '#/components/schemas/Error401' + - $ref: '#/components/schemas/Error403' + - $ref: '#/components/schemas/Error404' + - $ref: '#/components/schemas/Error409' + - $ref: '#/components/schemas/Error410' + - $ref: '#/components/schemas/Error412' + - $ref: '#/components/schemas/Error424' + - $ref: '#/components/schemas/Error500' + - $ref: '#/components/schemas/Error501' CustomerInfoFieldName: type: string enum: @@ -4223,7 +4235,7 @@ components: phoneNumber: type: string description: Phone number of the individual in E.164 format - example: 5555555555 + example: '+5555555555' pattern: ^\+[1-9]\d{1,14}$ taxId: type: string @@ -4366,6 +4378,7 @@ components: customerType: $ref: '#/components/schemas/CustomerType' umaAddress: + type: string description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. example: $john.doe@uma.domain.com discriminator: @@ -4462,18 +4475,33 @@ components: type: object description: Additional error details additionalProperties: true - Error: + Error410: type: object + required: + - message + - status + - code properties: + status: + type: integer + enum: + - 410 + description: HTTP status code code: type: string - description: Error code + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | + enum: + - CUSTOMER_DELETED message: type: string description: Error message details: type: object description: Additional error details + additionalProperties: true IndividualCustomerUpdateRequest: allOf: - $ref: '#/components/schemas/CustomerUpdateRequest' diff --git a/openapi.yaml b/openapi.yaml index ca5146a..5a660fe 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -485,7 +485,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Error' + $ref: '#/components/schemas/Error410' '500': description: Internal service error content: @@ -3786,6 +3786,18 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. schemas: + AllErrors: + anyOf: + - $ref: '#/components/schemas/Error400' + - $ref: '#/components/schemas/Error401' + - $ref: '#/components/schemas/Error403' + - $ref: '#/components/schemas/Error404' + - $ref: '#/components/schemas/Error409' + - $ref: '#/components/schemas/Error410' + - $ref: '#/components/schemas/Error412' + - $ref: '#/components/schemas/Error424' + - $ref: '#/components/schemas/Error500' + - $ref: '#/components/schemas/Error501' CustomerInfoFieldName: type: string enum: @@ -4223,7 +4235,7 @@ components: phoneNumber: type: string description: Phone number of the individual in E.164 format - example: 5555555555 + example: '+5555555555' pattern: ^\+[1-9]\d{1,14}$ taxId: type: string @@ -4366,6 +4378,7 @@ components: customerType: $ref: '#/components/schemas/CustomerType' umaAddress: + type: string description: Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. This is an optional identifier to route payments to the customer. example: $john.doe@uma.domain.com discriminator: @@ -4462,18 +4475,33 @@ components: type: object description: Additional error details additionalProperties: true - Error: + Error410: type: object + required: + - message + - status + - code properties: + status: + type: integer + enum: + - 410 + description: HTTP status code code: type: string - description: Error code + description: | + | Error Code | Description | + |------------|-------------| + | CUSTOMER_DELETED | Customer has been permanently deleted | + enum: + - CUSTOMER_DELETED message: type: string description: Error message details: type: object description: Additional error details + additionalProperties: true IndividualCustomerUpdateRequest: allOf: - $ref: '#/components/schemas/CustomerUpdateRequest' diff --git a/openapi/components/schemas/customers/CustomerCreateRequest.yaml b/openapi/components/schemas/customers/CustomerCreateRequest.yaml index e0e7555..a5f1782 100644 --- a/openapi/components/schemas/customers/CustomerCreateRequest.yaml +++ b/openapi/components/schemas/customers/CustomerCreateRequest.yaml @@ -10,6 +10,7 @@ properties: customerType: $ref: ./CustomerType.yaml umaAddress: + type: string description: >- Optional UMA address identifier. If not provided during customer creation, one will be generated by the system. If provided during customer update, the UMA address will be updated to the provided value. This is an optional identifier to route payments to the customer. diff --git a/openapi/components/schemas/customers/UltimateBeneficialOwner.yaml b/openapi/components/schemas/customers/UltimateBeneficialOwner.yaml index 388a3c3..4cee1fe 100644 --- a/openapi/components/schemas/customers/UltimateBeneficialOwner.yaml +++ b/openapi/components/schemas/customers/UltimateBeneficialOwner.yaml @@ -15,7 +15,7 @@ properties: phoneNumber: type: string description: Phone number of the individual in E.164 format - example: +5555555555 + example: '+5555555555' pattern: '^\+[1-9]\d{1,14}$' taxId: type: string diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index d69dfb1..822bbce 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -68,7 +68,19 @@ components: If the signature verification succeeds, the webhook is authentic. If not, it should be rejected. - + schemas: + AllErrors: + anyOf: + - $ref: components/schemas/errors/Error400.yaml + - $ref: components/schemas/errors/Error401.yaml + - $ref: components/schemas/errors/Error403.yaml + - $ref: components/schemas/errors/Error404.yaml + - $ref: components/schemas/errors/Error409.yaml + - $ref: components/schemas/errors/Error410.yaml + - $ref: components/schemas/errors/Error412.yaml + - $ref: components/schemas/errors/Error424.yaml + - $ref: components/schemas/errors/Error500.yaml + - $ref: components/schemas/errors/Error501.yaml paths: /config: $ref: paths/platform/config.yaml diff --git a/openapi/paths/customers/customers_{customerId}.yaml b/openapi/paths/customers/customers_{customerId}.yaml index e3ce584..d2af272 100644 --- a/openapi/paths/customers/customers_{customerId}.yaml +++ b/openapi/paths/customers/customers_{customerId}.yaml @@ -169,7 +169,7 @@ delete: content: application/json: schema: - $ref: ../../components/schemas/common/Error.yaml + $ref: ../../components/schemas/errors/Error410.yaml '500': description: Internal service error content: diff --git a/openapi/paths/internal-accounts/internal_accounts.yaml b/openapi/paths/internal-accounts/internal_accounts.yaml index 2e47010..94b3334 100644 --- a/openapi/paths/internal-accounts/internal_accounts.yaml +++ b/openapi/paths/internal-accounts/internal_accounts.yaml @@ -54,7 +54,7 @@ get: type: array description: List of internal accounts matching the filter criteria items: - $ref: ../../components/schemas/users/InternalAccount.yaml + $ref: ../../components/schemas/customers/InternalAccount.yaml hasMore: type: boolean description: Indicates if more results are available beyond this page