This section provides an OpenAPI specification for the self-serve connector contract: the endpoints, request/response schemas and errors that a connector must implement so Knova can call it. Knova is the client and your connector is the server. The specification does not represent the platform’s full feature set and will evolve over time.
{
"openapi": "3.0.3",
"info": {
"title": "Knova Connector API",
"description": "Build your own connector to link Knova to any system that holds accounts, balances or assets: a bank, a payments provider, a custodian, an exchange, a ledger or an internal platform. You write a small HTTP service that implements this contract, and Knova calls it.\n\n## How it works\n- **You build it, you own it.** The connector lives in your own repository, runs where you host it, and the code and IP are yours. Knova doesn't need your source, only the URL of your running service.\n- **Implement the contract.** Knova mirrors accounts, balances and asset codes from your connector and sends transfers to it.\n- **Register it.** Call the Knova API (`POST /v1/admin/connectors`) with a name and your service URL. Knova routes every call for that name to your service.\n\n## Implement only what you need\nSystems differ, so you don't have to support every operation. `GET /v1/capabilities` declares which endpoints your connector supports, and any endpoint you don't implement returns 501. Knova adapts to what you declare, so it needs no connector-specific code. Anything beyond the contract can be advertised through `GET /v1/extensions`.\n\n## Contract conventions\n- **Idempotent writes.** Creating accounts, payins and transfers, and closing accounts, require an `idempotency-key`. Store the result for one month and replay it for a repeated key.\n- **Your identifiers.** `account_id` and `transfer_id` are assigned by your connector. Knova treats them as opaque and uses them as identity.\n- **Connector-specific details.** Anything particular to your system travels in `connector_data`, validated against the schemas you publish in your capabilities.\n- **One error shape.** Every non-2xx response uses `ConnectorErrorBody`.\n- **Events.** A connector can push state changes (transfers, accounts, pay-ins, balances) back to Knova.",
"version": "0.21.0"
},
"paths": {
"/v1/accounts": {
"get": {
"tags": [
"Account"
],
"summary": "List accounts",
"description": "Enumerate accounts the connector knows about.",
"operationId": "list_accounts",
"parameters": [
{
"name": "page",
"schema": {
"description": "Zero-based page number to return.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "page_size",
"schema": {
"description": "Maximum number of accounts to return in the page.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "sort",
"schema": {
"description": "Ordering to apply, formatted as `field:asc` or `field:desc`.",
"type": "string"
},
"in": "query",
"style": "form"
},
{
"name": "updated_from",
"schema": {
"description": "Return only accounts changed at or after this instant.",
"type": "string",
"format": "date-time"
},
"in": "query",
"style": "form"
},
{
"name": "status",
"schema": {
"description": "Return only accounts in this lifecycle state.",
"allOf": [
{
"$ref": "#/components/schemas/AccountStatus"
}
]
},
"in": "query",
"style": "form"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"description": "A single page of results together with the position it was drawn from.",
"type": "object",
"properties": {
"data": {
"description": "The items in this page, ordered by the requested sort.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Account"
}
},
"page": {
"description": "Zero-based page number this result was drawn from.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"page_size": {
"description": "Page size used for this query. Endpoints that page over groups of rows can return more items than this.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"has_more": {
"description": "Whether at least one further page exists after this one.",
"type": "boolean"
},
"total_count": {
"nullable": true,
"description": "Total items matching the query, absent when the source cannot report a count.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 9223372036854775807
}
},
"required": [
"data",
"page",
"page_size",
"has_more"
]
}
}
}
},
"400": {
"description": "The query string is missing a required parameter, or a parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
},
"post": {
"tags": [
"Account"
],
"summary": "Create account",
"description": "Open an account at the vendor or register an external destination.",
"operationId": "create_account",
"parameters": [
{
"name": "idempotency-key",
"description": "Key that makes this request safe to repeat. The connector stores the result for one month and replays it for a repeated key.",
"required": true,
"schema": {
"type": "string"
},
"in": "header",
"style": "simple"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateAccountRequest"
}
}
},
"required": true
},
"responses": {
"400": {
"description": "The `idempotency-key` header is absent, empty, or too long.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"422": {
"description": "The request body is valid JSON but does not match the expected schema.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"200": {
"description": "The idempotency key replayed an account the connector already created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Account"
}
}
}
},
"201": {
"description": "The account was created.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Account"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/accounts/{account_id}": {
"get": {
"tags": [
"Account"
],
"summary": "Get account",
"description": "Read a single account.",
"operationId": "get_account",
"parameters": [
{
"name": "account_id",
"required": true,
"schema": {
"type": "string"
},
"in": "path",
"style": "simple"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Account"
}
}
}
},
"400": {
"description": "A path parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
},
"delete": {
"tags": [
"Account"
],
"summary": "Close account",
"description": "Close an account.",
"operationId": "close_account",
"parameters": [
{
"name": "account_id",
"required": true,
"schema": {
"type": "string"
},
"in": "path",
"style": "simple"
},
{
"name": "idempotency-key",
"description": "Key that makes this request safe to repeat. The connector stores the result for one month and replays it for a repeated key.",
"required": true,
"schema": {
"type": "string"
},
"in": "header",
"style": "simple"
}
],
"responses": {
"400": {
"description": "A path parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"204": {
"description": "The account is closed."
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/accounts/{account_id}/balances": {
"get": {
"tags": [
"Balance"
],
"summary": "Get account balances",
"description": "Read every asset balance held by one account.",
"operationId": "get_account_balances",
"parameters": [
{
"name": "account_id",
"required": true,
"schema": {
"type": "string"
},
"in": "path",
"style": "simple"
},
{
"name": "asset_code",
"schema": {
"description": "Return only the balance denominated in this asset code.",
"type": "string"
},
"in": "query",
"style": "form"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AccountBalances"
}
}
}
},
"400": {
"description": "A path parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/accounts/{account_id}/payins": {
"post": {
"tags": [
"Payin"
],
"summary": "Create payin",
"description": "Issue the instructions for paying an asset into an account.",
"operationId": "create_payin",
"parameters": [
{
"name": "account_id",
"required": true,
"schema": {
"type": "string"
},
"in": "path",
"style": "simple"
},
{
"name": "idempotency-key",
"description": "Key that makes this request safe to repeat. The connector stores the result for one month and replays it for a repeated key.",
"required": true,
"schema": {
"type": "string"
},
"in": "header",
"style": "simple"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreatePayinRequest"
}
}
},
"required": true
},
"responses": {
"400": {
"description": "A path parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"422": {
"description": "The request body is valid JSON but does not match the expected schema.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"200": {
"description": "The idempotency key replayed instructions the connector already issued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Payin"
}
}
}
},
"201": {
"description": "The instructions were issued.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Payin"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/asset_codes": {
"get": {
"tags": [
"AssetCode"
],
"summary": "List asset codes",
"description": "Enumerate the asset codes the connector can hold and transfer.",
"operationId": "list_asset_codes",
"parameters": [
{
"name": "page",
"schema": {
"description": "Zero-based page number to return.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "page_size",
"schema": {
"description": "Maximum number of asset codes to return in the page.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "sort",
"schema": {
"description": "Ordering to apply, formatted as `field:asc` or `field:desc`.",
"type": "string"
},
"in": "query",
"style": "form"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"description": "A single page of results together with the position it was drawn from.",
"type": "object",
"properties": {
"data": {
"description": "The items in this page, ordered by the requested sort.",
"type": "array",
"items": {
"$ref": "#/components/schemas/AssetCode"
}
},
"page": {
"description": "Zero-based page number this result was drawn from.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"page_size": {
"description": "Page size used for this query. Endpoints that page over groups of rows can return more items than this.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"has_more": {
"description": "Whether at least one further page exists after this one.",
"type": "boolean"
},
"total_count": {
"nullable": true,
"description": "Total items matching the query, absent when the source cannot report a count.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 9223372036854775807
}
},
"required": [
"data",
"page",
"page_size",
"has_more"
]
}
}
}
},
"400": {
"description": "The query string is missing a required parameter, or a parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/transfers": {
"post": {
"tags": [
"Transfer"
],
"summary": "Create transfer",
"description": "Accept a transfer instruction.",
"operationId": "create_transfer",
"parameters": [
{
"name": "idempotency-key",
"description": "Key that makes this request safe to repeat. The connector stores the result for one month and replays it for a repeated key.",
"required": true,
"schema": {
"type": "string"
},
"in": "header",
"style": "simple"
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TransferRequest"
}
}
},
"required": true
},
"responses": {
"400": {
"description": "The `idempotency-key` header is absent, empty, or too long.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"422": {
"description": "The request body is valid JSON but does not match the expected schema.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"200": {
"description": "The idempotency key replayed a transfer the connector already accepted.",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
},
"202": {
"description": "The transfer was accepted for settlement.",
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/transfers/{transfer_id}": {
"get": {
"tags": [
"Transfer"
],
"summary": "Get transfer",
"description": "Resolve a transfer the connector previously accepted.",
"operationId": "get_transfer",
"parameters": [
{
"name": "transfer_id",
"required": true,
"schema": {
"type": "string"
},
"in": "path",
"style": "simple"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Transfer"
}
}
}
},
"400": {
"description": "A path parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/settlements": {
"get": {
"tags": [
"Settlement"
],
"summary": "List settlements",
"description": "List settled movements the vendor has reported.",
"operationId": "list_settlements",
"parameters": [
{
"name": "account_id",
"schema": {
"description": "Return only settlements where this account appears on either side.",
"type": "string"
},
"in": "query",
"style": "form"
},
{
"name": "asset_code",
"schema": {
"description": "Return only settlements denominated in this asset code.",
"type": "string"
},
"in": "query",
"style": "form"
},
{
"name": "kind",
"schema": {
"description": "Return only settlements of this classification.",
"allOf": [
{
"$ref": "#/components/schemas/SettlementKind"
}
]
},
"in": "query",
"style": "form"
},
{
"name": "transfer_id",
"schema": {
"description": "Return only settlements produced by this transfer.",
"type": "string"
},
"in": "query",
"style": "form"
},
{
"name": "created_from",
"schema": {
"description": "Return only settlements recorded at or after this instant.",
"type": "string",
"format": "date-time"
},
"in": "query",
"style": "form"
},
{
"name": "created_to",
"schema": {
"description": "Return only settlements recorded before this instant.",
"type": "string",
"format": "date-time"
},
"in": "query",
"style": "form"
},
{
"name": "page",
"schema": {
"description": "Zero-based page number to return.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "page_size",
"schema": {
"description": "Maximum number of settlements to return in the page.",
"type": "integer",
"minimum": 0,
"maximum": 4294967295
},
"in": "query",
"style": "form"
},
{
"name": "sort",
"schema": {
"description": "Ordering to apply, formatted as `field:asc` or `field:desc`.",
"type": "string"
},
"in": "query",
"style": "form"
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"description": "A single page of results together with the position it was drawn from.",
"type": "object",
"properties": {
"data": {
"description": "The items in this page, ordered by the requested sort.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Settlement"
}
},
"page": {
"description": "Zero-based page number this result was drawn from.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"page_size": {
"description": "Page size used for this query. Endpoints that page over groups of rows can return more items than this.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 4294967295
},
"has_more": {
"description": "Whether at least one further page exists after this one.",
"type": "boolean"
},
"total_count": {
"nullable": true,
"description": "Total items matching the query, absent when the source cannot report a count.",
"type": "integer",
"format": "int64",
"minimum": 0,
"maximum": 9223372036854775807
}
},
"required": [
"data",
"page",
"page_size",
"has_more"
]
}
}
}
},
"400": {
"description": "The query string is missing a required parameter, or a parameter could not be parsed into its expected type.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/capabilities": {
"get": {
"tags": [
"Capabilities"
],
"summary": "Get capabilities",
"description": "Report what this connector supports.",
"operationId": "get_capabilities",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Capabilities"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
},
"/v1/extensions": {
"get": {
"tags": [
"Capabilities"
],
"summary": "Get extensions",
"description": "Report the endpoints this connector serves beyond the specification.",
"operationId": "get_extensions",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Extensions"
}
}
}
},
"500": {
"description": "The connector failed unexpectedly, or the vendor refused the request.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"501": {
"description": "This connector does not implement the endpoint.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
},
"503": {
"description": "The vendor is unreachable or erroring.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ConnectorErrorBody"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"Account": {
"description": "An account the connector can move funds to or from.",
"type": "object",
"properties": {
"account_id": {
"description": "Connector-assigned opaque reference that Knova uses as the account identity.",
"type": "string"
},
"kind": {
"$ref": "#/components/schemas/AccountKind"
},
"name": {
"nullable": true,
"description": "Human-readable name for operator display only; never used for lookups.",
"type": "string"
},
"created": {
"description": "Instant the account was opened at the vendor.",
"type": "string",
"format": "date-time"
},
"updated": {
"description": "Instant any field of the account last changed, used to discard stale updates.",
"type": "string",
"format": "date-time"
},
"status": {
"$ref": "#/components/schemas/AccountStatus"
},
"metadata": {
"description": "Opaque vendor-specific key/value pairs passed through untouched.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"account_id",
"kind",
"created",
"updated",
"status"
]
},
"AccountBalances": {
"description": "Every asset balance held by a single account at a point in time.",
"type": "object",
"properties": {
"account_id": {
"description": "Account the balances belong to.",
"type": "string"
},
"balances": {
"description": "One entry per asset the account holds, ordered by asset code.",
"type": "array",
"items": {
"$ref": "#/components/schemas/Balance"
}
},
"as_of": {
"description": "Instant the vendor computed this snapshot.",
"type": "string",
"format": "date-time"
}
},
"required": [
"account_id",
"balances",
"as_of"
]
},
"AccountKind": {
"type": "string",
"enum": [
"internal",
"external"
]
},
"AccountStatus": {
"type": "string",
"enum": [
"open",
"frozen",
"closed"
]
},
"AccountUpdatedEvent": {
"description": "An account the connector can move funds to or from.",
"allOf": [
{
"$ref": "#/components/schemas/Account"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"account_updated"
]
}
},
"required": [
"type"
]
}
]
},
"AssetCode": {
"description": "An asset the connector is able to hold and transfer.",
"type": "object",
"properties": {
"asset_code": {
"description": "Knova asset code the connector exposes on the wire.",
"type": "string"
},
"decimals": {
"description": "Maximum number of fractional digits the connector represents for this asset.",
"type": "integer",
"format": "int32",
"minimum": 0,
"maximum": 255
},
"enabled": {
"description": "Whether the connector currently accepts transfers denominated in this asset.",
"type": "boolean"
}
},
"required": [
"asset_code",
"decimals",
"enabled"
]
},
"Balance": {
"description": "The amount of a single asset held by an account.",
"type": "object",
"properties": {
"asset_code": {
"description": "Knova asset code the balance is denominated in.",
"type": "string"
},
"amount": {
"description": "Total balance of the asset.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
},
"required": [
"asset_code",
"amount"
]
},
"BalancesUpdatedEvent": {
"description": "Every asset balance held by a single account at a point in time.",
"allOf": [
{
"$ref": "#/components/schemas/AccountBalances"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"balances_updated"
]
}
},
"required": [
"type"
]
}
]
},
"Capabilities": {
"description": "What a connector supports, so Knova can adapt without per-vendor code.",
"type": "object",
"properties": {
"spec_version": {
"description": "Version of this connector specification the connector implements.",
"type": "string"
},
"endpoints": {
"description": "Support flag per capability. Keys are capability names: `account.list`, `account.get`, `account.create`, `account.close`, `payin.create`, `balance.get`, `asset_code.list`, `transfer.create`, `transfer.get`, `settlement.list`.",
"type": "object",
"additionalProperties": {
"type": "boolean"
}
},
"schemas": {
"$ref": "#/components/schemas/ConnectorSchemas"
}
},
"required": [
"spec_version",
"endpoints",
"schemas"
]
},
"ConnectorErrorBody": {
"description": "The error body returned by every non-2xx response, and embedded in a failed transfer.",
"type": "object",
"properties": {
"code": {
"$ref": "#/components/schemas/ConnectorErrorCode"
},
"message": {
"description": "Human-readable explanation intended for operators, never parsed.",
"type": "string"
},
"details": {
"nullable": true,
"description": "Optional structured context, such as the offending field or vendor status.",
"type": "object"
}
},
"required": [
"code",
"message"
]
},
"ConnectorErrorCode": {
"type": "string",
"enum": [
"account_not_found",
"transfer_not_found",
"account_closed",
"account_frozen",
"account_not_empty",
"asset_not_supported",
"insufficient_funds",
"invalid_amount",
"validation_failed",
"idempotency_conflict",
"idempotency_in_flight",
"unsupported",
"vendor_unavailable",
"vendor_rejected",
"internal"
]
},
"ConnectorSchemas": {
"description": "JSON Schemas describing the connector-specific payloads a connector accepts.",
"type": "object",
"properties": {
"internal_account": {
"nullable": true,
"description": "Schema for `connector_data` when opening an internal account.",
"type": "object"
},
"external_account": {
"nullable": true,
"description": "Schema for `connector_data` when registering an external account.",
"type": "object"
},
"payin": {
"nullable": true,
"description": "Schema for `connector_data` when asking for payin instructions.",
"type": "object"
},
"payin_destination": {
"nullable": true,
"description": "Schema for the `destination` a payin reports, describing the shape funds arrive over.",
"type": "object"
}
}
},
"CreateAccountRequest": {
"description": "A request to open an account or register an external destination.",
"type": "object",
"properties": {
"kind": {
"$ref": "#/components/schemas/AccountKind"
},
"asset_code": {
"description": "Asset, in the connector's own codes, the account is opened to hold or pay.",
"type": "string"
},
"name": {
"nullable": true,
"description": "Human-readable name for operator display only.",
"type": "string"
},
"connector_data": {
"nullable": true,
"description": "Connector-specific details validated against the connector's advertised schema.",
"type": "object"
},
"metadata": {
"description": "Opaque vendor-specific key/value pairs passed through untouched.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"kind",
"asset_code"
]
},
"CreatePayinRequest": {
"description": "A request for the instructions that pay an asset into an account.",
"type": "object",
"properties": {
"asset_code": {
"description": "Asset the instructions must accept.",
"type": "string"
},
"connector_data": {
"nullable": true,
"description": "Connector-specific details validated against the connector's advertised payin schema.",
"type": "object"
},
"metadata": {
"description": "Opaque vendor-specific key/value pairs passed through untouched.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"asset_code"
]
},
"Event": {
"description": "A state change the connector pushes to Knova's webhook.",
"type": "object",
"properties": {
"event_id": {
"description": "Connector-assigned unique identifier used by Knova to deduplicate deliveries.",
"type": "string"
},
"occurred_at": {
"description": "Instant the underlying state change occurred, used to discard stale deliveries.",
"type": "string",
"format": "date-time"
},
"payload": {
"$ref": "#/components/schemas/EventPayload"
}
},
"required": [
"event_id",
"occurred_at",
"payload"
]
},
"EventPayload": {
"description": "The state change being reported. `type` identifies the variant.",
"discriminator": {
"propertyName": "type",
"mapping": {
"transfer_updated": "#/components/schemas/TransferUpdatedEvent",
"account_updated": "#/components/schemas/AccountUpdatedEvent",
"pay_in_received": "#/components/schemas/PayInReceivedEvent",
"balances_updated": "#/components/schemas/BalancesUpdatedEvent"
}
},
"oneOf": [
{
"$ref": "#/components/schemas/TransferUpdatedEvent"
},
{
"$ref": "#/components/schemas/AccountUpdatedEvent"
},
{
"$ref": "#/components/schemas/PayInReceivedEvent"
},
{
"$ref": "#/components/schemas/BalancesUpdatedEvent"
}
]
},
"ExtensionEndpoint": {
"description": "A vendor-specific endpoint a connector serves in addition to the specification.",
"type": "object",
"properties": {
"url": {
"description": "Path the endpoint is served at, relative to the connector's base URL.",
"type": "string"
},
"name": {
"description": "Short machine-friendly name identifying the endpoint.",
"type": "string"
},
"description": {
"description": "Human-readable explanation of what the endpoint does.",
"type": "string"
},
"method": {
"description": "HTTP method the endpoint responds to.",
"type": "string"
},
"request_schema": {
"nullable": true,
"description": "JSON Schema string describing the request body, absent when no body is accepted.",
"type": "string"
},
"response_schema": {
"description": "JSON Schema string describing the success response body.",
"type": "string"
}
},
"required": [
"url",
"name",
"description",
"method",
"response_schema"
]
},
"Extensions": {
"description": "The vendor-specific endpoints a connector serves beyond the specification. Reported separately from capabilities: capabilities say which parts of the contract Knova may use, while extensions are called by vendor-specific code.",
"type": "object",
"properties": {
"extensions": {
"description": "Vendor-specific endpoints this connector serves beyond the specification.",
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtensionEndpoint"
}
}
},
"required": [
"extensions"
]
},
"PayInReceived": {
"description": "A pay-in that was received for an account.",
"type": "object",
"properties": {
"payin_id": {
"description": "Connector-assigned identifier for the pay-in.",
"type": "string"
},
"account_id": {
"description": "Account that received the pay-in.",
"type": "string"
},
"asset_code": {
"description": "Asset code received in the pay-in.",
"type": "string"
},
"amount": {
"description": "Amount received in the pay-in.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
},
"created": {
"description": "Instant the pay-in was received.",
"type": "string",
"format": "date-time"
}
},
"required": [
"payin_id",
"account_id",
"asset_code",
"amount",
"created"
]
},
"PayInReceivedEvent": {
"description": "A pay-in that was received for an account.",
"allOf": [
{
"$ref": "#/components/schemas/PayInReceived"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"pay_in_received"
]
}
},
"required": [
"type"
]
}
]
},
"Payin": {
"description": "Instructions for paying one asset into one account.",
"type": "object",
"properties": {
"account_id": {
"description": "Account the funds land in.",
"type": "string"
},
"asset_code": {
"description": "Asset the instructions accept.",
"type": "string"
},
"destination": {
"description": "Where the funds are to be sent, in the shape the connector advertises as its\n`payin_destination` schema.\n\nThe rails funds arrive over have nothing in common — an address and a tag on one, a\ncountry's banking fields on another — so the connector describes its own shape rather than\nthe specification naming a fixed set it would have to keep growing.",
"type": "object"
},
"created": {
"description": "Instant the instructions were issued.",
"type": "string",
"format": "date-time"
},
"expires": {
"nullable": true,
"description": "Instant the instructions stop being usable, absent when they do not expire.",
"type": "string",
"format": "date-time"
},
"metadata": {
"description": "Opaque vendor-specific key/value pairs passed through untouched.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"account_id",
"asset_code",
"destination",
"created"
]
},
"Settlement": {
"description": "A completed movement of a single asset between two endpoints, recorded once.",
"type": "object",
"properties": {
"settlement_id": {
"description": "Connector-assigned identifier for this movement.",
"type": "string"
},
"transfer_id": {
"nullable": true,
"description": "Transfer that produced this movement, absent for vendor-initiated activity.",
"type": "string"
},
"src_account_id": {
"nullable": true,
"description": "Account debited, absent when funds originate outside the connector.",
"type": "string"
},
"dst_account_id": {
"nullable": true,
"description": "Account credited, absent when funds leave the connector.",
"type": "string"
},
"asset_code": {
"description": "Knova asset code that moved.",
"type": "string"
},
"amount": {
"description": "Unsigned amount that moved; direction is implied by the two endpoints.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
},
"kind": {
"$ref": "#/components/schemas/SettlementKind"
},
"created": {
"description": "Instant the vendor settled the movement.",
"type": "string",
"format": "date-time"
}
},
"required": [
"settlement_id",
"asset_code",
"amount",
"kind",
"created"
]
},
"SettlementKind": {
"type": "string",
"enum": [
"transfer",
"deposit",
"withdrawal",
"fee",
"adjustment"
]
},
"Transfer": {
"description": "The connector's view of a transfer it was asked to perform.",
"type": "object",
"properties": {
"transfer_id": {
"description": "Connector-assigned identifier used to resolve this transfer.",
"type": "string"
},
"idempotency_key": {
"description": "Idempotency key the caller supplied when creating the transfer.",
"type": "string"
},
"requested_src_account_id": {
"description": "Source account requested when the transfer was created.",
"type": "string"
},
"requested_dst_account_id": {
"description": "Destination account requested when the transfer was created.",
"type": "string"
},
"requested_asset_code": {
"description": "Asset code requested when the transfer was created.",
"type": "string"
},
"requested_amount": {
"description": "Amount requested when the transfer was created.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
},
"status": {
"$ref": "#/components/schemas/TransferStatus"
},
"failure": {
"nullable": true,
"description": "Structured reason the transfer failed, present only for a failed transfer.",
"allOf": [
{
"$ref": "#/components/schemas/ConnectorErrorBody"
}
]
},
"created": {
"description": "Instant the connector accepted the transfer request.",
"type": "string",
"format": "date-time"
},
"updated": {
"description": "Instant the status last changed.",
"type": "string",
"format": "date-time"
},
"balance_changes": {
"description": "Every balance movement this transfer produced.",
"type": "array",
"items": {
"$ref": "#/components/schemas/TransferBalanceChange"
}
}
},
"required": [
"transfer_id",
"idempotency_key",
"requested_src_account_id",
"requested_dst_account_id",
"requested_asset_code",
"requested_amount",
"status",
"created",
"updated",
"balance_changes"
]
},
"TransferBalanceChange": {
"description": "One balance movement produced by a transfer reaching a terminal state.",
"type": "object",
"properties": {
"src_account_id": {
"nullable": true,
"description": "Account debited by this movement, absent when funds originated outside the connector.",
"type": "string"
},
"dst_account_id": {
"nullable": true,
"description": "Account credited by this movement, absent when funds left the connector.",
"type": "string"
},
"asset_code": {
"description": "Asset code the balance changed in.",
"type": "string"
},
"amount": {
"description": "Unsigned amount moved; direction is implied by the two endpoints.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
}
},
"required": [
"asset_code",
"amount"
]
},
"TransferRequest": {
"description": "A request to move a single asset between two accounts known to the connector.",
"type": "object",
"properties": {
"src_account_id": {
"description": "Account funds are debited from.",
"type": "string"
},
"dst_account_id": {
"description": "Account funds are credited to.",
"type": "string"
},
"asset_code": {
"description": "Knova asset code being moved.",
"type": "string"
},
"amount": {
"description": "Amount to move, strictly greater than zero.",
"type": "string",
"format": "decimal",
"pattern": "^-?[0-9]+(\\.[0-9]+)?$"
},
"metadata": {
"description": "Opaque vendor-specific key/value pairs passed through untouched.",
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"required": [
"src_account_id",
"dst_account_id",
"asset_code",
"amount"
]
},
"TransferStatus": {
"type": "string",
"enum": [
"pending",
"succeeded",
"failed"
]
},
"TransferUpdatedEvent": {
"description": "The connector's view of a transfer it was asked to perform.",
"allOf": [
{
"$ref": "#/components/schemas/Transfer"
},
{
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"transfer_updated"
]
}
},
"required": [
"type"
]
}
]
}
}
}
}
What’s in the spec
- Accounts, balances and pay-ins
- Asset codes
- Transfers and settlements
- Capabilities, so you declare which endpoints you implement
- Events, as optional webhooks back to Knova
- One error format for every non-2xx response
Getting started
Download the spec, generate a server stub or implement the endpoints by hand, then register your connector with POST /v1/admin/connectors. The full walkthrough is in Self-Serve Connectors.
