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

# Dynamic Forms

> Interpret, complete, validate, and submit schemas returned by Blockradar API flows.

## In a nutshell

Some Blockradar API flows return a Dynamic Form Schema when an operation needs
more information. The schema describes the object that your integration must
produce.

```text theme={null}
Input:       The schema returned by a requirements or discovery request
Values:      Data collected for the schema's properties
Output:      A JSON object that validates against the schema
Destination: The request property documented by the API operation
```

Your integration:

1. receives the JSON Schema;
2. determines which fields apply to the current values;
3. obtains values from a user or automated workflow;
4. loads choices and resolves fields when their dependencies are ready;
5. builds and validates the completed object; and
6. submits that object as documented by the API guide for the operation.

Do not submit the completed object back to a requirements or discovery
endpoint. Submit it in the request property documented by the next API
operation, such as `paymentMethodData` or `additionalData`.

## How Dynamic Forms work

A Dynamic Form Schema is a JSON object that describes the fields to collect. It
uses standard JSON Schema keywords and documented `x-*` properties for choices,
files, and fields whose values come from another API call.

Do not hard-code fields from an earlier response. Use the schema returned for
the current operation.

Some fields apply only when another value selects a particular `oneOf` or
`if`/`then`/`else` branch. Collect, validate, and submit only the fields in the
selected branch. When a value selects a different branch, remove values that no
longer apply.

Repeat these steps as values change:

```text theme={null}
Receive schema
    ↓
Find the fields that apply
    ↓
Collect or generate available values
    ↓
Load choices and resolve fields when their required inputs are valid
    ↓
Check again which fields apply
    ↺ Repeat until all required values are available
    ↓
Validate the completed object
    ↓
Submit it as documented by the API guide
```

## Recipes

### Collect a basic object

For a schema with no conditions or API-driven fields:

1. Read the field names from `properties`.
2. Read required field names from `required`.
3. Collect values using the declared types and validation rules.
4. Validate the completed object against the schema.
5. Submit the object in the request property documented by the API guide.

For example, this schema:

```json theme={null}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "accountNumber": {
      "type": "string",
      "pattern": "^[0-9]{10}$"
    },
    "bankCode": {
      "type": "string"
    }
  },
  "required": ["accountNumber", "bankCode"]
}
```

produces an object with the same property names and types:

```json theme={null}
{
  "accountNumber": "0123456789",
  "bankCode": "999"
}
```

### Switch between conditional fields

An object-level `oneOf` can describe different sets of fields. Each branch uses
a required `type.const` value as its selector:

```json theme={null}
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "oneOf": [
    {
      "title": "Bank account",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "type": { "type": "string", "const": "bank" },
        "accountNumber": { "type": "string" }
      },
      "required": ["type", "accountNumber"]
    },
    {
      "title": "Mobile money",
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "type": { "type": "string", "const": "mobile_money" },
        "phoneNumber": { "type": "string" }
      },
      "required": ["type", "phoneNumber"]
    }
  ]
}
```

When `type` is `"mobile_money"`, collect and submit only that branch:

```json theme={null}
{
  "type": "mobile_money",
  "phoneNumber": "+2348012345678"
}
```

If `type` changes to `"bank"`, remove `phoneNumber` and collect
`accountNumber`.

### Complete Withdraw Fiat requirements

1. [Get the payment-method requirements](/en/api-reference/withdraw-fiat/payment-method-requirements-v2)
   from `GET /v2/wallets/{id}/withdraw/fiat/payment-method-requirements`.
2. Collect the fields in `data.schema`.
3. When a field contains `x-resolution`, call it after its dependencies are
   valid, as described below.
4. Validate the completed object.
5. Send it as `paymentMethodData` when you
   [get a quote](/en/api-reference/withdraw-fiat/master-wallet-get-quote-v2) or
   [execute the withdrawal](/en/api-reference/withdraw-fiat/master-wallet-execute-v2).

```json theme={null}
{
  "assetId": "ae455f23-3824-4125-baab-d158315cbcbd",
  "amount": "100.00",
  "currency": "NGN",
  "paymentMethod": "bank_transfer",
  "paymentMethodData": {
    "institutionIdentifier": "999",
    "accountIdentifier": "0123456789",
    "accountName": "Ada Okafor"
  }
}
```

`accountName` is included because the resolution returned it. The field is
optional in this example because it is not listed in the schema's `required`
array.

### Complete Virtual Account requirements

