diff --git a/docs/content/envoy/_index.en.md b/docs/content/envoy/_index.en.md index b058ddc..bc36b27 100644 --- a/docs/content/envoy/_index.en.md +++ b/docs/content/envoy/_index.en.md @@ -1,9 +1,68 @@ --- title: TRISA Envoy -date: 2021-04-23T01:35:35-04:00 -lastmod: 2022-08-10T13:22:02-04:00 +date: 2024-05-09T10:14:24-04:00 +lastmod: 2024-05-09T10:14:24-04:00 description: "Using the TRISA Envoy self-hosted node" -weight: 7 +weight: 12 --- -Documentation coming soon! \ No newline at end of file +Built to help compliance teams handle travel rule exchanges efficiently and economically, Envoy is an open source, secure, peer-to-peer messaging platform. Designed by TRISA engineers and compliance experts, Envoy offers a mechanism for Travel Rule compliance by providing [IVMS101](https://www.intervasp.org/) data exchanges using the [TRISA](https://trisa.io) and [TRP](https://www.openvasp.org/) protocols. + +#### Key Benefits + +- **Intuitive UI**: Send and receive TRISA & TRP messages with a user-friendly interface. +- **Efficient Administration**: Simplify setup and maintenance with our admin tools. +- **Secure Decentralized Messaging**: Protect customer privacy and IVMS101 data with encrypted peer-to-peer communication. +- **Compliance-Friendly API**: A straightforward JSON REST API for seamless TRISA & TRP interactions. + +#### Not Included + +It is important to understand what Envoy is, and also, what Envoy _is not_. + +Envoy is strictly a **secure messaging service** designed to meet FATF requirements. + +Envoy does not: +- Include _on chain analytics or automated KYC/AML checks_. Future plug-ins may be available for on-chain analytics or KYC checks. +- Solve the **Sunrise** problem (nothing does yet). + +## Implementation Options + +You have three options to deploy or manage your Envoy node: + +1. **DIY**: Envoy is open source ([MIT License](https://github.com/trisacrypto/envoy/blob/main/LICENSE)). You're free to download, install, integrate, host and support your own node. You're also free to fork and modify the node for your own use cases. Provided you have the technical capabilities and availability, you can have complete flexibility and control of your deployment! +2. **One-time Integration Service**: For a one-time fee, the Envoy team will install and configure your Envoy node in your environment while you host, maintain, and support the node on an ongoing basis. The Envoy team will provide some training to get started with your node, and upgrade emails when it is time to update the Envoy version. +3. **Managed Service**: If you're looking to get something up and running now, The Envoy team will install, configure, host, maintain, and support an Envoy node for you. + +This documentation is focused on the DIY option. If you'd like more information on the one-time integration service or managed services, please [schedule a demo](https://rtnl.link/p2WzzmXDuSu) with the Envoy team! + +## Data Storage and Security + +Because Envoy is intended to exchange and store compliance information that is by nature PII (Personally Identifiable Information), security and privacy is a top-of-mind concern. To that end, Envoy works to ensure data is stored in a secure and protected fashion. + +### Data Storage + +Nodes must store compliance data locally on disk in a backend store. Currently only sqlite3 is supported, but Postgres and other databases may be options in the future. When configuring an Envoy node, ensure: + +1. Enough disk is provisioned for **long-term** travel rule data storage. +2. Ensure that the disk is independently **secured and encrypted**, particularly if you are hosting your Envoy service on a shared architecture or the cloud. +3. If you're using an external database, ensure it is not accessible from the public Internet. + +### Security + +Envoy employs TRISA cryptographic security standards for data in-flight and at-rest. Data exchanges are **secured by mTLS** when conducted over the TRISA network and if available for a TRP exchange. All TRP exchanges require valid TLS certificates to establish a secure connection. + +Travel Rule PII is stored as secure envelopes with original TRISA cryptography. Even if the transfer occurs over TRP, a secure envelope is created to store the information on disk. Secure envelopes use multi-stage strong encryption, encrypting data symmetrically with AES-256, signing the data with HMAC-256, then encrypting the encryption keys and hmac secrets via asymmetric encryption using RSA-OAEP-256. + +One you delete the private keys used to seal secure envelopes -- the data encrypted with those keys is effectively deleted. + +Some indexing information is extracted for lookups and and search, but is not PII. + +### Key Management + +Public key management is required for effective use of Envoy. Envoy can store key material locally on the same disk as its database, but this is not recommended. Instead we recommend the use of a key store or vault such as Google Secret Manager or Hashicorp Vault. + +### Authentication and Access + +We strongly recommend that the internal UI and API is restricted to specific IP addresses or accessed from within a VPN. This will prevent brute force attacks on your Envoy node. + +Passwords and API Key secrets are stored on the Envoy disk as [Argon2](https://github.com/P-H-C/phc-winner-argon2) hashes that cannot be reversed. Argon2 is a password-hashing function that includes a time and memory cost to balance defense against cracking attacks. Even using Argon2, it is still recommended that passwords are routinely changed and that strong passwords are generated. API keys should also be revoked if not in use, or recycled on a routine basis. \ No newline at end of file diff --git a/docs/content/envoy/api.en.md b/docs/content/envoy/api.en.md new file mode 100644 index 0000000..275305b --- /dev/null +++ b/docs/content/envoy/api.en.md @@ -0,0 +1,78 @@ +--- +title: API Guide +date: 2024-05-08T14:42:51-05:00 +lastmod: 2024-05-08T14:42:51-05:00 +description: "Guide to using the Envoy API" +weight: 90 +--- + +{{% notice style="tip" title="API Credentials" icon="rocket" %}} +In order to use the API you will have to generate an API Key Client ID and Secret either from the user interface, or by using the command line tool as described in ["creating api keys"]({{< relref "deploy.md#creating-api-keys" >}}). +{{% /notice %}} + +## Authentication and Authorization + +All API endpoints require authorization using `Bearer` tokens in the `Authorization` header. The `Bearer` token is a time-limited JWT token that is signed by the Envoy server. The JWT token contains the valid claims that the API key has: most importantly the _permissions_ assigned to the API Key. If the endpoint requires a permission that the API key does not have then a 403 error is returned. + +To obtain the JWT bearer token, you must first authenticate with the server using your client ID and secret with a `POST` request to `/v1/authenticate`. + +``` +POST /v1/authenticate HTTP/1.1 +Host: [ENDPOINT] +Accept: application/json +Content-Type: application/json + +{ + "client_id": "[CLIENT_ID]", + "client_secret": "[CLIENT_SECRET]" +} +``` + +The response will contain a JWT `access_token` and `refresh_token`. The `access_token` should be used as the `Authorization: Bearer` token for all API requests: + +``` +GET /v1/counterparties HTTP/1.1 +Host: [ENDPOINT] +Accept: application/json +Content-Type: application/json +Authorization: Bearer [ACCESS_TOKEN] +``` + +By default the access token expires after 1 hour and the refresh token expires after 2 hours, becoming available 15 minutes before the access token expires (though these durations can all be configured). + +To reauthenticate while the `refresh_token` is still valid, `POST` the `refresh_token` to the `/v1/reauthenticate` endpoint to obtain new access and refresh tokens without having to resupply the client ID and secret. + +``` +POST /v1/reauthenticate HTTP/1.1 +Host: [ENDPOINT] +Accept: application/json +Content-Type: application/json + +{ + "refresh_token": "[REFRESH_TOKEN]" +} +``` + +### Permissions + +The following table contains the permissions available to API keys: + +| Permission | Description | +|---|---| +| `users:manage` | Can create, edit, and delete users | +| `users:view` | Can view users registered on the node | +| `apikeys:manage` | Can create apikeys and view associated secret | +| `apikeys:view` | Can view apikeys created on the node | +| `apikeys:revoke` | Can revoke apikeys and delete them | +| `counterparties:manage` | Can create, edit, and delete counterparties | +| `counterparties:view` | Can view counterparty details | +| `accounts:manage` | Can create, edit, and delete accounts and crypto addresses | +| `accounts:view` | Can view accounts and crypto addresses | +| `travelrule:manage` | Can create, accept, reject, and archive transactions and send secure envelopes | +| `travelrule:delete` | Can delete transactions and associated secure envelopes | +| `travelrule:view` | Can view travel rule transactions and secure envelopes | +| `config:manage` | Can manage the configuration of the node | +| `config:view` | Can view the configuration of the node | +| `pki:manage` | Can create and edit certificates and sealing keys | +| `pki:delete` | Can delete certificates and sealing keys | +| `pki:view` | Can view certificates and sealing keys | \ No newline at end of file diff --git a/docs/content/envoy/configuration.en.md b/docs/content/envoy/configuration.en.md new file mode 100644 index 0000000..f469ef5 --- /dev/null +++ b/docs/content/envoy/configuration.en.md @@ -0,0 +1,77 @@ +--- +title: Configuration +date: 2024-05-08T12:14:45-05:00 +lastmod: 2024-05-08T12:14:45-05:00 +description: "Configuring Envoy" +weight: 70 +--- + +For the latest and most up to date description of the Envoy configuration, ask Envoy directly! You can do this using the Envoy docker image as follows: + +``` +$ docker run trisa/envoy:latest envoy config +``` + +This will print out a table of the configuration options, default values, and descriptions. If you'd prefer it in list form, run: + +``` +$ docker run trisa/envoy:latest envoy config --list +``` + +## Configuration Values + +Envoy is configured via the environment and for local development, also supports using `.env` files in the working directory for loading environment variables. We recommend configuring Envoy using the deployment mechanism of your choice. For example, if you're running the binary using `systemd`, then the environment should be defined in your `.service` using `Environment` or an `EnvironmentFile`. If you're using Kubernetes or Docker, then the environment variables should be added to the manifest of your deployment. + +A list of the primary environment variables and their configuration are as follows: + +| EnvVar | Type | Default | Description | +|---|---|---|---| +| TRISA_MAINTENANCE | bool | false | If true, the node will start in maintenance mode and will respond Unavailable to requests | +| TRISA_MODE | string | release | Specify the mode of the API/UI server (release, debug, or testing) | +| TRISA_LOG_LEVEL | string | info | Specify the verbosity of logging (trace, debug, info, warn, error, fatal, panic) | +| TRISA_CONSOLE_LOG | bool | false | If true, logs colorized human readable output instead of json | +| TRISA_DATABASE_URL | string | sqlite3:///trisa.db | DSN containing the backend database configuration | +| TRISA_ENDPOINT | string | | The endpoint of the TRISA node as defined by the mTLS certificates (to create travel addresses) | + +### Web UI/API Configuration + +These configuration values influence the behavior of the internal web UI and API. + +| EnvVar | Type | Default | Description | +|---|---|---|---| +| TRISA_WEB_ENABLED | bool | true | If false, both the web UI and API are disabled | +| TRISA_WEB_BIND_ADDR | string | :8000 | The IP address and port to bind the web server on | +| TRISA_WEB_ORIGIN | string | http://localhost:8000 | The origin (url) of the web UI for creating API endpoints | +| TRISA_WEB_AUTH_KEYS | map | | Optional static RSA key configuration for signing access and refresh tokens. Should be a comma separated map of keyID:path. | +| TRISA_WEB_AUTH_AUDIENCE | string | http://localhost:8000 | The value for the `aud` (audience) claim in JWT tokens issued by the API | +| TRISA_WEB_AUTH_ISSUER | string | http://localhost:8000 | The value for the `iss` (issuer) claim in JWT tokens issued by the API | +| TRISA_WEB_AUTH_COOKIE_DOMAIN | string | localhost | Limit cookies for the UI to the specified domain (exclude any port information) | +| TRISA_WEB_AUTH_ACCESS_TOKEN_TTL | duration | 1h | The amount of time before an access token expires | +| TRISA_WEB_AUTH_REFRESH_TOKEN_TTL | duration | 2h | The amount of time before refresh tokens expire | +| TRISA_WEB_AUTH_TOKEN_OVERLAP | duration | -15m | The amount of overlap between the access and refresh tokens, the more negative the duration the more the overlap | + +### TRISA Node Configuration + +Configuration values for the public facing TRISA node. + +| EnvVar | Type | Default | Description | +|---|---|---|---| +| TRISA_NODE_ENABLED | bool | true | If false, the TRISA node server will not be run | +| TRISA_NODE_BIND_ADDR | string | :8100 | The ip address and port to bind the TRISA node server on | +| TRISA_NODE_POOL | path | | The path to TRISA x509 certificate pool; this allows you to define what certificate authorities you're willing to accept using mTLS (optional) | +| TRISA_NODE_CERTS | path | | The path to your TRISA identify certificates and private key for establishing mTLS connections to TRISA peer counterparties | +| TRISA_NODE_KEY_EXCHANGE_CACHE_TTL | duration | 24h | The duration to cache public keys exchanged with remote TRISA nodes before performing another key exchange | + +### TRISA Directory Configuration + +The following configuration influences how the Envoy node connects to the TRISA Global Directory Service. + +If you're running a TestNet node, then ensure the values point to `trisatest.net` (e.g. `api.trisatest.net:443`), if you're running a MainNet node, then ensure the values point to `vaspdirectory.net` (the default values). + +| EnvVar | Type | Default | Description | +|---|---|---|---| +| TRISA_NODE_DIRECTORY_INSECURE | bool | false | If true, do not connect to the directory using TLS (only useful for local development) | +| TRISA_NODE_DIRECTORY_ENDPOINT | string | api.vaspdirectory.net:443 | The endpoint of the public GDS service | +| TRISA_NODE_DIRECTORY_MEMBERS_ENDPOINT | string | members.vaspdirectory.net:443 | The endpoint of the private members GDS service | +| TRISA_DIRECTORY_SYNC_ENABLED | bool | true | If false, then the background directory sync service will not run | +| TRISA_DIRECTORY_SYNC_INTERVAL | duration | 6h | The interval that the node will synchronize counterparties with the GDS | \ No newline at end of file diff --git a/docs/content/envoy/deploy.en.md b/docs/content/envoy/deploy.en.md new file mode 100644 index 0000000..49435a3 --- /dev/null +++ b/docs/content/envoy/deploy.en.md @@ -0,0 +1,114 @@ +--- +title: Deploying Envoy +date: 2024-05-08T13:56:37-05:00 +lastmod: 2024-05-08T13:56:37-05:00 +description: "A quick guide on deploying your Envoy node" +weight: 60 +--- + +This guide assumes that you're ready to deploy your Envoy node and that you've already obtained either TRISA TestNet or MainNet certificates as described by [the Joining TRISA guide]({{< ref "joining-trisa" >}}). If you haven't already, please go to the [TRISA Global Directory Service (vaspdirectory.net)](https://vaspdirectory.net/) to register for your certificates! + +{{% notice style="note" title="Local Development" icon="code" %}} +If you'd like information about how to run Envoy locally using [Docker Compose](https://docs.docker.com/compose/) and self-signed keys generated using `openssl` please go to the repository at [trisacrypto/envoy](https://github.com/trisacrypto/envoy) and follow the instructions in the `README.md`. +{{% /notice %}} + +The general/top-level steps to deploy an Envoy node are as follows: + +1. Obtain and decrypt TRISA certificates +2. Setup a deployment environment (e.g. a cloud instance or kubernetes cluster) +3. [Configure]({{< relref "configuration.md" >}}) the Envoy node via the environment +4. Deploy your Envoy node using one of the instructions below +5. Ensure that you can reach your node at port 443 +6. Configure DNS to point your TRISA endpoint at your node +7. Create users/api keys to access the internal UI/API + +## Deploying Envoy + +There are many ways to deploy Envoy and a lot depends on your internal infrastructure or cloud service provider. This guide provides examples for deploying Envoy using a Kubernetes cluster (the default way that we deploy our services) or using `systemd` on Ubuntu to run your Envoy service on a cloud instance. + +### Using Kubernetes + +Coming Soon! + +### Using systemd + +Coming Soon! + +### Compiling and Installing Envoy + +Envoy is written in the [Go programming language](https://pkg.go.dev/github.com/trisacrypto/envoy) and so can be compiled using the [go toolchain](https://go.dev/doc/tutorial/compile-install). If you have `go` installed on your computer, it may be possible for you to simply run: + +``` +$ go install github.com/trisacrypto/envoy/cmd/envoy@latest +``` + +To compile and install the latest version of `envoy` on your `$PATH`. Or if you prefer to install a specific version of Envoy: + +``` +$ go install github.com/trisacrypto/envoy/cmd/envoy@v0.11.0 +``` + +The complicating factor is that `CGO` is required to compile Envoy, which means you may have to set the `CGO_ENABLED=1` environment variable. Additionally you'll have to have either `clang` or `gcc` installed to compile the dependent packages for your architecture if they cannot be installed using go modules. + +### Building the Docker Image + +You can build the Envoy Docker image using the `Dockerfile` in the root of the [trisacrypto/envoy](https://github.com/trisacrypto/envoy) repository. After cloning the repository and changing into its root directory, run: + +``` +$ docker build -t [TAG] . +``` + +If you'd like to build the image for a different platform, you can use `docker buildx` as follows: + +``` +$ docker buildx build -t [TAG] --platform linux/amd64,linux/arm64 . +``` + +Feel free to push the resulting image to your container registry of choice; or just use the default Docker images on DockerHub! + +## Accessing Envoy + +Although you can create users and API keys using the user interface there is a bit of a chicken and egg problem: how do you create a user to log in to the user interface to create a user? Additionally, if you've disabled the UI, how do you create API keys for the internal API? Luckily the `envoy` command has some helper tools to do this on your behalf. + +### Running the Envoy Command + +To create users and API keys, wherever you run the `envoy` command will need to have the correct configuration to reach the database that backs Envoy. In practice, this typically means that you need to run the `envoy` command on the instance where you deployed it. For example, if you've deployed using Kubernetes you could run: + +``` +$ kubectl -n [NAMESPACE] exec -it [POD] -- envoy -h +``` + +Alternatively you will have to SSH into the instance you're running, and ensure that `envoy` is on your `$PATH` and that you have the correct permissions to execute it. + +### Creating Users + +Use the following command to create a user: + +``` +$ envoy createuser -n [NAME] -e [EMAIL] -r [ROLE] +``` + +This command will create the specified user and will print out the password that you can use to login to the user interface with. + +Role can be one of: + +- `admin`: has full access to the UI +- `compliance`: can perform compliance related actions but not create users/apikeys +- `observer`: only has read-only access to the Envoy node + +### Creating API Keys + +Use the following command to create an API key that has all permissions: + +``` +$ envoy createapikey all +``` + +Alternative, you can specify which permissions you want the API key to have by listing them each in a space delimited form: + +``` +$ envoy createapikey users:manage users:view +``` + +The list of the permissions you can add to an API key can be found in the [API guide permissions table]({{< relref "api.en.md#permissions" >}}). + diff --git a/docs/content/envoy/openapi.en.md b/docs/content/envoy/openapi.en.md new file mode 100644 index 0000000..7080c0a --- /dev/null +++ b/docs/content/envoy/openapi.en.md @@ -0,0 +1,10 @@ +--- +title: API Reference +date: 2024-05-08T12:14:45-05:00 +lastmod: 2024-05-08T12:14:45-05:00 +description: "OpenAPI 3.0 specification generated documentation" +weight: 91 +--- + +{{< openapi src="/openapi/envoy.json" >}} + diff --git a/docs/hugo.toml b/docs/hugo.toml index 341c2d4..cf8655e 100644 --- a/docs/hugo.toml +++ b/docs/hugo.toml @@ -53,6 +53,8 @@ enableMissingTranslationPlaceholders = false additionalContentLanguage = ['en'] + disableAssetsBusting = true + [languages] [languages.en] title = "TRISA Documentation" diff --git a/docs/static/openapi/envoy.json b/docs/static/openapi/envoy.json new file mode 100644 index 0000000..8a2a749 --- /dev/null +++ b/docs/static/openapi/envoy.json @@ -0,0 +1,1346 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "TRISA Node API", + "description": "The TRISA Node API allows users to interact with the TRISA Self-Hosted Node\nin a programmatic fashion. This API is a JSON REST API that does not have\nauthentication -- it is intended that this API is served on an internal\nIP address with routing only available to internal services.", + "termsOfService": "https://trisa.io/terms/", + "contact": { + "email": "support@rotational.io" + }, + "license": { + "name": "MIT Licensed", + "url": "https://github.com/trisacrypto/trisa/blob/main/LICENSE" + }, + "version": "v0.11.0" + }, + "externalDocs": { + "description": "TRISA Developer Documentation", + "url": "https://trisa.dev" + }, + "tags": [ + { + "name": "account", + "description": "Stored information about your user/customer accounts." + }, + { + "name": "crypto_address", + "description": "Associate crypto addresses with user accounts." + }, + { + "name": "transaction", + "description": "Travel Rule information exchanges for specific crypto asset transactions." + }, + { + "name": "counterparty", + "description": "Counterparties to exchange travel rule information with using TRISA or TRP protocols." + }, + { + "name": "secure_envelope", + "description": "Secure Envelopes provide an audit record of travel rule exchanges with a counterparty." + } + ], + "paths": { + "/v1/status": { + "get": { + "summary": "Heartbeat endpoint", + "description": "Allows users to check the status of the node", + "operationId": "status", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusReply" + } + } + } + }, + "503": { + "description": "Unavailable", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StatusReply" + } + } + } + } + } + } + }, + "/v1/transactions": { + "get": { + "tags": [ + "transaction" + ], + "summary": "List transactions", + "description": "Paginated list of all transactions", + "operationId": "listTransactions", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionsList" + } + } + } + } + } + }, + "post": { + "tags": [ + "transaction" + ], + "summary": "Create transaction", + "description": "Create a new transaction", + "operationId": "createTransaction", + "requestBody": { + "description": "Create a new transaction", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Transaction created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + } + }, + "/v1/transactions/{transactionID}": { + "get": { + "tags": [ + "transaction" + ], + "summary": "Find transaction by ID", + "description": "Returns a single transaction if found", + "operationId": "transactionDetail", + "parameters": [ + { + "name": "transactionID", + "in": "path", + "description": "ID of transaction to return", + "required": true, + "schema": { + "type": "string", + "format": "UUID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "404": { + "description": "Transaction not found" + } + } + }, + "put": { + "tags": [ + "transaction" + ], + "summary": "Updates a transaction record", + "description": "Update a transaction record (does not patch, all fields are required)", + "operationId": "updateTransaction", + "parameters": [ + { + "name": "transactionID", + "in": "path", + "description": "ID of transaction to return", + "required": true, + "schema": { + "type": "string", + "format": "UUID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + }, + "delete": { + "tags": [ + "transaction" + ], + "summary": "Deletes a transaction", + "description": "Deletes a transaction", + "operationId": "deleteTransaction", + "parameters": [ + { + "name": "transactionID", + "in": "path", + "description": "ID of transaction to return", + "required": true, + "schema": { + "type": "string", + "format": "UUID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reply" + } + } + } + }, + "404": { + "description": "Account not found" + } + } + } + }, + "/v1/transactions/{transactionID}/secure-envelopes": { + "get": { + "tags": [ + "secure_envelope" + ], + "summary": "List secure envelopes for transaction", + "description": "Paginated list of all secure envelopes for the specified transaction", + "operationId": "listSecureEnvelopes", + "parameters": [ + { + "name": "transactionID", + "in": "path", + "description": "ID of transaction to return secure envelopes for", + "required": true, + "schema": { + "type": "string", + "format": "UUID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EnvelopesList" + } + } + } + } + } + } + }, + "/v1/transactions/{transactionID}/secure-envelopes/{envelopeID}": { + "get": { + "tags": [ + "secure_envelope" + ], + "summary": "Lookup a specific secure envelope", + "description": "Returns detailed information about the specified secure envelope", + "operationId": "envelopeDetail", + "parameters": [ + { + "name": "transactionID", + "in": "path", + "description": "ID of transaction to return secure envelopes for", + "required": true, + "schema": { + "type": "string", + "format": "UUID" + } + }, + { + "name": "envelopeID", + "in": "path", + "description": "ID of secure envelope to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SecureEnvelope" + } + } + } + }, + "404": { + "description": "Account or crypto address not found" + } + } + } + }, + "/v1/accounts": { + "get": { + "tags": [ + "account" + ], + "summary": "List customer accounts", + "description": "Paginated list of all stored customer accounts", + "operationId": "listAccounts", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AccountList" + } + } + } + } + } + }, + "post": { + "tags": [ + "account" + ], + "summary": "Create customer account", + "description": "Create a new customer account", + "operationId": "createAccount", + "requestBody": { + "description": "Create a new customer account", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Account created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + } + }, + "/v1/account/{accountID}": { + "get": { + "tags": [ + "account" + ], + "summary": "Find account by ID", + "description": "Returns a single account if found", + "operationId": "accountDetail", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "404": { + "description": "Account not found" + } + } + }, + "put": { + "tags": [ + "account" + ], + "summary": "Updates an account record", + "description": "Update an account record (does not patch, all fields are required)", + "operationId": "updateAccount", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + }, + "delete": { + "tags": [ + "account" + ], + "summary": "Deletes an account", + "description": "Deletes an account and associated crypto addresses", + "operationId": "deleteAccount", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reply" + } + } + } + }, + "404": { + "description": "Account not found" + } + } + } + }, + "/v1/accounts/{accountID}/crypto-addresses": { + "get": { + "tags": [ + "crypto_address" + ], + "summary": "list all crypto addresses for account", + "description": "returns a paginated list of all crypto addresses associated with the account", + "operationId": "listCryptoAddresses", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddressList" + } + } + } + }, + "404": { + "description": "Account not found" + } + } + }, + "post": { + "tags": [ + "crypto_address" + ], + "summary": "Create crypto address", + "description": "Create a crypto address associated with the specified account", + "operationId": "createCryptoAddress", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "responses": { + "201": { + "description": "Crypto address created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "404": { + "description": "Account not found" + }, + "422": { + "description": "Validation exception or missing field" + } + } + } + }, + "/v1/accounts/{accountID}/crypto-addresses/{cryptoAddressID}": { + "get": { + "tags": [ + "crypto_address" + ], + "summary": "Lookup a specific crypto address", + "description": "Returns detailed information about the specified crypto address", + "operationId": "cryptoAddressesDetail", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + }, + { + "name": "cryptoAddressID", + "in": "path", + "description": "ID of crypto address to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "404": { + "description": "Account or crypto address not found" + } + } + }, + "put": { + "tags": [ + "crypto_address" + ], + "summary": "Update a crypto address", + "description": "Update a crypto address record (does not patch, all fields are required)", + "operationId": "updateCryptoAddresses", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + }, + { + "name": "cryptoAddressID", + "in": "path", + "description": "ID of crypto address to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "404": { + "description": "Account or crypto address not found" + }, + "422": { + "description": "Validation exception or missing field" + } + } + }, + "delete": { + "tags": [ + "crypto_address" + ], + "summary": "Delete a crypto address", + "description": "Delete a crypto address record associated with account", + "operationId": "deleteCryptoAddresses", + "parameters": [ + { + "name": "accountID", + "in": "path", + "description": "ID of account to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + }, + { + "name": "cryptoAddressID", + "in": "path", + "description": "ID of crypto address to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reply" + } + } + } + }, + "404": { + "description": "Account or crypto address not found" + } + } + } + }, + "/v1/counterparties": { + "get": { + "tags": [ + "counterparty" + ], + "summary": "List counterparties", + "description": "Paginated list of all stored counterparties", + "operationId": "listCounterparties", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CounterpartyList" + } + } + } + } + } + }, + "post": { + "tags": [ + "counterparty" + ], + "summary": "Create counterparty", + "description": "Create a new counterparty", + "operationId": "createCounterparty", + "requestBody": { + "description": "Create a new counterparty", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Counterparty" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Counterparty created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Counterparty" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + } + }, + "/v1/counterparties/{counterpartyID}": { + "get": { + "tags": [ + "counterparty" + ], + "summary": "Find counterparty by ID", + "description": "Returns a single counterparty if found", + "operationId": "counterpartyDetail", + "parameters": [ + { + "name": "counterpartyID", + "in": "path", + "description": "ID of counterparty to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Counterparty" + } + } + } + }, + "404": { + "description": "Counterparty not found" + } + } + }, + "put": { + "tags": [ + "counterparty" + ], + "summary": "Updates a counterparty record", + "description": "Update a counterparty record (does not patch, all fields are required)", + "operationId": "updateCounterparty", + "parameters": [ + { + "name": "counterpartyID", + "in": "path", + "description": "ID of counterparty to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Counterparty" + } + } + } + }, + "400": { + "description": "Invalid input" + }, + "422": { + "description": "Validation exception or missing field" + } + } + }, + "delete": { + "tags": [ + "counterparty" + ], + "summary": "Deletes a counterparty", + "description": "Deletes a counterparty", + "operationId": "deleteCounterparty", + "parameters": [ + { + "name": "counterpartyID", + "in": "path", + "description": "ID of counterparty to return", + "required": true, + "schema": { + "type": "string", + "format": "ULID" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Reply" + } + } + } + }, + "404": { + "description": "Account not found" + } + } + } + } + }, + "components": { + "schemas": { + "Transaction": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "UUID", + "example": "c44d81ec-7c2d-4372-8844-28fdb6b1c664" + } + } + }, + "TransactionsList": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PageQuery" + }, + "transactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + "SecureEnvelope": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "ULID" + }, + "direction": { + "type": "string", + "enum": [ + "incoming", + "outgoing" + ] + }, + "envelope_id": { + "type": "string", + "format": "UUID" + }, + "payload": { + "type": "string", + "format": "base64" + }, + "encryption_key": { + "type": "string", + "format": "base64" + }, + "encryption_algorithm": { + "type": "string", + "example": "AES-GCM-256" + }, + "valid_hmac": { + "type": "boolean" + }, + "hmac": { + "type": "string", + "format": "base64" + }, + "hmac_secret": { + "type": "string", + "format": "base64" + }, + "hmac_algorithm": { + "type": "string", + "example": "HMAC-SHA-256" + }, + "is_error": { + "type": "boolean" + }, + "error": { + "type": "object" + }, + "timestamp": { + "type": "string", + "format": "datetime" + }, + "sealed": { + "type": "boolean" + }, + "public_key_signature": { + "type": "string" + }, + "original": { + "type": "string", + "format": "base64" + } + } + }, + "DecryptedEnvelope": { + "type": "object", + "properties": { + "identity": { + "type": "object" + }, + "transaction": { + "type": "object" + }, + "pending": { + "type": "object" + }, + "sent_at": { + "type": "string", + "format": "datetime" + }, + "received_at": { + "type": "string", + "format": "datetime" + } + } + }, + "EnvelopeQuery": { + "type": "object", + "properties": { + "decrypt": { + "type": "boolean", + "default": false + }, + "archives": { + "type": "boolean", + "default": false + } + } + }, + "EnvelopesList": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PageQuery" + }, + "is_decrypted": { + "type": "boolean" + }, + "secure_envelopes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SecureEnvelope" + } + }, + "decrypted_envelopes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DecryptedEnvelope" + } + } + } + }, + "Counterparty": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "ULID", + "example": "01HV1GBK4JG9KF8RHJG2KF0B9M" + }, + "source": { + "type": "string", + "readOnly": true, + "enum": [ + "user", + "gds" + ] + }, + "directory_id": { + "type": "string", + "format": "UUID", + "example": "af0e1286-a2a1-40b1-989a-dd5b75fdfbf3", + "readOnly": true + }, + "registered_directory": { + "type": "string", + "example": "trisatest.net", + "readOnly": true + }, + "protocol": { + "type": "string", + "example": "trisa", + "enum": [ + "trisa", + "trp" + ] + }, + "common_name": { + "type": "string", + "format": "hostname", + "example": "trisa.example.com" + }, + "endpoint": { + "type": "string", + "format": "uri", + "example": "trisa.example.com:443" + }, + "name": { + "type": "string", + "example": "AliceVASP" + }, + "website": { + "type": "string", + "format": "uri" + }, + "country": { + "type": "string", + "format": "ISO-3166-1 ALPHA-2" + }, + "business_category": { + "type": "string" + }, + "vasp_categories": { + "type": "array", + "items": { + "type": "string" + } + }, + "verified_on": { + "type": "string", + "format": "datetime" + }, + "ivms101": { + "type": "object" + }, + "created": { + "type": "string", + "format": "datetime", + "readOnly": true + }, + "modified": { + "type": "string", + "format": "datetime", + "readOnly": true + } + } + }, + "CounterpartyList": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PageQuery" + }, + "counterparties": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Counterparty" + } + } + } + }, + "CryptoAddress": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "ULID", + "example": "01HSNQWH856KKB2PFXEF4XJQWY" + }, + "crypto_address": { + "type": "string", + "example": "18dlice0aYQesEaFs3XuLHBXGoCyhjjHin" + }, + "network": { + "type": "string", + "format": "SLIP-0044", + "example": "BTC", + "externalDocs": { + "description": "SLIP-0044 Definitions", + "url": "https://github.com/satoshilabs/slips/blob/master/slip-0044.md" + } + }, + "asset_type": { + "type": "string", + "description": "crypto asset type for multi-asset chains" + }, + "tag": { + "type": "string", + "description": "tag or memo for chains that require one" + }, + "created": { + "type": "string", + "format": "datetime" + }, + "modified": { + "type": "string", + "format": "datetime" + } + } + }, + "CryptoAddressList": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PageQuery" + }, + "crypto_addresses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + }, + "Account": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "ULID", + "example": "01HSNQWH856KKB2PFXEF4XJQWY", + "description": "node-specific identifier" + }, + "customer_id": { + "type": "string", + "description": "VASP identifier for customer account" + }, + "first_name": { + "type": "string", + "example": "Jane", + "description": "Given or first name of customer" + }, + "last_name": { + "type": "string", + "example": "Doe", + "description": "Family or last name of customer" + }, + "crypto_addresses": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CryptoAddress" + } + }, + "created": { + "type": "string", + "format": "datetime" + }, + "modified": { + "type": "string", + "format": "datetime" + } + } + }, + "AccountList": { + "type": "object", + "properties": { + "page": { + "$ref": "#/components/schemas/PageQuery" + }, + "accounts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "Reply": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "error": { + "type": "string", + "example": "could not complete requested operation" + }, + "version": { + "type": "string", + "format": "semver", + "example": "1.0.0" + } + } + }, + "StatusReply": { + "type": "object", + "properties": { + "status": { + "type": "string", + "example": "ok", + "enum": [ + "ok", + "not ready", + "unhealthy", + "maintenance" + ] + }, + "uptime": { + "type": "string", + "format": "duration", + "example": "25h33m29.4860s" + }, + "version": { + "type": "string", + "format": "semver", + "example": "1.0.0" + } + } + }, + "PageQuery": { + "type": "object", + "properties": { + "page_size": { + "type": "integer", + "format": "int32", + "example": 50, + "default": 100 + }, + "next_page_token": { + "type": "string", + "format": "base64" + }, + "prev_page_token": { + "type": "string", + "format": "base64" + } + } + } + }, + "requestBodies": { + "Account": { + "description": "Account that needs to be added or updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Account" + } + } + } + }, + "CryptoAddress": { + "description": "CryptoAddress associated with account that needs to be added or updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CryptoAddress" + } + } + } + } + }, + "securitySchemes": { + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} \ No newline at end of file