Below is a collection of thoughts and feedback from working with the Blockstack API while building the Portal and macOS app.
It is divided into sections that aren't in any particular order.
Overall, I think the API is pretty straightforward and intuitive to work with. Kudos to @jude @muneeb and the team for all of their great work up to this point.
Use error codes/types
Return a human & computer readable error code for each request.
For example instead of:
"error": "Failed to transfer balance: Invalid amount"
We could have something like this:
"message": "Failed to transfer balance because of invalid amount."
"message": "Provided value is not in a known Bitcoin address format."
"code": (required) is a string constant. Clients of the the API can use this code to determine how they want to handle the error. Often this will be used to look up an error message in the user's language to display to the user.
"param": (optional) is the name of the parameter that has a problem. This can be used by clients of the API to display error messages next to the appropriate user input field.
"message": (required) is a human readable error message. This is intended to provide clients of the API with additional information as to why their request failed.
The intention would be that this
message: field provides helpful information in explaining the problem for developers. It can be displayed to end users in apps that are MVPs and/or okay with English-only error messages targeted at technical users.
For example, in a app targeted towards users familiar with the Bitcoin protocol it might be fine to display the message:
"Cannot withdraw dust."
For a general consumer app targeted towards a global audience the developer might want to display that message in way more friendly for laymen or localized to a particular language such as:
en_US -> "There's not enough money in your wallet"
zh_CN -> "你钱包的余额不足"
It's preferable to return all of the errors that the API knows about at once so that the app can prompt the user to fix all of their input problems at once.
HTTP Status Codes
Consistently use HTTP error codes. The API should return 40x error codes when there's something wrong with information provided by the user. It should return 50x errors only when there’s an unexpected problem with Core. If a user encounters a 50x error, their Core API node is either configured incorrectly or there's a bug in Core.
For example, the API shouldn’t return 50x errors if there’s not enough money in wallet. This isn’t a problem with Core, it’s a problem with the user’s request.
Example: Stripe's error codes
The Blockstack Core API currently uses a "coarse" versioning system where version 1 is placed in the sub-directory
/v1. The thinking is that once the API is stable, we will commit to supporting
One of the biggest frustrations as the user of an API is refactoring your code for changes to the API. Along the same lines, one of the biggest frustrations for the developer of the API is being unable to change it for fear of breaking backward compatibility.
We are building in a completely new space. It is pretty safe to say that the first version of our API won't be the most ideal. As our project grows and the ecosystem grows, we'll see many ways for the API to improve. It would be sad to be unable to improve the API because we're worried that our vast decentralized ecosystem of apps can't easily be updated.
Another approach would be to consider using a more finely grained versioning system uses for our API. Allow apps to request the version of the API for which they were designed. We could use compatibility layers to translate the request from the version of the API the app specifies to the latest API code. We could then transform the latest version of the API response back to the response format of the version of the API the user requested. This would allow us to continually improve the API without breaking backward compatibility for existing apps. There's a great write up on how Stripe does something similar that is well worth reading.
Core API currently expects the API password to be passed in requests with the
basic type as follows:
Authorization: basic <api-password>
basic authentication is a specific type of authentication that requires a base64-encoded username and password combination .
APIs typically use the
bearer type in the
Authorization header to pass a single token secret. An API password (or key or oauth2 token) is passed on each request as follows:
Authorization: bearer <token> . See Github's API authentication section for some examples.
We need to be able to install and run Core API on machines that don't have developer tools or a Python toolchain installed.
In the Blockstack macOS app, we are currently building a virtualenv but are unable to relocate it because of native dependencies and the inability to run Core in directories that contain spaces. To work around these issues, we build our Core virtualenv in
/tmp/blockstack-venv and deploy in the same location on every machine.
Besides posing a security problem this also makes it impossible to support multiple users. (See this thread for more discussion about multi-user issues).
It would be nice to be able to start the API without having to first run the
blockstack setup step. It seems like this is something that the API could do automatically for us at start up.
Users should be able to see how their Core node is configured and modify that configuration via the API.
DRY up config
We use UTXO providers in both Core and Portal. We should consider exposing Core's UTXO provider through the API so that Portal can use it. This way we can only one UTXO setting.
Working with wallets
When running Core as part of the macOS app, on-boarding of the user happens through the Portal web app, after we've already started the Core API. It is during that on-boarding process that users select the password for the wallet built-in to the Portal that currently only holds their identities.
We randomly generate a wallet password for the Core wallet prior to on-boarding the user since on-boarding occurs after the Core API is started.
This makes it difficult for the user to protect their funds. They don't know the password nor can they easily back up the wallet.
Even if they did, this would mean they have two wallets: an identity wallet and a bitcoin wallet - and would have to back up and restore each separately. From a UX perspective, it makes more sense for the user to only have 1 wallet: 1 password to remember and one backup phrase to keep safe.
I hope we can agree going forward to use either the Core wallet or the Portal wallet for both identity and bitcoin storage.
Using Core wallet via the API
If we go forward with using the Core wallet, it help to have an API that lets users create, delete and otherwise work with wallets. Methods that require the user's password should require it as a parameter.
Using Portal wallet with the API
If we go forward with using the Portal wallet for both identity and money, we would need methods that need to generate Blockstack transactions to generate and return Bitcoin transactions that can be signed by the Portal wallet.
A built in easy to use test mode (ie. not real bitcoin) mode would a HUGE UX improvement for developers. Configuring test environments is a pain point for developers. It isn't reasonable to expect a developer looking to write an app that uses our API to install a local
bitcoind node running on testnet and run our integration test framework.
To see how much of a barrier this is, we need only look at our own Portal team. @guylepage3 , @ryan and I develop the Portal on the live network. This limits the amount of real testing we can do because of cost and time limitations.
Perhaps we could provide a test mode public fleet of testnet
bitcoind nodes and build in an easily enabled test mode into Core. Something that we could start with a point and click from the Blockstack for [macOS, Windows, Linux] apps.
Stripe's Test/Live mode is (again) a best of class example of a developer friendly test mode - developers can switch between test and live mode of the API by switching API keys.
It's also important provide test input values that let developers replicate the full range of success and error states of the API. In our case, we might want to populate our test mode with a series of test profiles, etc. Again, see Stripe's test input data.
Developers LOVE friendly APIs. (Which is why I keep citing Stripe's API)
Building a product is hard. An API that is a pleasure to work with and makes your life easier lets you focus your energy on solving problems for your customers and building an amazing product.
I think we're well on our way to an API that developers will fall in love with.
I'd love to hear your thoughts both on what I've wrote and things you think I've missed.