1. [Get the Virtual Account requirements](/en/api-reference/virtual-accounts/requirements-v2)
   from `GET /v2/wallets/{id}/virtual-accounts/requirements`.
2. If `data.additionalDataRequired` is `true`, collect the fields in
   `data.additionalDataSchema`.
3. Validate the completed object.
4. Send it as `additionalData` when you
   [create the Virtual Account](/en/api-reference/virtual-accounts/master-wallet-create-v2).
   Use the `provider` returned by the requirements request.

```json theme={null}
{
  "currency": "NGN",
  "assetId": "ae455f23-3824-4125-baab-d158315cbcbd",
  "provider": "example-provider",
  "additionalData": {
    "bvn": "12345678901",
    "dateOfBirth": "1992-04-18"
  }
}
```

### Complete Deposit Fiat payment-order requirements

1. [Get a Deposit Fiat quote](/en/api-reference/deposit-fiat/master-wallet-get-quote)
   from `POST /v1/wallets/{id}/deposit/fiat/quote`.
2. Read the schema from `data.additionalDataRequirements.schema`.
3. Collect its fields and resolve any `x-resolution` fields using the
   instructions in the schema. See
   [Resolve Payment Order Requirements](/en/api-reference/deposit-fiat/resolve-payment-order-requirements).
4. Validate the completed object.
5. Send it as `additionalData` when
   [creating the payment order](/en/api-reference/deposit-fiat/master-wallet-create-payment-order)
   with `POST /v1/wallets/{id}/deposit/fiat/orders`.

The quote may return `null` for the schema. In that case, the selected route
does not require `additionalData`.

### Complete customer onboarding requirements

1. [Create an onboarding request](/en/api-reference/customer-onboarding/create-request)
   for the customer.
2. Read `data.requirements.schema` from the response. You can also
   [retrieve the request](/en/api-reference/customer-onboarding/get-request)
   to obtain its current schema and status.
3. Collect and validate the customer information described by the schema.
   For file and document fields, first
   [prepare the upload](/en/api-reference/customer-onboarding/prepare-uploads),
   then follow the returned direct-upload instructions or
   [send the prepared document](/en/api-reference/customer-onboarding/upload-prepared-document).
4. [Submit the completed object](/en/api-reference/customer-onboarding/submit-request)
   in the request body as `data`.
5. If the response includes `data.requirements`, repeat the process using that
   current schema. If it does not, follow the returned case status and hosted
   session information.

If the case requires confirmation, fetch its
[confirmation requirements](/en/api-reference/customer-onboarding/get-confirmation-requirements),
complete that schema, and
[submit the confirmation](/en/api-reference/customer-onboarding/submit-confirmation).

```json theme={null}
{
  "data": {
    "firstName": "Ada",
    "lastName": "Okafor",
    "dateOfBirth": "1992-04-18"
  }
}
```

The property names above are illustrative. Always use the properties returned
by the current onboarding request.

### Complete a schema from existing data

For an automated integration:

1. Copy existing values only when their property paths and JSON types match the
   fields that currently apply.
2. Report required fields that have no value. Do not invent placeholders.
3. Accept only identifiers declared by `enum` or `const`.
4. Load documented choices and resolve fields only after their dependencies are
   valid.
5. If required information is still missing, stop and return the missing and
   invalid property paths to the calling workflow.
6. Return a completed object only after full validation succeeds.

## Schema structure and supported keywords

