# FabricBloc API Documentation — Full Context This file contains all public API documentation for LLM consumption. All requests go through Kong API Gateway — send `Authorization: Bearer `, Kong validates and enriches. --- # fabric-nft API > NFT collection deployment and asset management for the FabricBloc platform. Deploy ERC-721 collections, mint tokens, manage metadata, and configure royalties — all without writing Solidity. ## Base URL `/v1/nft` ## Authentication All requests go through the Kong API Gateway, which validates JWTs and enriches requests with `X-User-ID` and `X-Subscription-Tier` headers. You only need to send `Authorization: Bearer ` — Kong handles the rest. ## Endpoints ### NFTs #### List NFTs owned by an address across every collection `GET /accounts/{address}/nfts` Parameters: address (path, string, required): Owner wallet address; collection_id (query, string): Filter by collection ID (UUID); contract_type (query, string): Filter by contract type (ERC-721, ERC-1155); page (query, integer): 1-indexed page number; limit (query, integer): Page size (max 100); sort (query, string): Sort field; '-' prefix for desc; expand (query, string): Comma-separated expand keys (metadata) Returns 200: OK → { has_next (boolean), items (array), limit (integer), page (integer), total (integer) } Errors: 400: Bad Request; 401: Unauthorized; 500: Internal Server Error #### List minted NFTs in a collection `GET /collections/{id}/nfts` Parameters: id (path, string, required): Collection ID; owner (query, string): Filter by current on-chain owner address; status (query, string): Filter by NFT status (e.g. minted, burned); token_id_gte (query, string): Lower bound on token_id (inclusive); token_id_lte (query, string): Upper bound on token_id (inclusive); page (query, integer): 1-indexed page number; limit (query, integer): Page size (max 100); sort (query, string): Sort field; '-' prefix for desc Returns 200: OK → { has_next (boolean), items (array), limit (integer), page (integer), total (integer) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Get a single minted NFT by on-chain token ID `GET /collections/{id}/nfts/{token_id}` Parameters: id (path, string, required): Collection ID; token_id (path, string, required): On-chain token ID; expand (query, string): Comma-separated expand keys (metadata,royalty) Returns 200: OK → { asset_index (integer), collection_id (string), contract_address (string), created_at (string), id (string), image_ipfs_cid (string), image_url (string), max_supply (integer), metadata_ipfs_cid (string), metadata_json (object), metadata_uri (string), owner_address (string), quantity (integer), resolved_metadata (object), royalty (object), status (string), token_id (string), token_name (string), token_type (string), transaction_hash (string), updated_at (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error ### Collections #### List collections in the caller's project `GET /collections` Parameters: creator (query, string): Filter by creator user ID; status (query, string): Filter by lifecycle status; chain (query, string): Filter by chain ID; contract_type (query, string): Filter by contract type; page (query, integer): 1-indexed page number; limit (query, integer): Page size (max 100); sort (query, string): Sort field; '-' prefix for desc Returns 200: OK → { has_next (boolean), items (array), limit (integer), page (integer), total (integer) } Errors: 400: Bad Request; 401: Unauthorized; 500: Internal Server Error #### Create a draft collection `POST /collections` Body: { contract_type (string); description (string); metadata (object); metadata_strategy (string); mint_settings (object); name (string); network (string); owner_address (string); storage_method (string); symbol (string) } Returns 201: Created → { chain_id (string), contract_address (string), contract_type (string), created_at (string), deployment_nonce (integer), deployment_salt (string), description (string), error_message (string), gasless_enabled (boolean), id (string), metadata (object), metadata_strategy (string), mint_settings (object), name (string), network (string), owner_address (string), status (string), storage_method (string), symbol (string), transaction_hash (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 409: Conflict; 500: Internal Server Error #### Delete a draft collection `DELETE /collections/{id}` Parameters: id (path, string, required): Collection ID Returns 204: No Content Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error #### Get a collection by ID `GET /collections/{id}` Parameters: id (path, string, required): Collection ID; expand (query, string): Comma-separated expand keys Returns 200: OK → { chain_id (string), contract_address (string), contract_type (string), created_at (string), deployment_nonce (integer), deployment_salt (string), deployments (array), description (string), error_message (string), gasless_enabled (boolean), id (string), media_count (integer), metadata (object), metadata_strategy (string), mint_settings (object), minted_count (integer), name (string), network (string), owner_address (string), royalty_default (object), status (string), storage_method (string), symbol (string), transaction_hash (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Partial update of a draft collection `PATCH /collections/{id}` Parameters: id (path, string, required): Collection ID Body: { description (string); metadata (object); metadata_strategy (string); mint_settings (object); name (string); network (string); storage_method (string); symbol (string) } Returns 200: OK → { chain_id (string), contract_address (string), contract_type (string), created_at (string), deployment_nonce (integer), deployment_salt (string), description (string), error_message (string), gasless_enabled (boolean), id (string), metadata (object), metadata_strategy (string), mint_settings (object), name (string), network (string), owner_address (string), status (string), storage_method (string), symbol (string), transaction_hash (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error #### Deploy a collection (operator-executed, creator-scoped) `POST /collections/{id}/admin/deploy` Parameters: id (path, string, required): Collection ID Returns 200: Sync path: tx submitted, tx_hash populated → { chain_id (integer), operation_id (string), status (string), tx_hash (string) } Errors: 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway; 503: Service Unavailable #### Get ERC-1155 balances for an address `GET /collections/{id}/balance` Parameters: id (path, string, required): Collection ID; address (query, string, required): Wallet address; token_id (query, string): Single token ID (use either token_id or token_ids); token_ids (query, string): Comma-separated list of token IDs (max 100) Returns 200: OK → { address (string), balances (array), collection_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Prepare collection deployment calldata `POST /collections/{id}/deploy/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error #### List per-chain deployments for a collection `GET /collections/{id}/deployments` Parameters: id (path, string, required): Collection ID Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Estimate deployment gas cost `POST /collections/{id}/estimate` Parameters: id (path, string, required): Collection ID Returns 200: OK → { currency (string), estimated_cost_eth (string), estimated_cost_wei (string), gas_estimate (integer), gas_price_wei (string), network (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Get next ERC-1155 token ID `GET /collections/{id}/next-token-id` Parameters: id (path, string, required): Collection ID Returns 200: OK → { collection_id (string), contract_type (string), next_token_id (integer) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Get on-chain supply (collection-wide or per-token) `GET /collections/{id}/supply` Parameters: id (path, string, required): Collection ID; token_id (query, string): Per-token supply (ERC-1155 only) Returns 200: OK → { collection_id (string), max_supply (integer), total_supply (integer) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Get on-chain tokenURI `GET /collections/{id}/token-uri/{token_id}` Parameters: id (path, string, required): Collection ID; token_id (path, string, required): Token ID Returns 200: OK → { collection_id (string), token_id (string), token_uri (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Verify a deployed collection contract on the explorer `POST /collections/{id}/verify` Parameters: id (path, string, required): Collection ID Returns 200: OK → { contract_address (string), explorer_url (string), message (string), success (boolean) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 500: Internal Server Error #### Verify token ownership `GET /collections/{id}/verify-owner` Parameters: id (path, string, required): Collection ID; token_id (query, string, required): Token ID; address (query, string, required): Wallet address to check Returns 200: OK → { address (string), collection_id (string), is_owner (boolean), token_id (string), token_type (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway ### Admin #### Prepare pause-collection calldata `POST /collections/{id}/admin/pause/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare transfer-ownership calldata `POST /collections/{id}/admin/transfer-ownership/prepare` Parameters: id (path, string, required): Collection ID Body: { new_owner (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare unpause-collection calldata `POST /collections/{id}/admin/unpause/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Burn #### Prepare unified burn calldata (single + ERC-1155 batch) `POST /collections/{id}/burn/prepare` Parameters: id (path, string, required): Collection ID Body: { items (array) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Config #### Prepare set-base-URI calldata (post-deploy config) `POST /collections/{id}/config/base-uri/prepare` Parameters: id (path, string, required): Collection ID Body: { base_uri (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare set-max-supply calldata (post-deploy config) `POST /collections/{id}/config/max-supply/prepare` Parameters: id (path, string, required): Collection ID Body: { max_supply (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare set-mint-price calldata (post-deploy config) `POST /collections/{id}/config/mint-price/prepare` Parameters: id (path, string, required): Collection ID Body: { mint_price (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare toggle-public-mint calldata (post-deploy config) `POST /collections/{id}/config/public-mint/toggle/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare set-token-max-supply calldata (ERC-1155 only) `POST /collections/{id}/config/token-max-supply/prepare` Parameters: id (path, string, required): Collection ID Body: { max_supply (string); token_id (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Media #### List pre-deploy media uploads for a collection `GET /collections/{id}/media` Parameters: id (path, string, required): Collection ID; status (query, string): Filter by asset status; page (query, integer): 1-indexed page number; limit (query, integer): Page size (max 100); sort (query, string): Sort field; '-' prefix for desc Returns 200: OK → { has_next (boolean), items (array), limit (integer), page (integer), total (integer) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Upload media (single + bulk) to a draft collection `POST /collections/{id}/media` Parameters: id (path, string, required): Collection ID Returns 201: Created → { failed_count (integer), items (array), uploaded_count (integer) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 413: Request Entity Too Large; 503: Service Unavailable #### Delete a pre-deploy media upload `DELETE /collections/{id}/media/{media_id}` Parameters: id (path, string, required): Collection ID; media_id (path, string, required): Media (asset) ID Returns 204: No Content Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 503: Service Unavailable #### Generate a short-lived presigned URL for a media object `POST /collections/{id}/media/{media_id}/presigned-url` Parameters: id (path, string, required): Collection ID; media_id (path, string, required): Media (asset) ID Returns 200: OK → { expires_in (integer), presigned_url (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 503: Service Unavailable #### Finalize a direct-to-S3 media upload `POST /collections/{id}/media/finalize` Parameters: id (path, string, required): Collection ID Body: { content_hash (string); image_content_type (string); image_name (string); s3_key (string); upload_id (string) } Returns 201: Created → { asset_index (integer), collection_id (string), contract_address (string), created_at (string), id (string), image_ipfs_cid (string), image_url (string), max_supply (integer), metadata_ipfs_cid (string), metadata_json (object), metadata_uri (string), owner_address (string), quantity (integer), status (string), token_id (string), token_name (string), token_type (string), transaction_hash (string), updated_at (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 503: Service Unavailable #### Issue a presigned PUT URL for direct-to-S3 media upload `POST /collections/{id}/media/presigned-upload` Parameters: id (path, string, required): Collection ID Body: { content_hash (string); image_content_type (string) } Returns 200: OK → { already_exists (boolean), asset (object), expires_in (integer), presigned_url (string), s3_key (string), upload_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 503: Service Unavailable ### Metadata #### Prepare metadata-refresh calldata (per-strategy) `POST /collections/{id}/metadata/refresh/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Mint #### Prepare unified mint calldata (single + batch) `POST /collections/{id}/mint/prepare` Parameters: id (path, string, required): Collection ID Body: { items (array) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare public-mint calldata (single + batch) `POST /collections/{id}/public-mint/prepare` Parameters: id (path, string, required): Collection ID Body: { items (array) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Minters #### Get minter authorization status `GET /collections/{id}/minters/{address}` Parameters: id (path, string, required): Collection ID; address (path, string, required): Minter wallet address Returns 200: OK → { collection_id (string), contract_type (string), is_authorized (boolean), minter_address (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Prepare authorize-minter calldata `POST /collections/{id}/minters/authorize/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare revoke-minter calldata `POST /collections/{id}/minters/revoke/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Royalties #### Get the collection default royalty `GET /collections/{id}/royalties/default` Parameters: id (path, string, required): Collection ID Returns 200: OK → { collection_id (string), fee_numerator (integer), fee_percentage (number), is_set (boolean), receiver (string), token_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Prepare delete-default-royalty calldata `POST /collections/{id}/royalties/default/delete/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare set-default-royalty calldata `POST /collections/{id}/royalties/default/prepare` Parameters: id (path, string, required): Collection ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Get the per-token royalty `GET /collections/{id}/royalties/nfts/{token_id}` Parameters: id (path, string, required): Collection ID; token_id (path, string, required): On-chain token ID Returns 200: OK → { collection_id (string), fee_numerator (integer), fee_percentage (number), is_set (boolean), receiver (string), token_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error; 502: Bad Gateway #### Prepare delete-token-royalty calldata `POST /collections/{id}/royalties/nfts/{token_id}/delete/prepare` Parameters: id (path, string, required): Collection ID; token_id (path, string, required): On-chain token ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway #### Prepare set-token-royalty calldata `POST /collections/{id}/royalties/nfts/{token_id}/prepare` Parameters: id (path, string, required): Collection ID; token_id (path, string, required): On-chain token ID Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Transfer #### Prepare unified transfer calldata (single + ERC-1155 batch) `POST /collections/{id}/transfer/prepare` Parameters: id (path, string, required): Collection ID Body: { items (array) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Withdraw #### Prepare withdraw calldata `POST /collections/{id}/withdraw/prepare` Parameters: id (path, string, required): Collection ID Body: { recipient (string) } Returns 200: OK → { batch_size (integer), calldata (object), expires_at (string), gas (object), operation_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 409: Conflict; 500: Internal Server Error; 502: Bad Gateway ### Health #### Health check (liveness) `GET /health` Returns 200: OK → { status (string) } Errors: 503: Service Unavailable ### Operations #### Get operation status + calldata + timeline `GET /operations/{id}` Parameters: id (path, string, required): Operation ID Returns 200: OK → { asset_id (string), batch_size (integer), block_number (integer), calldata (object), chain_id (string), collection_id (string), confirmed_at (string), created_at (string), expires_at (string), operation_id (string), status (string), status_timeline (array), tx_hash (string), verb (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Submit a prepared operation gaslessly via the bundler `POST /operations/{id}/submit-gasless` Parameters: id (path, string, required): Operation ID Returns 200: OK → { asset_id (string), batch_size (integer), block_number (integer), calldata (object), chain_id (string), collection_id (string), confirmed_at (string), created_at (string), expires_at (string), operation_id (string), status (string), status_timeline (array), tx_hash (string), verb (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 409: Conflict; 410: Gone; 500: Internal Server Error; 501: Not Implemented; 502: Bad Gateway #### Report tx hash for a prepared operation `POST /operations/{id}/tx` Parameters: id (path, string, required): Operation ID Body: { tx_hash (string) } Returns 200: OK → { asset_id (string), batch_size (integer), block_number (integer), calldata (object), chain_id (string), collection_id (string), confirmed_at (string), created_at (string), expires_at (string), operation_id (string), status (string), status_timeline (array), tx_hash (string), verb (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 409: Conflict; 410: Gone; 500: Internal Server Error ### Webhooks #### Receive HMAC-signed webhook event `POST /webhooks/events` Returns 200: OK Errors: 400: validation_failed; 401: auth_missing | auth_invalid --- # FabricBloc Token Service API > ERC-20 token deployment and management for the FabricBloc platform. Deploy fungible tokens, mint, burn, transfer, and manage supply — all without writing Solidity. ## Base URL `/v1/token` ## Authentication All requests go through the Kong API Gateway, which validates JWTs and enriches requests with `X-User-ID` and `X-Subscription-Tier` headers. You only need to send `Authorization: Bearer ` — Kong handles the rest. ## Endpoints ### Tokens #### List tokens for authenticated user `GET /v1/tokens` Parameters: page (query, integer): Page number (default 1); limit (query, integer): Page size (default 20) Returns 200: OK → { has_next (boolean), has_previous (boolean), items (array), limit (integer), page (integer), total (integer) } Errors: 401: Unauthorized; 500: Internal Server Error #### Create a new token `POST /v1/tokens` Parameters: X-Client-Type (header, string): Client type: sdk, frontend, fe, web, browser (optional - auto-detected if not provided) Body: { decimals (integer); initialSupply (string); maxSupply (string); name (string); network (string); symbol (string) } Returns 200: OK → { chain_id (string), created_at (string), decimals (integer), deploy_block (integer), deploy_tx_hash (string), error (string), id (string), is_paused (boolean), max_supply (string), name (string), network (string), status (string), symbol (string), token_address (string), token_type (string), total_supply (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 500: Internal Server Error #### Delete a draft token `DELETE /v1/tokens/{id}` Parameters: id (path, string, required): Token UUID Returns 204: Token deleted successfully Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 500: Internal Server Error #### Get token information `GET /v1/tokens/{id}` Parameters: id (path, string, required): Token UUID; X-Client-Type (header, string): Client type: sdk, frontend, fe, web, browser (optional - auto-detected if not provided) Returns 200: OK → { chain_id (string), created_at (string), decimals (integer), deploy_block (integer), deploy_tx_hash (string), error (string), id (string), is_paused (boolean), max_supply (string), name (string), network (string), status (string), symbol (string), token_address (string), token_type (string), total_supply (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Update a draft token `PATCH /v1/tokens/{id}` Parameters: id (path, string, required): Token UUID Body: { decimals (integer); initialSupply (string); maxSupply (string); name (string); network ("sepolia"); symbol (string) } Returns 200: OK → { chain_id (string), created_at (string), decimals (integer), deploy_block (integer), deploy_tx_hash (string), error (string), id (string), is_paused (boolean), max_supply (string), name (string), network (string), status (string), symbol (string), token_address (string), token_type (string), total_supply (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 403: Forbidden; 404: Not Found; 500: Internal Server Error #### Get token balance for an address `GET /v1/tokens/{id}/balance/{address}` Parameters: id (path, string, required): Token UUID; address (path, string, required): Ethereum address to check balance for Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Burn tokens `POST /v1/tokens/{id}/burn` Parameters: id (path, string, required): Token (UUID) Body: { amount (string) } Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Deploy a draft token by ID `POST /v1/tokens/{id}/deploy` Parameters: id (path, string, required): Token UUID Returns 200: OK → { chain_id (string), created_at (string), decimals (integer), deploy_block (integer), deploy_tx_hash (string), error (string), id (string), is_paused (boolean), max_supply (string), name (string), network (string), status (string), symbol (string), token_address (string), token_type (string), total_supply (string), updated_at (string), user_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Estimate gas for token deployment `GET /v1/tokens/{id}/estimate-gas` Parameters: id (path, string, required): Token UUID Returns 200: OK → { currency (string), estimated_cost_wei (string), gas_estimate (string), gas_price (string), gas_price_wei (string), network (string), total_cost (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Mint tokens `POST /v1/tokens/{id}/mint` Parameters: id (path, string, required): Token UUID Body: { amount (string); to (string) } Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Pause or unpause token `POST /v1/tokens/{id}/pause` Parameters: id (path, string, required): Token UUID Body: { action (string): "pause" or "unpause" } Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Get paused status `GET /v1/tokens/{id}/paused` Parameters: id (path, string, required): Token UUID Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Transfer tokens `POST /v1/tokens/{id}/transfer` Parameters: id (path, string, required): Token UUID Body: { amount (string); to (string) } Returns 200: OK Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error ### Health #### Health check endpoint `GET /v1/tokens/health` Returns 200: OK → { service (string), status (string), timestamp (string) } --- # FabricBloc Wallet Service API > Self-custodial MPC wallet management for the FabricBloc platform. Every consumer gets a non-custodial wallet secured by 2-of-3 threshold Multi-Party Computation. ## Base URL `/v1/wallet` ## Authentication All requests go through the Kong API Gateway, which validates JWTs and enriches requests with `X-User-ID` and `X-Subscription-Tier` headers. You only need to send `Authorization: Bearer ` — Kong handles the rest. ## Endpoints ### Wallet #### Get a wallet by id `GET /v1/wallet/{walletId}` Parameters: walletId (path, string, required): Wallet UUID Returns 200: OK → { created_at (string), id (string), is_smart_account_owner (boolean), label (string), mpc_wallet (object), signer_address (string), smart_account_address (string), status (string), wallet_type (string) } Errors: 400: Malformed walletId; 401: Unauthorized; 404: Wallet not found in the caller's project; 500: Internal Server Error #### Get consumer's wallet `GET /v1/wallet/consumer/{consumerId}` Parameters: consumerId (path, string, required): Consumer UUID Returns 200: OK → { created_at (string), id (string), is_smart_account_owner (boolean), label (string), mpc_wallet (object), signer_address (string), smart_account_address (string), status (string), wallet_type (string) } Errors: 400: Invalid consumer ID; 401: Unauthorized; 404: No wallet found for consumer; 500: Internal Server Error #### List consumers `GET /v1/wallet/consumers` Parameters: page (query, integer): Page number (default: 1); limit (query, integer): Items per page (default: 20, max: 100) Returns 200: OK → { consumers (array), limit (integer), page (integer), total (integer) } Errors: 403: API key authentication required; 500: Internal Server Error #### Register an EOA wallet `POST /v1/wallet/eoa` Body: { eoa_address (string, required); consumer_id (string) } Returns 200: EOA wallet registered → { created_at (string), id (string), is_smart_account_owner (boolean), label (string), mpc_wallet (object), signer_address (string), smart_account_address (string), status (string), wallet_type (string) } Errors: 400: Invalid request or eoa_address; 401: Authentication required; 500: Failed to register EOA wallet ### Deployment #### List a wallet's deployments `GET /v1/wallet/{walletId}/deployments` Parameters: walletId (path, string, required): Wallet UUID Returns 200: Deployment status per chain Errors: 401: Authentication required; 404: Wallet not found or not owned by caller; 500: Failed to list deployments #### Get a wallet's deployment status on a chain `GET /v1/wallet/{walletId}/deployments/{chainId}` Parameters: walletId (path, string, required): Wallet UUID; chainId (path, integer, required): Target chain ID Returns 200: Deployment status → { chain_id (integer), factory_address (string), factory_version (string), implementation_address (string), implementation_version (string), signer_address (string), signing_method (string), smart_account_address (string), status (string), transaction_hash (string), wallet_id (string) } Errors: 400: Invalid chainId; 401: Authentication required; 404: Wallet not found or not owned by caller; 500: Failed to get deployment status #### Prepare a wallet-scoped ownership transfer `POST /v1/wallet/{walletId}/ownership-transfer/prepare` Parameters: walletId (path, string, required): Source wallet UUID (smart account being transferred away) Body: { chain_id (integer, required); consumer_id (string): UUID of the Consumer who owns both the current smart account and the new wallet/EOA. Required for api-key credential auth; optional and ignored for delegated-session auth.; new_eoa_address (string): EOA address (mutually exclusive with new_wallet_id); new_wallet_id (string): MPC wallet UUID (mutually exclusive with new_eoa_address) } Returns 200: Transfer UserOp prepared → { expires_in_seconds (integer), needs_deploy (boolean), owner_address (string), user_op (object), user_op_hash (string), wallet_id (string), wallet_type (object) } Errors: 400: Invalid request; 401: Authentication required; 404: Source wallet, destination wallet, or deployment not found; 429: Too many requests ### Devices #### List a wallet's device keys `GET /v1/wallet/{walletId}/devices` Parameters: walletId (path, string, required): Wallet UUID Returns 200: OK → { devices (array), total (integer) } Errors: 400: Malformed walletId; 401: Unauthorized; 404: Wallet not found in the caller's project #### Register a device key for a wallet `POST /v1/wallet/{walletId}/devices` Parameters: walletId (path, string, required): Wallet UUID Body: { platform (string, required); public_key (string, required); wallet_id (string, required); authorization_nonce (string); authorization_signature (string): Signature from an existing device authorizing this registration (multi-device flow). Not required for first device registration.; consumer_id (string); device_model (string); key_algorithm (string); name (string); os_version (string) } Returns 201: Created → { created_at (string), device_model (string), id (string), is_active (boolean), is_primary (boolean), key_algorithm (string), last_used_at (string), name (string), os_version (string), platform (string), public_key (string), use_count (integer), wallet_id (string) } Errors: 400: Malformed walletId or invalid body; 401: Unauthorized; 404: Wallet not found in the caller's project #### Get a wallet's device key by id `GET /v1/wallet/{walletId}/devices/{deviceId}` Parameters: walletId (path, string, required): Wallet UUID; deviceId (path, string, required): Device key UUID Returns 200: OK → { created_at (string), device_model (string), id (string), is_active (boolean), is_primary (boolean), key_algorithm (string), last_used_at (string), name (string), os_version (string), platform (string), public_key (string), use_count (integer), wallet_id (string) } Errors: 400: Malformed id; 401: Unauthorized; 404: Device not found #### Complete multi-device authorization `POST /v1/wallet/{walletId}/devices/authorize/complete` Parameters: walletId (path, string, required): Wallet UUID Body: { nonce (string, required); signature (string, required); consumer_id (string) } Returns 200: OK Errors: 400: Malformed walletId or invalid body; 401: Unauthorized; 404: Wallet not found in the caller's project #### Initiate multi-device authorization `POST /v1/wallet/{walletId}/devices/authorize/initiate` Parameters: walletId (path, string, required): Wallet UUID Body: { new_device_platform (string, required); new_device_public_key (string, required); consumer_id (string); new_device_name (string) } Returns 201: Created Errors: 400: Malformed walletId or invalid body; 401: Unauthorized; 404: Wallet not found in the caller's project ### Wallet Lifecycle #### Freeze a wallet `POST /v1/wallet/{walletId}/freeze` Parameters: walletId (path, string, required): Wallet UUID Body: { reason (string, required) } Returns 200: Wallet frozen → { frozen (boolean), frozen_at (string), frozen_reason (string), id (string), status (string) } Errors: 400: Bad Request - invalid wallet ID or missing reason; 401: Unauthorized - authentication required; 404: Not Found - wallet not found in this project; 409: Conflict - wallet is already frozen; 500: Internal Server Error #### Unfreeze a wallet `POST /v1/wallet/{walletId}/unfreeze` Parameters: walletId (path, string, required): Wallet UUID Returns 200: Wallet unfrozen → { frozen (boolean), frozen_at (string), frozen_reason (string), id (string), status (string) } Errors: 400: Bad Request - invalid wallet ID; 401: Unauthorized - authentication required; 404: Not Found - wallet not found in this project; 409: Conflict - wallet is not frozen; 500: Internal Server Error ### Operations #### List a wallet's operations `GET /v1/wallet/{walletId}/operations` Parameters: walletId (path, string, required): Wallet UUID; status (query, string): Filter by operation status; chain_id (query, integer): Filter by chain id; type (query, string): Filter by operation type; page (query, integer): Page (default 1); limit (query, integer): Page size (default 10) Returns 200: OK → { limit (integer), nextPage (integer), operations (array), page (integer), prevPage (integer), total (integer), totalPages (integer) } Errors: 400: Invalid query; 401: Authentication required; 404: Wallet not found or not owned by caller #### Get a wallet-scoped operation `GET /v1/wallet/{walletId}/operations/{opHash}` Parameters: walletId (path, string, required): Wallet UUID; opHash (path, string, required): Operation user_op_hash Returns 200: OK → { actualGasCost (string), blockNumber (integer), chainId (integer), confirmedAt (string), errorCode (string), errorMessage (string), gasPrice (string), gasUsed (string), id (string), nonce (string), operationHash (string), paymaster (string), referenceId (string), referenceType (string), revertReason (string), sender (string), status (string), submittedAt (string), success (boolean), to (string), transactionHash (string), type (string), value (string), walletId (string) } Errors: 401: Authentication required; 404: Operation not found or not owned by this wallet #### Confirm a wallet-scoped operation `POST /v1/wallet/{walletId}/operations/{opHash}/confirm` Parameters: walletId (path, string, required): Wallet UUID; opHash (path, string, required): Operation user_op_hash Returns 200: Current operation status after reconcile → { actualGasCost (string), blockNumber (integer), chainId (integer), confirmedAt (string), errorCode (string), errorMessage (string), gasPrice (string), gasUsed (string), id (string), nonce (string), operationHash (string), paymaster (string), referenceId (string), referenceType (string), revertReason (string), sender (string), status (string), submittedAt (string), success (boolean), to (string), transactionHash (string), type (string), value (string), walletId (string) } Errors: 401: Authentication required; 404: Operation not found or not owned by this wallet; 500: Reconciler not configured or reconcile failed #### Get a wallet-scoped operation's legs `GET /v1/wallet/{walletId}/operations/{opHash}/legs` Parameters: walletId (path, string, required): Wallet UUID; opHash (path, string, required): Operation user_op_hash Returns 200: OK Errors: 401: Authentication required; 404: Operation not found or not owned by this wallet #### Prepare a wallet-scoped batch operation `POST /v1/wallet/{walletId}/operations/batch/prepare` Parameters: walletId (path, string, required): Wallet UUID Body: { chain_id (integer, required); operations (array, required); consumer_id (string): UUID of the Consumer the batch targets. Required for api-key credential auth; optional and ignored for delegated-session auth.; signature_type (string); wallet_id (string) } Returns 200: Batch operation prepared → { batch_size (integer), expires_in_seconds (integer), needs_deploy (boolean), owner_address (string), user_op (object), user_op_hash (string), wallet_id (string), wallet_type ("mpc" | "eoa") } Errors: 400: Invalid request; 401: Authentication required; 404: Wallet not found or not owned by caller #### Estimate gas for a wallet-scoped operation `POST /v1/wallet/{walletId}/operations/estimate-gas` Parameters: walletId (path, string, required): Wallet UUID Body: { amount (string, required); networkId (string, required); toAddress (string, required); tokenSymbol (string, required); data (string); fromAddress (string) } Returns 200: OK → { baseFee (integer), feeOptions (object), gasLimit (integer), lastUpdated (string), priorityFee (object), units (object) } Errors: 400: Invalid request; 401: Authentication required; 404: Wallet not found or not owned by caller; 429: Too many requests #### Prepare a wallet-scoped operation `POST /v1/wallet/{walletId}/operations/prepare` Parameters: walletId (path, string, required): Wallet UUID Body: { chain_id (integer, required): Target chain (e.g., 1=Ethereum, 137=Polygon); consumer_id (string): UUID of the Consumer the operation targets. Required for api-key credential auth; optional and ignored for delegated-session auth (Kong's signed X-External-Consumer-ID is authoritative).; data (string): Calldata for contract interaction (optional); op_type (string): OpType classifies the operation intent. Accepted values: "" (default, same as "call"), "transfer", "call", and "deploy" (delegates to the guarded deployment path). Validation is enforced at the HTTP handler layer, not here, so the service remains agnostic to the transport concern.; reference_id (string): Operation reference (optional): links this operation to a domain object for automatic status tracking. Webhook handler uses these to auto-update domain models on confirm/fail.; reference_type (string): Type identifier (e.g., "session_key_registration"); signature_type (string): SignatureType hints the expected signature format for accurate gas estimation. "session_key" → 225-byte composite placeholder. Default (empty) → 65-byte ECDSA. Only affects gas estimation — never stored or submitted on-chain.; to (string): Destination address (0x-prefixed). Required for transfer/call; enforced at the handler since op_type=deploy omits it.; value (string): ETH value in wei as decimal string (optional, default "0"); wallet_id (string): UUID of the user's wallet (optional for JWT auth — auto-resolved) } Returns 200: Operation prepared → { expires_in_seconds (integer), needs_deploy (boolean), owner_address (string), user_op (object), user_op_hash (string), wallet_id (string), wallet_type (object) } Errors: 400: Invalid request or op_type; 401: Authentication required; 404: Wallet not found or not owned by caller; 500: Failed to prepare operation #### Submit a signed wallet-scoped operation `POST /v1/wallet/{walletId}/operations/submit` Parameters: walletId (path, string, required): Wallet UUID Body: { signature (string, required); user_op_hash (string, required) } Returns 200: Operation submitted (or idempotent replay) Errors: 400: Invalid request; 401: Authentication required; 404: Prepared operation not found for this wallet; 409: Operation already in flight; 410: Prepared operation expired #### Validate a wallet-scoped operation `POST /v1/wallet/{walletId}/operations/validate` Parameters: walletId (path, string, required): Wallet UUID Body: { chain_id (integer, required); to (string, required); consumer_id (string): UUID of the Consumer the validation targets. Required for api-key credential auth; optional and ignored for delegated-session auth.; data (string); value (string); wallet_id (string) } Returns 200: OK → { chain_id (integer), needs_deploy (boolean), smart_account_address (string), valid (boolean), wallet_id (string), wallet_type ("mpc" | "eoa") } Errors: 400: Invalid request; 401: Authentication required; 404: Wallet not found or not owned by caller ### MPC Wallet #### Retry wallet registration `POST /v1/wallet/{walletId}/retry-registration` Parameters: walletId (path, string, required): The wallet UUID (from DKG creation) Returns 200: Event published successfully Errors: 400: Bad Request - Invalid wallet ID format; 401: Unauthorized - User ID not found in token; 404: Not Found - Wallet not found or not an MPC wallet; 500: Internal Server Error - Failed to publish event ### MPC #### Complete secure MPC signing `POST /v1/wallet/{walletId}/sign/complete` Parameters: walletId (path, string, required): Wallet UUID Body: { contribution (string, required): 0x-prefixed hex: r × x_device mod n; session_id (string, required) } Returns 200: OK → { r (string), s (string), signature (string), v (integer) } Errors: 400: Validation error or session/path wallet mismatch; 401: Caller identity missing; 403: Session does not belong to this caller; 404: Session not found or expired; 500: Internal Server Error #### Initialize secure MPC signing session `POST /v1/wallet/{walletId}/sign/init` Parameters: walletId (path, string, required): Wallet UUID Body: { message_hash (string, required): 0x-prefixed, 32 bytes hex } Returns 200: OK → { expires_in (integer), r (string), session_id (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Wallet not found in the caller's project; 500: Internal Server Error #### Initiate DKG protocol `POST /v1/wallet/consumer/{consumerId}/dkg/initiate` Parameters: Idempotency-Key (header, string): Idempotency key (UUID). Repeated calls with the same key and body return the cached response.; consumerId (path, string, required): Consumer the wallet is created for (resource-id addressing) Body: { chain_id (integer, required); client_aes_key (string, required): base64, 32 raw bytes after decode; device_info (object, required); device_public_key (string, required); label (string) } Returns 200: OK → { dkg_session_id (string), smart_account_address (string), status (string), wallet_id (string) } Errors: 400: Bad Request; 409: Conflict; 500: Internal Server Error #### Create MPC wallet with streaming progress (SDK-Orchestrated) `POST /v1/wallet/consumer/{consumerId}/dkg/stream` Parameters: consumerId (path, string, required): Consumer the wallet is created for (resource-id addressing) Body: { chain_id (integer, required); client_aes_key (string, required): base64, 32 raw bytes after decode; device_info (object, required); device_public_key (string, required); label (string) } Returns 200: Streamed via SSE event 'dkg.complete' Errors: 400: Streamed via SSE event 'dkg.error'; 500: Streamed via SSE event 'dkg.error' #### Recovery-rekey an MPC wallet with streaming progress `POST /v1/wallet/consumer/{consumerId}/recovery/rekey/stream` Parameters: consumerId (path, string, required): Consumer that owns the wallet being recovered Body: { chain_id (integer, required); client_aes_key (string, required): base64, 32 raw bytes after decode; device_info (object, required); device_public_key (string, required); label (string) } Returns 200: Streamed via SSE event 'dkg.complete' Errors: 400: Streamed via SSE event 'dkg.error'; 500: Streamed via SSE event 'dkg.error' #### Rename an MPC wallet `PATCH /v1/wallet/mpc/{walletId}/label` Parameters: walletId (path, string, required): MPC wallet UUID Body: { label (string, required) } Returns 200: OK → { created_at (string), id (string), is_smart_account_owner (boolean), label (string), mpc_wallet (object), signer_address (string), smart_account_address (string), status (string), wallet_type (string) } Errors: 400: Bad Request; 401: Unauthorized; 404: Not Found; 500: Internal Server Error #### Complete DKG protocol `POST /v1/wallet/mpc/dkg/complete` Body: { device_contribution (string, required): base64; dkg_session_id (string, required) } Returns 200: OK → { encrypted_envelope (string), signer_address (string), smart_account_address (string), status (string), wallet_id (string), wallet_label (string) } Errors: 400: Bad Request; 500: Internal Server Error ### Shard Rotation #### Rotate backup shard `POST /v1/wallet/mpc/shards/rotate-backup` Body: { deviceShard (string, required): Base64 encoded device shard for 2-of-3 verification; fabricBlocShardId (string, required): ID of the FabricBloc shard to identify the MPC key; consumer_id (string): UUID of the Consumer whose shard is being rotated. Required for api-key credential auth; optional and ignored for delegated-session auth.; currentBackupHash (string): Optional: SHA256 hash of current backup shard for verification } Returns 200: OK → { gracePeriodEnds (string), message (string), newBackupShard (string), rotationId (string), status (string) } Errors: 400: Invalid request; 401: Unauthenticated - missing caller identity; 404: Not Found - shard not found or not owned by the caller; 500: Internal server error #### Rotate device shard `POST /v1/wallet/mpc/shards/rotate-device` Body: { currentDeviceShard (string, required): Base64 encoded current device shard for verification; fabricBlocShardId (string, required): ID of the FabricBloc shard to identify the MPC key; consumer_id (string): UUID of the Consumer whose shard is being rotated. Required for api-key credential auth; optional and ignored for delegated-session auth (Kong's signed X-External-Consumer-ID is authoritative).; newDevicePublicKey (string): Optional: new device public key for the reshared shard } Returns 200: OK → { gracePeriodEnds (string), message (string), newDeviceShard (string), rotationId (string), status (string) } Errors: 400: Invalid request; 401: Unauthenticated - missing caller identity; 404: Not Found - shard not found or not owned by the caller; 500: Internal server error #### Get shard rotation status `GET /v1/wallet/mpc/shards/rotation/{rotationId}/status` Parameters: rotationId (path, string, required): Rotation ID; consumer_id (query, string): Consumer UUID whose rotation is being read. Required for api-key credential auth; ignored for delegated-session auth. Returns 200: OK → { completedAt (string), createdAt (string), errorMessage (string), gracePeriodEnds (string), newShardActive (boolean), oldShardActive (boolean), rotationId (string), shardType (string), status (string) } Errors: 400: Invalid rotation ID or missing consumer_id; 404: Rotation not found ### Utilities #### Validate blockchain address `POST /v1/wallet/validate-address` Body: { address (string, required); chainType (string, required): Chain type (ethereum, polygon, binance, base, solana, etc.) } Returns 200: Address validation result. → { addressType (string), chainType (string), isValid (boolean), message (string) } Errors: 400: Bad Request - Invalid request body or validation errors.; 401: Unauthorized - User ID not found in token or invalid token.; 500: Internal Server Error - Failed to validate address due to a server-side issue.