Dynamic Form Schemas use JSON Schema
[Draft 07](https://json-schema.org/draft-07) and declare:

```json theme={null}
{ "$schema": "http://json-schema.org/draft-07/schema#" }
```

Accept `http://json-schema.org/draft-07/schema#`. Accept another value only when
the API operation explicitly documents that dialect. Otherwise, stop and report
that the schema dialect is unsupported.

Implement only the features present in the schemas your integration receives:

| When the schema contains                          | Your integration needs to handle  |
| ------------------------------------------------- | --------------------------------- |
| `properties`, `required`, and validation keywords | Basic collection and validation   |
| `allOf`, `oneOf`, or `if`/`then`/`else`           | Conditional fields                |
| `enum` or `oneOf` branches with `const`           | Choices                           |
| `x-resolution`                                    | API-resolved values               |
| `x-data-source`                                   | The documented remote-choice flow |
| File extensions                                   | The documented upload flow        |

### Validation keywords

| Keyword                         | Meaning                                                                               |
| ------------------------------- | ------------------------------------------------------------------------------------- |
| `type`                          | Value type, including `string`, `number`, `integer`, `boolean`, `object`, and `array` |
| `properties`                    | Exact property names and their schemas                                                |
| `required`                      | Required property names in the containing object                                      |
| `additionalProperties`          | Whether undeclared properties are accepted                                            |
| `enum`                          | Allowed string, number, boolean, or `null` values                                     |
| `const`                         | One fixed value, including a branch selector                                          |
| `pattern`                       | Regular expression for a string                                                       |
| `format`                        | String format, including `email` and `date` (`YYYY-MM-DD`)                            |
| `minLength`, `maxLength`        | String-length limits                                                                  |
| `minimum`, `maximum`            | Inclusive numeric limits                                                              |
| `items`, `minItems`, `maxItems` | Array item schema and length limits                                                   |
| `allOf`                         | Apply every listed schema                                                             |
| `oneOf`                         | Exactly one listed schema must match                                                  |
| `if`, `then`, `else`            | Apply conditional constraints from current values                                     |

Requiredness belongs to the containing object's `required` array. A
field-level `required: true` is not Draft 07.

### Presentation annotations

| Annotation    | Meaning                                                                                 |
| ------------- | --------------------------------------------------------------------------------------- |
| `title`       | Field, section, or choice label                                                         |
| `description` | Explanatory text                                                                        |
| `readOnly`    | Presentation hint that the value is not entered directly; it does not resolve the value |

### Blockradar extensions

| Extension                  | Meaning                                               |
| -------------------------- | ----------------------------------------------------- |
| `x-field-kind`             | Identifies a resolved, file, files, or document field |
| `x-resolution`             | Request and response mapping for a resolved field     |
| `x-data-source`            | Named remote-choice source                            |
| `x-data-source-depends-on` | Field paths required before loading choices           |
| `x-accept`                 | Accepted file MIME types                              |
| `x-max-file-size-bytes`    | Maximum file size                                     |
| `x-document-type`          | Fixed document type                                   |
| `x-document-types`         | Allowed document types, labels, and required sides    |

Nested objects define their own `properties`, `required`, and
`additionalProperties`. Arrays retain the structure declared by `items`. Do
not flatten nested paths when validating or submitting values.

For a selectable object-level `oneOf`, each branch has a non-empty `title` and
a required `type` property with a unique string `const`. Dynamic Forms does not
use an OpenAPI `discriminator`.

### Language-neutral data model

<Accordion title="View the complete schema model">
  `?` means optional, `[]` means a list, and `map<string, T>` means an object
  whose property names map to values of type `T`.

  ```text theme={null}
  JSON primitive = string | number | boolean | null
  JSON value     = JSON primitive | JSON value[] | map<string, JSON value>

  Dynamic Form Schema
    $schema?: string
    type: "string" | "number" | "integer" | "boolean" | "object" | "array"
    title?: string
    description?: string
    readOnly?: boolean
    enum?: JSON primitive[]
    const?: JSON primitive
    allOf?: Dynamic Form Schema[]
    oneOf?: Dynamic Form Schema[]
    if?: Dynamic Form Schema
    then?: Dynamic Form Schema
    else?: Dynamic Form Schema

    When type is "string"
      pattern?: string
      format?: string
      minLength?: number
      maxLength?: number

    When type is "number" or "integer"
      minimum?: number
      maximum?: number

    When type is "array"
      items: Dynamic Form Schema
      minItems?: number
      maxItems?: number

    When type is "object"
      properties?: map<string, Dynamic Form Schema>
      required?: string[]
      additionalProperties?: boolean | Dynamic Form Schema

    Blockradar extensions
      x-field-kind?: "resolved" | "file" | "files" | "document" | string
      x-resolution?: Resolution Definition
      x-data-source?: string
      x-data-source-depends-on?: string[]
      x-accept?: string[]
      x-max-file-size-bytes?: number
      x-document-type?: string
      x-document-types?: Document Type[]

  Resolution Definition
    method: "POST" | "PATCH"
    url: string
    body?: map<string, JSON value>
    inputPath?: string
    dependsOn: string[]
    responsePath: string

  Document Type
    = string
    | { value: string, label: string, sides?: ("front" | "back")[] }
  ```
</Accordion>

## Finding the fields that apply

Start at the root schema and follow nested objects and arrays. Apply conditional
keywords before collecting their properties:

```text theme={null}
visit(schema, path, values):
  apply every allOf schema
  evaluate if and apply then or else
  select the oneOf branch that matches current values
  if object: visit each property that applies
  if array: visit items for each current array item
  otherwise: record the field at path
```

At each object, read `required` from that object. At each array, retain numeric
indexes in error paths, such as `owners[0].address.country`.

When a value selects a different object-level branch, remove fields that exist
only in the previous branch. Reload choices and resolved values that depended
on the changed value.

Use a Draft 07 validator to evaluate `allOf`, `oneOf`, and conditional schemas.
Do not implement branch selection by field titles or property order.

## Supplying field values

Store every value at the exact property path from the selected branch and keep
its declared JSON type. Do not assume that every schema contains a field such
as `accountName`, `country`, or `document`.

Validate supplied values before using them. Accept only declared `enum` or
`const` identifiers, obtain resolved values through `x-resolution`, and obtain
file references through the upload flow documented by the API operation.

## Choices, dependencies, and resolved fields

### Static choices

For `enum`, submit the selected value. For a `oneOf` choice whose branches
contain `const`, show each branch's `title` and submit its `const`:

```json theme={null}
{
  "type": "string",
  "title": "Institution",
  "oneOf": [
    { "const": "999", "title": "Example Bank" }
  ]
}
```

The completed value is `"999"`, not `"Example Bank"`.

### Remote choices

`x-data-source` names a remote choice source.
`x-data-source-depends-on` lists the property paths required before that source
can load. Wait until every dependency is present and valid.

`x-data-source` does not provide the endpoint, request body, or response format.
Use the loading instructions documented by the API guide. Discard old choices
when a dependency changes.

If the API guide does not document the source, do not guess an endpoint from
the source name. Stop and tell the caller that the choice source has no
documented integration flow.

### Resolved fields

A field with `x-resolution` gets its value from the API request described by
`x-resolution`:

| Property       | Required | Meaning                                                   |
| -------------- | -------- | --------------------------------------------------------- |
| `method`       | Yes      | `POST` or `PATCH`                                         |
| `url`          | Yes      | API-relative path beginning `/v<version>/`                |
| `body`         | No       | JSON values to copy into the request body                 |
| `inputPath`    | No       | Request-body path for the current form object             |
| `dependsOn`    | Yes      | Property paths required before the call                   |
| `responsePath` | Yes      | Path to read inside the response's top-level `data` value |

`x-field-kind: "resolved"` does not make a field required. A resolved field is
required only when its property name appears in the containing object's
`required` array. Resolve an optional field when the API flow needs its value or
when you want to show the verified result.

#### Resolve an account name

This recipe uses the `x-resolution` returned by the
[Withdraw Fiat requirements request](/en/api-reference/withdraw-fiat/payment-method-requirements-v2):

```json theme={null}
{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "institutionIdentifier": { "type": "string" },
    "accountIdentifier": {
      "type": "string",
      "pattern": "^[0-9]{10}$"
    },
    "accountName": {
      "type": "string",
      "readOnly": true,
      "x-field-kind": "resolved",
      "x-resolution": {
        "method": "POST",
        "url": "/v2/wallets/4465468a-3c36-4536-918a-91d689e18a74/withdraw/fiat/payment-method/resolve",
        "body": {
          "provider": "example-provider",
          "assetId": "ae455f23-3824-4125-baab-d158315cbcbd",
          "currency": "NGN",
          "amount": "100.00",
          "paymentMethod": "bank_transfer"
        },
        "inputPath": "paymentMethodData",
        "dependsOn": ["institutionIdentifier", "accountIdentifier"],
        "responsePath": "data.accountName"
      }
    }
  },
  "required": [
    "institutionIdentifier",
    "accountIdentifier"
  ]
}
```

Given these current values:

```json theme={null}
{
  "institutionIdentifier": "999",
  "accountIdentifier": "0123456789"
}
```

the request body is:

```json theme={null}
{
  "provider": "example-provider",
  "assetId": "ae455f23-3824-4125-baab-d158315cbcbd",
  "currency": "NGN",
  "amount": "100.00",
  "paymentMethod": "bank_transfer",
  "paymentMethodData": {
    "institutionIdentifier": "999",
    "accountIdentifier": "0123456789"
  }
}
```

Send this body to `x-resolution.url`. For this recipe, see
[Resolve Payment Method](/en/api-reference/withdraw-fiat/resolve-payment-method-v2).

The API returns:

```json theme={null}
{
  "statusCode": 200,
  "message": "Payment method resolved successfully",
  "data": {
    "paymentMethod": "bank_transfer",
    "data": {
      "accountNumber": "0123456789",
      "accountName": "Ada Okafor",
      "bankCode": "999"
    }
  }
}
```

Follow `responsePath` from the response's top-level `data` value:

```text theme={null}
Start:  response.data
Path:   data.accountName
Result: "Ada Okafor"
```

Store the result at `accountName`:

```json theme={null}
{
  "institutionIdentifier": "999",
  "accountIdentifier": "0123456789",
  "accountName": "Ada Okafor"
}
```

Copy `body` into a new request body. When `inputPath` is present, add the
current form values at that path. If a dependency changes, clear the previous
result and call the resolution again with the new values.

Do not call the resolution while a path in `dependsOn` is missing or invalid.
If the request fails, leave the field unresolved and surface the API error to
the calling workflow.

## File and document fields

File fields add upload constraints to a schema property:

```json theme={null}
{
  "type": "object",
  "title": "Identity document",
  "x-field-kind": "file",
  "x-accept": ["application/pdf", "image/jpeg", "image/png"],
  "x-max-file-size-bytes": 5000000,
  "x-document-types": ["passport", "national_id"]
}
```

`x-field-kind: "files"` uses an array whose `items` are objects.
`x-document-types` can contain strings or objects with `value`, `label`, and
optional `sides` containing `front` or `back`.

The API guide defines how to upload the file and which value to place in the
completed object. The schema itself does not provide an upload endpoint,
multipart field name, response format, or file-reference format. Do not place a
platform-specific file object or raw bytes in the JSON object.

If the API operation does not document the upload flow or file-reference
format, stop and tell the caller that the file field cannot be submitted.

Enforce `x-accept`, `x-max-file-size-bytes`, array limits, document types, and
required sides before submission.

## Partial and complete validation

Partial validation answers: **Are the values supplied so far valid?** It does
not mean the object is ready to submit.

For partial validation, validate the current object against the branch that
applies. Continue to enforce:

* supplied value types and constraints;
* `enum` and `const`;
* `additionalProperties`;
* array constraints; and
* `allOf`, `oneOf`, and any `if`/`then`/`else` branch that can already be
  determined.

Ignore only `required` failures for properties that have not been supplied yet.
Do not validate each property in isolation because rules on the containing
object may also apply.

Complete validation answers: **Is the object ready to submit?** Validate the
completed object against the full, unmodified schema and report every failure.

Preserve strings when the schema declares `type: "string"`, including account
numbers and decimal amounts. Submit JSON numbers for fields declared as
`number` or `integer`.

## Constructing and submitting the final object

Build a new object from the properties that currently apply:

* copy accepted values at exact property paths;
* retain nested objects and arrays;
* copy identifiers rather than labels;
* include values returned by resolution calls;
* include documented uploaded-file references;
* omit properties from unselected branches and any UI-only state;
* reject unknown properties where `additionalProperties: false`; and
* omit absent optional properties.

Validate this new object against the complete schema. Then place it in the
request property documented by the API guide.

## Error handling

Dynamic Forms uses the [standard Blockradar API error
response](/en/about-the-api/errors). A validation failure includes the HTTP
status and a message that identifies the missing or invalid field:

```json theme={null}
{
  "statusCode": 400,
  "message": "Invalid payment method data for accountIdentifier: accountIdentifier does not match the expected format"
}
```

`requestId` is included when request context is available. Some errors can also
include `details`. Do not depend on undocumented error codes. Use the HTTP
status and `message`, and retain `requestId` when contacting support.

## Security boundaries

The integration must:

* keep API credentials out of untrusted application code;
* use the schema returned by Blockradar instead of a modified client copy;
* read the schema as data; never execute content from it as code;
* accept only the documented `POST` or `PATCH` resolution methods and
  API-relative paths on the configured Blockradar origin;
* validate the completed object before submission; and
* avoid logging credentials or sensitive field values.

## Testing and production checklist

Before production, verify that your integration can:

* retrieve schemas dynamically;
* identify required and conditional fields at nested paths;
* handle static choices, documented data sources, and resolved fields;
* preserve exact names, types, paths, objects, and arrays;
* remove values from branches that no longer apply;
* build and validate the completed object; and
* submit it in the request property documented by the API guide.
