<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Alex Barron]]></title><description><![CDATA[API Solutions Engineer & Technical Writer]]></description><link>https://abarron.com/</link><image><url>https://abarron.com/favicon.png</url><title>Alex Barron</title><link>https://abarron.com/</link></image><generator>Ghost 5.76</generator><lastBuildDate>Fri, 03 Apr 2026 05:39:47 GMT</lastBuildDate><atom:link href="https://abarron.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Creating an OpenAPI Mock Server with Prism]]></title><description><![CDATA[<p>Prism is a free, open source tool created by <a href="https://stoplight.io/?ref=abarron.com">Stoplight</a> that enables developers to quickly create mock API servers using only an OpenAPI document. Prism parses your OpenAPI spec to understand your endpoints, schemas, and examples to spin up a local server you can make API calls to using whatever</p>]]></description><link>https://abarron.com/how-to-create-an-openapi-mock-server-with-prism/</link><guid isPermaLink="false">63d7cd7056f43f07294f2382</guid><category><![CDATA[OpenAPI]]></category><category><![CDATA[Guides]]></category><category><![CDATA[Open Source]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Tue, 30 Jan 2024 09:18:50 GMT</pubDate><content:encoded><![CDATA[<p>Prism is a free, open source tool created by <a href="https://stoplight.io/?ref=abarron.com">Stoplight</a> that enables developers to quickly create mock API servers using only an OpenAPI document. Prism parses your OpenAPI spec to understand your endpoints, schemas, and examples to spin up a local server you can make API calls to using whatever client you want.</p><p>This is invaluable because it allows developers to test the API experience before they write any real code. With a mock server, developers can spot potential issues early in the API design process and quickly iterate by modifying a YAML or JSON document instead of having to make expensive code changes.</p><p>Here&apos;s how to get Prism set up on your machine.</p><p>First, we install Prism using <code>npm</code> or <code>yarn</code>.</p><pre><code class="language-bash">npm install -g @stoplight/prism-cli

# OR

yarn global add @stoplight/prism-cli</code></pre><p>Next, we&apos;ll need an OpenAPI document. Use one you have already, get a copy of the standard sample <a href="https://github.com/OAI/OpenAPI-Specification/blob/main/examples/v3.0/petstore.yaml?ref=abarron.com">OpenAPI document of a Petstore API</a>, or use the one I created in <a href="https://abarron.com/creating-an-openapi-specification-the-hard-way/" rel="noreferrer">Creating an OpenAPI Specification the Hard Way</a>.</p><p>Now it&apos;s as easy as running Prism and referencing your OpenAPI file.</p><pre><code class="language-bash">prism mock my_open_api_doc.yaml</code></pre><p>If all goes well, your output should be a list of all your endpoints with local server paths.</p><pre><code class="language-bash">[2:59:43&#x202F;PM] &#x203A; [CLI] &#x2026;  awaiting  Starting Prism&#x2026;
[2:59:43&#x202F;PM] &#x203A; [CLI] &#x2139;  info      GET        http://127.0.0.1:4010/definitions/tortellini
[2:59:43&#x202F;PM] &#x203A; [CLI] &#x2139;  info      PUT        http://127.0.0.1:4010/definitions/tortellini
[2:59:43&#x202F;PM] &#x203A; [CLI] &#x2139;  info      DELETE     http://127.0.0.1:4010/definitions/tortellini
[2:59:43&#x202F;PM] &#x203A; [CLI] &#x2139;  info      POST       http://127.0.0.1:4010/definitions
[2:59:43&#x202F;PM] &#x203A; [CLI] &#x25B6;  start     Prism is listening on http://127.0.0.1:4010
</code></pre><p>If the command fails, it&apos;s likely due to minor syntax issues in your document. Use a YAML or JSON validator to check for those. Or check out one of these <a href="https://nordicapis.com/8-openapi-linters/?ref=abarron.com" rel="noreferrer">OpenAPI linters</a>.</p><p>Assuming everything is working, let&apos;s do a simple GET to our local server at <code>http://127.0.0.1:4010</code>.</p><pre><code class="language-bash">curl -i http://127.0.0.1:4010/definitions/tagliatelle</code></pre><p>Ideally, we get a response like:</p><pre><code class="language-bash">curl -i http://127.0.0.1:4010/definitions/tagliatelle
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 225
Date: Mon, 29 Jan 2024 14:18:59 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{&quot;id&quot;:40125,&quot;word&quot;:&quot;tagliatelle&quot;,&quot;created_at&quot;:&quot;2023-12-06T04:55:36.887Z&quot;,&quot;updated_at&quot;:&quot;2022-12-06T04:55:36.887Z&quot;,&quot;meanings&quot;:[{&quot;id&quot;:333,&quot;part_of_speech&quot;:&quot;noun&quot;,&quot;meaning&quot;:&quot;A type of pasta shaped into long, thin, flat strips&quot;}]}</code></pre><p>The response body is constructed by whatever we put in the responses child object of any API path. In this case, our GET endpoint references the <code>definitions</code> schema object which includes the above sample data.</p><p>Your mock server will also give you server logs notifying you of any validation issues. Here&apos;s what a good GET event should look like:</p><pre><code class="language-bash">[3:18:59&#x202F;PM] &#x203A; [HTTP SERVER] get /definitions/tortellini &#x2139;  info      Request received
[3:18:59&#x202F;PM] &#x203A;     [NEGOTIATOR] &#x2139;  info      Request contains an accept header: */*
[3:18:59&#x202F;PM] &#x203A;     [VALIDATOR] &#x2714;  success   The request passed the validation rules. Looking for the best response
[3:18:59&#x202F;PM] &#x203A;     [NEGOTIATOR] &#x2714;  success   Found a compatible content for */*
[3:18:59&#x202F;PM] &#x203A;     [NEGOTIATOR] &#x2714;  success   Responding with the requested status code 200
</code></pre><p>And if we try a route like <code>/v2/definitions/tagliatelle</code> that doesn&apos;t exist in our OpenAPI spec, we see Prism handles that as expected with a <code>404 Not Found</code> response.</p><pre><code class="language-bash">curl -i http://127.0.0.1:4010/v2/definitions/tagliatelle
HTTP/1.1 404 Not Found
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
content-type: application/problem+json
Content-Length: 218
Date: Tue, 30 Jan 2024 09:01:27 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{&quot;type&quot;:&quot;https://stoplight.io/prism/errors#NO_PATH_MATCHED_ERROR&quot;,&quot;title&quot;:&quot;Route not resolved, no path matched&quot;,&quot;status&quot;:404,&quot;detail&quot;:&quot;The route /v2/definitions/tagliatelle hasn&apos;t been found in the specification file&quot;}</code></pre><p>We can also test a POST request protected with Basic Auth. If you&apos;ve protected your API with Basic Auth, Prism will expect to see a Basic Auth header included in the request. Of course it can&apos;t actually validate the token, but it&apos;s helpful enough in the early development phase of an API</p><pre><code class="language-bash">curl -i --request POST \
  --url http://127.0.0.1:4010/definitions \
  --header &apos;Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=&apos; \
  --header &apos;Content-Type: application/json&apos; \
  --data &apos;{
  &quot;word&quot;: &quot;tagliatelle&quot;,
  &quot;meanings&quot;: [
    {
      &quot;part_of_speech&quot;: &quot;noun&quot;,
      &quot;meaning&quot;: &quot;A type of pasta shaped into long, thin, flat strips&quot;
    }
  ]
}&apos;
HTTP/1.1 201 Created
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-type: application/json
Content-Length: 225
Date: Mon, 29 Jan 2024 14:36:02 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{&quot;id&quot;:40125,&quot;word&quot;:&quot;tagliatelle&quot;,&quot;created_at&quot;:&quot;2022-12-06T04:55:36.887Z&quot;,&quot;updated_at&quot;:&quot;2022-12-06T04:55:36.887Z&quot;,&quot;meanings&quot;:[{&quot;id&quot;:333,&quot;part_of_speech&quot;:&quot;noun&quot;,&quot;meaning&quot;:&quot;A type of pasta shaped into long, thin, flat strips&quot;}]}</code></pre><p>Prism will also handle the various erroneous requests we might want to test. You can try removing the Basic Auth header, and you&apos;ll get a <code>401 Unauthorized.</code></p><pre><code class="language-bash">curl -i --request POST \
  --url http://127.0.0.1:4010/definitions \
  --header &apos;Content-Type: application/json&apos; \
  --data &apos;{
  &quot;word&quot;: &quot;tagliatelle&quot;,
  &quot;meanings&quot;: [
    {
      &quot;part_of_speech&quot;: &quot;noun&quot;,
      &quot;meaning&quot;: &quot;A type of pasta shaped into long, thin, flat strips&quot;
    }
  ]
}&apos;
HTTP/1.1 401 Unauthorized
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
sl-violations: [{&quot;location&quot;:[&quot;request&quot;],&quot;severity&quot;:&quot;Error&quot;,&quot;code&quot;:401,&quot;message&quot;:&quot;Invalid security scheme used&quot;}]
Date: Mon, 29 Jan 2024 14:40:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 0
</code></pre><p>Changing the <code>Content-Type</code> header to <code>application/xml</code> when our API doesn&apos;t support XML returns a <code>422 Unprocessable Entity</code>.</p><pre><code class="language-bash">curl -i --request POST \
  --url http://127.0.0.1:4010/definitions \
  --header &apos;Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=&apos; \
  --header &apos;Content-Type: application/xml&apos; \
  --data &apos;{
  &quot;word&quot;: &quot;tagliatelle&quot;,
  &quot;meanings&quot;: [
    {
      &quot;part_of_speech&quot;: &quot;noun&quot;,
      &quot;meaning&quot;: &quot;A type of pasta shaped into long, thin, flat strips&quot;
    }
  ]
}&apos;
HTTP/1.1 422 Unprocessable Entity
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
sl-violations: [{&quot;location&quot;:[&quot;request&quot;],&quot;severity&quot;:&quot;Error&quot;,&quot;code&quot;:415,&quot;message&quot;:&quot;Supported content types: application/json&quot;}]
Date: Mon, 29 Jan 2024 14:41:51 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 0
</code></pre><p>Adding a <code>pronunciation</code> field to our request body that doesn&apos;t exist in our schema returns the expected <code>400 Bad Request</code> error.</p><pre><code class="language-bash">curl -i --request POST \
  --url http://127.0.0.1:4010/definitions \
  --header &apos;Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=&apos; \
  --header &apos;Content-Type: application/json&apos; \
  --data &apos;{
  &quot;pronunciation: &quot;ta&#x28E;&#x28E;a&#x2C8;t&#x25B;lle&quot;,
  &quot;word&quot;: &quot;tagliatelle&quot;,
  &quot;meanings&quot;: [
    {
      &quot;part_of_speech&quot;: &quot;noun&quot;,
      &quot;meaning&quot;: &quot;A type of pasta shaped into long, thin, flat strips&quot;
    }
  ]
}&apos;
HTTP/1.1 400 Bad Request
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Content-Type: application/json; charset=utf-8
Content-Length: 58
Date: Mon, 29 Jan 2024 14:42:42 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{&quot;error&quot;:{&quot;code&quot;:&quot;invalid_json&quot;,&quot;message&quot;:&quot;Invalid JSON&quot;}}</code></pre><p>A Prism mock server is no replacement for a fully working prototype, but it is an invaluable tool for the early design phase of an API as stakeholders are negotiating over how the API should function. It&apos;s certainly worth a whirl.</p>]]></content:encoded></item><item><title><![CDATA[Making an AI Search Results Analyzer with SerpApi, ChatGPT, & Ruby on Rails]]></title><description><![CDATA[<p>I recently discovered <a href="https://serpapi.com/?ref=abarron.com">SerpApi</a> and thought it would be fun to build a simple application using search results pulled from SerpApi. If you don&apos;t know about SerpApi, they&apos;re experts in scraping practically every search engine in existence and packaging the results in an easy to use</p>]]></description><link>https://abarron.com/making-an-ai-search-results-analyzer-with-serpapi-and-chatgpt/</link><guid isPermaLink="false">65ae645c56f43f07294f29cd</guid><category><![CDATA[SerpApi]]></category><category><![CDATA[OpenAI]]></category><category><![CDATA[Guides]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Wed, 24 Jan 2024 14:14:05 GMT</pubDate><content:encoded><![CDATA[<p>I recently discovered <a href="https://serpapi.com/?ref=abarron.com">SerpApi</a> and thought it would be fun to build a simple application using search results pulled from SerpApi. If you don&apos;t know about SerpApi, they&apos;re experts in scraping practically every search engine in existence and packaging the results in an easy to use API. </p><p>This gives businesses a powerful tool for monitoring search results that can help optimize their SEO, track news, or train AI models. Ultimately, the only limitation is one&apos;s own creativity.</p><p>My idea was to wire up SerpApi with OpenAI to get ChatGPT to analyze search results based on user-generated prompts. Here&apos;s how I built it.</p><h2 id="initial-setup">Initial Setup</h2><pre><code class="language-bash">rails new search-results-ai-analyzer
cd search-results-ai-analyzer
rails db:migrate</code></pre><p>I&apos;m also going to install Tailwind CSS to make it a slightly easier on the eyes. I suggest following <a href="https://tailwindcss.com/docs/guides/ruby-on-rails?ref=abarron.com">the installation steps on the Tailwind website</a>.</p><p>Check that everything is working by firing up the server. With Tailwind installed, we start the server by running <code>bin/dev</code>.</p><p>Next, we&apos;ll create a <code>SearchAnalysis</code> model using Rails&apos; scaffold generator to save us some time. The model needs the following fields:</p><ul>
<li>query: what we want to search</li>
<li>engine: enum of which search engine to use</li>
<li>prompt: what we want to ask ChatGPT to do</li>
<li>search_results: the results from SerpApi</li>
<li>chat_response: the response from ChatGPT</li>
<li>status: enum of where we are in the process (e.g. pending, failed, complete)</li>
</ul>
<p>Run the scaffold generator and migrate the database with the commands below.</p><pre><code class="language-bash">rails g scaffold SearchAnalysis query:text engine:integer prompt:text search_results:text chat_response:text status:integer</code></pre><p>The <code>status</code> field should have a default value of <code>0</code>. This must be manually set in the migration file before running the migration. <code>0</code> will be defined as <code>pending_search</code> in the model file.</p><pre><code class="language-ruby"># db/migrate/20240122133152_create_search_analyses.rb

class CreateSearchAnalyses &lt; ActiveRecord::Migration[7.0]
  def change
    create_table :search_analyses do |t|
      t.text :query
      t.integer :engine
      t.text :prompt
      t.text :search_results
      t.text :chat_response
      t.integer :status, default: 0

      t.timestamps
    end
  end
end</code></pre><p>Now we run our migration.</p><pre><code class="language-bash">rails db:migrate</code></pre><p>Add our enum values and some very basic validations for user submitted data.</p><pre><code class="language-ruby"># app/models/search_analysis.rb

class SearchAnalysis &lt; ApplicationRecord
  enum :engine, { google: 0, bing: 1, duckduckgo: 2, yahoo: 3, yandex: 4, baidu: 5 }
  enum :status, { pending_search: 0, pending_chat: 1, complete: 2, failed: 3 }

  validates :query, presence: true
  validates :engine, presence: true
  validates :prompt, presence: true
end</code></pre><p>Change the root path to map to <code>search_analyses#index</code> in your <code>config/routes.rb</code> file.</p><pre><code class="language-ruby"># config/routes.rb

Rails.application.routes.draw do
  resources :search_analyses

  root &quot;search_analyses#index&quot;
end</code></pre><p>We don&apos;t want our forms to ever accept user submitted data for the <code>status</code>, <code>search_results</code> and <code>chat_response</code> fields so we&apos;ll remove those from our strong params method.</p><pre><code class="language-ruby"># app/controllers/search_analyses_controller.rb

    # Only allow a list of trusted parameters through.
    def search_analysis_params
      params.require(:search_analysis).permit(:query, :engine, :prompt)
    end</code></pre><p>We should also remove those fields from our form so the user doesn&apos;t even see them. </p><p>While we&apos;re editing our form, let&apos;s also improve the field labels to be more clear and also have the search engine field use a select dropdown.</p><p>Your form should look like this:</p><pre><code class="language-ruby"># app/views/search_analyses/_form.html.erb

&lt;%= form_with(model: search_analysis, class: &quot;contents&quot;) do |form| %&gt;
  &lt;% if search_analysis.errors.any? %&gt;
    &lt;div id=&quot;error_explanation&quot; class=&quot;bg-red-50 text-red-500 px-3 py-2 font-medium rounded-lg mt-3&quot;&gt;
      &lt;h2&gt;&lt;%= pluralize(search_analysis.errors.count, &quot;error&quot;) %&gt; prohibited this search_analysis from being saved:&lt;/h2&gt;

      &lt;ul&gt;
        &lt;% search_analysis.errors.each do |error| %&gt;
          &lt;li&gt;&lt;%= error.full_message %&gt;&lt;/li&gt;
        &lt;% end %&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;% end %&gt;

  &lt;div class=&quot;my-5&quot;&gt;
    &lt;%= form.label :query, &quot;Search Query&quot; %&gt;
    &lt;%= form.text_field :query, rows: 4, class: &quot;block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full&quot; %&gt;
  &lt;/div&gt;

  &lt;div class=&quot;my-5&quot;&gt;
      &lt;%= form.label :engine, &quot;Search Engine&quot; %&gt;
      &lt;%= form.select(:engine, [&apos;google&apos;, &apos;bing&apos;, &apos;duckduckgo&apos;, &apos;yahoo&apos;, &apos;yandex&apos;, &apos;baidu&apos;], { :class =&gt; &quot;block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full&quot; }) %&gt;
  &lt;/div&gt;

  &lt;div class=&quot;my-5&quot;&gt;
    &lt;%= form.label :prompt, &quot;Chat Prompt&quot; %&gt;
    &lt;%= form.text_area :prompt, rows: 3, class: &quot;block shadow rounded-md border border-gray-200 outline-none px-3 py-2 mt-2 w-full&quot; %&gt;
  &lt;/div&gt;

  &lt;div class=&quot;inline&quot;&gt;
    &lt;%= form.submit class: &quot;rounded-lg py-3 px-5 bg-blue-600 text-white inline-block font-medium cursor-pointer&quot; %&gt;
  &lt;/div&gt;
&lt;% end %&gt;</code></pre><p>As an optional but recommended step, you can remove the following things the scaffold generated that we don&apos;t need.</p><ol><li>The edit and update actions in the SearchAnalyses controller.</li><li>The entire edit view file <code>views/search_analyses/edit.html.erb</code></li><li>Remove the &quot;Edit this search analysis&quot; buttons from <code>views/search_analyses/_search_analysis.html.erb</code> and <code>views/search_analyses/show.html.erb</code></li></ol><p>Give your form a quick test before moving on. </p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2024/01/image-1.png" class="kg-image" alt loading="lazy" width="809" height="455" srcset="https://abarron.com/content/images/size/w600/2024/01/image-1.png 600w, https://abarron.com/content/images/2024/01/image-1.png 809w" sizes="(min-width: 720px) 720px"></figure><h2 id="connecting-to-openaichatgpt">Connecting to OpenAI/ChatGPT</h2><p>Now we&apos;ll wire up our app to talk to OpenAI&apos;s API. If you don&apos;t have an OpenAI API account yet, <a href="https://openai.com/?ref=abarron.com">create one here first</a> and then find <a href="https://platform.openai.com/api-keys?ref=abarron.com">the API Keys page</a>. Create a key and store it somewhere safe. OpenAI will only show you the key once. </p><p>Hopefully OpenAI will give you some free credit to play around with the API. If not, you&apos;ll need to add at least $5 credit on <a href="https://platform.openai.com/account/billing/overview?ref=abarron.com">the Billing page</a>. $5 will go very far for just hobby usage.</p><p>We&apos;ll be using the <a href="https://github.com/alexrudall/ruby-openai?ref=abarron.com">ruby-openai</a> gem. It&apos;s not an official library made by OpenAI, but it more than does the job and OpenAI recommends it in their API documentation.</p><p>Add <code>gem &quot;ruby-openai&quot;</code> to your <code>Gemfile</code> and run <code>bundle install</code>.</p><p>We&apos;ll also want to create an initializer file to configure the gem with our OpenAI API key.</p><pre><code class="language-bash">touch config/initializers/openai.rb</code></pre><p>Add the following to the file.</p><pre><code class="language-ruby"># config/initializers/openai.rb

OpenAI.configure do |config|
  config.access_token = ENV.fetch(&quot;OPENAI_ACCESS_TOKEN&quot;)
end</code></pre><p>We&apos;ll also need to set this environment variable in our command line to get this to work.</p><pre><code class="language-bash">export OPENAI_ACCESS_TOKEN=your_api_key_here</code></pre><p>We can test all this is working by opening up the <code>rails console</code>. You should be able to run <code>client = OpenAI::Client.new</code> to create a client and then <code>client.models.list</code> to get a list of available AI models.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001&gt; client = OpenAI::Client.new
=&gt; 
#&lt;OpenAI::Client:0x00007fa6404fa6f8
...
irb(main):002&gt; client.models.list
=&gt; 
{&quot;object&quot;=&gt;&quot;list&quot;,
 &quot;data&quot;=&gt;
  [{&quot;id&quot;=&gt;&quot;curie-search-query&quot;,
    &quot;object&quot;=&gt;&quot;model&quot;,
    &quot;created&quot;=&gt;1651172509,
    &quot;owned_by&quot;=&gt;&quot;openai-dev&quot;},
   {&quot;id&quot;=&gt;&quot;babbage-search-query&quot;,
    &quot;object&quot;=&gt;&quot;model&quot;,
    &quot;created&quot;=&gt;1651172509,
    &quot;owned_by&quot;=&gt;&quot;openai-dev&quot;},
   {&quot;id&quot;=&gt;&quot;dall-e-3&quot;,
    &quot;object&quot;=&gt;&quot;model&quot;,
    &quot;created&quot;=&gt;1698785189,
    &quot;owned_by&quot;=&gt;&quot;system&quot;},
   {&quot;id&quot;=&gt;&quot;babbage-search-document&quot;,
    &quot;object&quot;=&gt;&quot;model&quot;,
    &quot;created&quot;=&gt;1651172510,
    &quot;owned_by&quot;=&gt;&quot;openai-dev&quot;},
   {&quot;id&quot;=&gt;&quot;dall-e-2&quot;,
...</code></pre><p>If the above fails, the most likely reason is because of an issue configuring your API key. Ensure your environment variable is set and the initializer file references it correctly.</p><p>We can hit the OpenAI API technically from nearly anywhere in our Rails app, but I prefer dropping this logic into a service. We&apos;ll create two directories and two files.</p><pre><code class="language-ruby">mkdir app/services
mkdir app/services/openai_services
touch app/services/openai_services.rb
touch app/services/openai_services/chat_service.rb</code></pre><p>In <code>app/services/openai_services.rb</code>, we can initialize the module with the following code.</p><pre><code class="language-ruby"># app/services/openai_services.rb

module OpenAIServices
end</code></pre><p>To be able to reference this module as <code>OpenAIServices</code>, we need to make an addition to our <code>config/initializers/inflections.rb</code> file. Otherwise, our Rails app will be expecting <code>OpenaiServices</code> because the file name is <code>openai_services.rb</code> and Rails doesn&apos;t know the <code>ai</code> part should read as <code>AI</code>. We&apos;ll encounter the same problem with <code>SerpApi</code>. We&apos;ll fix both with the below code.</p><pre><code class="language-ruby"># config/initializers/inflections.rb

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym &quot;OpenAI&quot;
  inflect.acronym &quot;SerpApi&quot;
end</code></pre><p>Open up our <code>ChatService</code> file and let&apos;s get to work on hitting the OpenAI API. We&apos;ll start by creating a <code>ChatService</code> class that is initialized with a <code>search_analysis</code> object. Our <code>SearchAnalysis</code> model includes everything we might need for our AI prompt.</p><pre><code class="language-ruby"># app/services/openai_services/chat_service.rb

module OpenAIServices
  class ChatService
    attr_reader :search_analysis
    
    def initialize(search_analysis)
      @search_analysis = search_analysis
    end
  end
end</code></pre><p>Next, we&apos;ll create a <code>call</code> method that will handle the logic of sending a message to the Chat API.</p><pre><code class="language-ruby"># app/services/openai_services/chat_service.rb

module OpenAIServices
  class ChatService
    attr_reader :search_analysis
    
    def initialize(search_analysis)
      @search_analysis = search_analysis
    end
    
    def call
      client = OpenAI::Client.new
      response = client.chat(
        parameters: {
          model: &quot;gpt-3.5-turbo&quot;,
          messages: [{ role: &quot;user&quot;, content: @search_analysis.prompt}],
          temperature: 0,
        })
      chat_response = response.dig(&quot;choices&quot;, 0, &quot;message&quot;, &quot;content&quot;)
    end
  end
end</code></pre><p>We ought to test this connection in <code>rails console</code> before continuing.</p><pre><code class="language-ruby">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001&gt; search_analysis = SearchAnalysis.new(prompt: &quot;Hey, can you hea
r me?&quot;)
=&gt; 
#&lt;SearchAnalysis:0x00007f6684c5a7f0
...
irb(main):002&gt; OpenAIServices::ChatService.new(search_analysis).call
=&gt; &quot;Yes, I can hear you. How can I assist you today?&quot;</code></pre><p>Great, we got a working response back from ChatGPT. Now we&apos;ll improve the logic in our <code>call</code> method to follow typical Rails service object conventions of returning an <code>OpenStruct</code>.</p><pre><code class="language-ruby"># app/services/openai_services/chat_service.rb

module OpenAIServices
  class ChatService
    attr_reader :search_analysis
    
    def initialize(search_analysis)
      @search_analysis = search_analysis
    end
    
    def call
      begin
        client = OpenAI::Client.new
        response = client.chat(
          parameters: {
            model: &quot;gpt-3.5-turbo&quot;,
            messages: [{ role: &quot;user&quot;, content: @search_analysis.prompt}],
            temperature: 0,
          })
        chat_response = response.dig(&quot;choices&quot;, 0, &quot;message&quot;, &quot;content&quot;)
      rescue StandardError =&gt; e
        OpenStruct.new(success?: false, error: e)
      else
        OpenStruct.new({success?: true, openai_response: chat_response})
      end
    end
  end
end</code></pre><p>The prompt we&apos;re sending to ChatGPT is currently just what the user put in the form, but we actually need to append some additional context to their prompt. Remember this is a search results analyzer so we&apos;ll need to include the search results in the prompt. It&apos;d also be a good idea to tell ChatGPT what our search query was. Let&apos;s add a private method called <code>generate_prompt</code> to handle this logic.</p><pre><code class="language-ruby"># app/services/openai_services/chat_service.rb

    private
    
      def generate_prompt
        &lt;&lt;~PROMPT
          #{@search_analysis.prompt}

          My search query: #{@search_analysis.query}&quot;

          The search results were:
          #{@search_analysis.search_results}
        PROMPT
      end</code></pre><p>We also need to update the <code>content</code> value in the chat parameters to call our method <code>messages: [{ role: &quot;user&quot;, content: generate_prompt}]</code>.</p><pre><code class="language-ruby"># app/services/openai_services/chat_service.rb
    
    def call
      begin
        client = OpenAI::Client.new
        response = client.chat(
          parameters: {
              model: &quot;gpt-3.5-turbo&quot;,
              messages: [{ role: &quot;user&quot;, content: generate_prompt}],
              temperature: 0,
          })
        chat_response = response.dig(&quot;choices&quot;, 0, &quot;message&quot;, &quot;content&quot;)
      rescue StandardError =&gt; e
        OpenStruct.new(success?: false, error: e)
      else
        OpenStruct.new({success?: true, results: chat_response})
      end
    end</code></pre><p>We&apos;ll do our <code>rails console</code> check again.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails cLoading development environment (Rails 7.0.8)
irb(main):001* search_analysis = SearchAnalysis.new(
irb(main):002*   query: &quot;italian city with best food&quot;,
irb(main):003*   prompt: &quot;Analyze the search results below and tell me which city I should visit if I&apos;m a foodie. Tell me why as well&quot;,
irb(main):004*   search_results: &quot;Surveys confirm Bologna as the food capital of Italy thanks to its wonderful food products like mortadella.&quot;
irb(main):005&gt; )
=&gt; 
#&lt;SearchAnalysis:0x00007f537cd93c40
...
irb(main):006&gt; OpenAIServices::ChatService.new(search_analysis).call
=&gt; #&lt;OpenStruct success?=true, results=&quot;Based on the search results, it is suggested that you should visit Bologna if you are a foodie. Bologna is known as the food capital of Italy, and it offers wonderful food products like mortadella. This implies that Bologna has a rich culinary tradition and is renowned for its delicious Italian cuisine. As a foodie, you can expect to indulge in a wide variety of authentic and high-quality Italian dishes in Bologna, making it an ideal destination for your culinary exploration.&quot;&gt;</code></pre><p>This all looks good. We&apos;ll leave this for now and move on to our SerpApi connection.</p><h2 id="connecting-to-serpapi">Connecting to SerpApi</h2><p>First, <a href="https://serpapi.com/users/sign_up?ref=abarron.com">create a SerpApi account</a> and then <a href="https://serpapi.com/manage-api-key?ref=abarron.com">locate your API key</a>.</p><p>SerpApi&apos;s free plan grants 100 searches per month. For our hobby purposes, this will be enough, but we should be judicious with how we use them. Fortunately, SerpApi caches recent searches so if you re-run the same search several times over the course of an afternoon, you&apos;ll get cached results that don&apos;t consume your free search allowance.</p><p>It&apos;s also worth noting your <a href="https://serpapi.com/searches?ref=abarron.com">search history is stored in your dashboard</a> with the complete JSON response as well as a UI rendering of what your search results would have looked like in the browser.</p><p>From what I can tell from job postings, SerpApi uses Ruby on Rails as a major part of their tech stack. They must be Rubyists at heart so course they have an <a href="https://github.com/serpapi/google-search-results-ruby?ref=abarron.com">official Ruby gem</a> that we&apos;ll be using. </p><p>Add <code>gem &quot;google_search_results&quot;</code> to your <code>Gemfile</code> and run <code>bundle install</code>.</p><p>The following steps we&apos;ll be very similar to what we did for the OpenAI connection. First, we&apos;ll create an initializer to set our API key.</p><pre><code class="language-bash">touch config/initializers/serpapi.rb</code></pre><p>Set the key using an environment variable again.</p><pre><code class="language-bash">export SERPAPI_API_KEY=your_api_key_here</code></pre><p>And reference that environment variable in the initializer. We&apos;ll use the generic SerpApi class <code>SerpApiSearch</code> which allows us to search any engine as long as we specify the engine in the request. </p><p>If you only wanted to use a particular search engine, you can set the key on classes like <code>GoogleSearch</code>, <code>DuckDuckgoSearch</code>, <code>YandexSearch</code>, etc. By doing this, you can omit the engine in the API request since the engine is already set via the class. <a href="https://github.com/serpapi/google-search-results-ruby?tab=readme-ov-file&amp;ref=abarron.com#supported-search-engine">See the docs</a> for more on this.</p><pre><code class="language-ruby"># config/initializers/serpapi.rb

SerpApiSearch.api_key = ENV.fetch(&quot;SERPAPI_API_KEY&quot;)</code></pre><p>We&apos;ll test our setup in <code>rails console</code>.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001* query = {
irb(main):002*   q: &quot;italian city with best food&quot;,
irb(main):003*   engine: &quot;google&quot;
irb(main):004&gt; }
=&gt; {:q=&gt;&quot;italian city with best food&quot;, :engine=&gt;&quot;google&quot;}
irb(main):005&gt; search = SerpApiSearch.new(query)
irb(main):006&gt; organic_results = search.get_hash[:organic_results]
=&gt; 
[{:position=&gt;1,
...
irb(main):007&gt; organic_results
=&gt; 
[{:position=&gt;1,
  :title=&gt;&quot;9 Best Food Cities in Italy&quot;,
  :link=&gt;&quot;https://www.celebritycruises.com/blog/best-food-cities-in-italy&quot;,
  :redirect_link=&gt;
   &quot;https://www.google.comhttps://www.celebritycruises.com/blog/best-food-cities-in-italy&quot;,
  :displayed_link=&gt;
   &quot;https://www.celebritycruises.com &#x203A; blog &#x203A; best-food-ci...&quot;,
  :thumbnail=&gt;
   &quot;https://serpapi.com/searches/65af9352fdca3e33d38b4de0/images/5c56f82085de25fb7b377b8923ee596b106bddcb2a9676ce6af1dc1ce6a4662b.jpeg&quot;,
  :favicon=&gt;
   &quot;https://serpapi.com/searches/65af9352fdca3e33d38b4de0/images/5c56f82085de25fb7b377b8923ee596b5a12458ea0949e9ab510eb6af83484f2.png&quot;,
  :date=&gt;&quot;Nov 29, 2023&quot;,
  :snippet=&gt;
   &quot;9 Best Food Cities in Italy &#xB7; Bologna &#xB7; Palermo &#xB7; Rome &#xB7; Florence &#xB7; Cagliari &#xB7; Sorrento &#xB7; Genoa &#xB7; Parma. Street view of Parma. Parma. Often ...&quot;,
  :snippet_highlighted_words=&gt;[&quot;Best Food Cities&quot;, &quot;Italy&quot;],
...</code></pre><p>This all looks good. Now onto our service object. Let&apos;s create the module and directory.</p><pre><code class="language-bash">mkdir app/services/serpapi_services
touch app/services/serpapi_services.rb
touch app/services/serpapi_services/search_service.rb</code></pre><pre><code class="language-ruby"># app/services/serpapi_services.rb

module SerpApiServices
end</code></pre><p>Our initial setup looks exactly the same as our <code>ChatService</code> where we initialize an instance of the <code>SearchService</code> with a <code>search_analysis</code>.</p><pre><code class="language-ruby"># app/services/serpapi_services/search_service.rb

module SerpApiServices
  class SearchService
    attr_reader :search_analysis
    
    def initialize(search_analysis)
      @search_analysis = search_analysis
    end
  end
end</code></pre><p>We&apos;ll then create a basic <code>call</code> method.</p><pre><code class="language-ruby"># app/services/serpapi_services/search_service.rb

module SerpApiServices
  class SearchService
    attr_reader :search_analysis
    
    def initialize(search_analysis)
      @search_analysis = search_analysis
    end
    
    def call
      search = SerpApiSearch.new(
        q: @search_analysis.query, 
        engine: @search_analysis.engine
      )
      search.get_hash[:organic_results]
    end
  end
end</code></pre><p>We could theoretically take the whole JSON response from SerpApi and send it to ChatGPT for analysis. I&apos;ve tested this and ChatGPT understands the content of the JSON well enough to analyze it. Trouble is the JSON body is very verbose with a lot of characters there are irrelevant to ChatGPT. This is an issue because OpenAI charges more for prompts with a lot of text. We&apos;ll need to cut down how much of the response body we use.</p><p>We&apos;ll simplify the search results to only use organic results and only use page titles, snippets, and links. We&apos;ll also save just the content and none of the JSON syntax. This will make it more readable for both humans and ChatGPT as well as keep the character count down. </p><p>We&apos;ll handle this logic in a private <code>textify_results</code> method. I&apos;ll also add the <code>OpenStruct</code> responses to our <code>call</code> method.</p><pre><code class="language-ruby"># app/services/serpapi_services/search_service.rb

  private
  
    def textify_results(results)
      concatenated_text = &quot;&quot;

      results.each do |result|
        concatenated_text += &lt;&lt;~RESULT
          Title: #{result[:title]}
          Link: #{result[:link]}
          Snippet: #{result[:snippet]}
        RESULT
      end

      return concatenated_text
    end</code></pre><p>Test it in <code>rails console</code>.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001* search_analysis = SearchAnalysis.new(
irb(main):002*   query: &quot;italian city with best food&quot;,
irb(main):003*   engine: &quot;google&quot;
irb(main):004&gt; )
irb(main):005&gt; test_search = SerpApiServices::SearchService.new(search_analysis).call
=&gt; #&lt;OpenStruct success?=true, results=&quot;Title: 9 Best Food Cities in Ita...
irb(main):006&gt; test_search.results
=&gt; &quot;Title: 9 Best Food Cities in Italy\nLink: https://www.celebritycruises.com/blog/best-food-cities-in-italy\nSnippet: 9 Best Food Cities in Italy &#xB7; Bologna &#xB7; Palermo &#xB7; Rome &#xB7; Florence &#xB7; Cagliari &#xB7; Sorrento &#xB7; Genoa &#xB7; Parma. Street view of Parma. Parma. Often ...\n\nTitle: 10 of the best Italian cities for food - Times Travel\nLink: https://www.thetimes.co.uk/travel/destinations/europe/italy/italy-food-guide\nSnippet: Italy&apos;s best cities for food &#xB7; 1. Florence &#xB7; 2. Venice &#xB7; 3. Naples &#xB7; 4. Rome &#xB7; 5. Milan &#xB7; 6. Bologna &#xB7; 7. Genoa &#xB7; 8. Parma.\n\n
...</code></pre><p>All good here. Onto the next task.</p><h2 id="linking-it-all-together">Linking It All Together</h2><p>We finally have both APIs wired up and a model ready to go. Now we need to link all these pieces together.</p><p>We&apos;ll start in our <code>SearchAnalysis</code> model and create methods that will call our API service objects.</p><p>First, a simple<code>run_search</code> method. This will call our SerpApi service and if the response is successful, it will store the results in <code>search_results</code>. </p><p>If it fails, we&apos;ll record the error message and mark the record as failed. Ideally, the error message would be cleaned up to present something user friendly, but that can be left aside for now.</p><pre><code class="language-ruby"># app/models/search_analysis.rb

  def run_search
    # We only run the search if the status is &quot;pending_search&quot;
    return unless self.status == &quot;pending_search&quot;
    
    search_response = SerpApiServices::SearchService.new(self).call
    
    if search_response.success?
      self.search_results = search_response.results
      self.status = &quot;pending_chat&quot;
    else
      self.search_results = &quot;Failed due to error: #{search_response.error.to_s}&quot;
      self.status = &quot;failed&quot;
    end
    
    self.save
  end</code></pre><p>A quick test in <code>rails console</code> confirms this works and our search results get saved.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001* search_analysis = SearchAnalysis.new(
irb(main):002*   query: &quot;italian city with best food&quot;,
irb(main):003*   engine: &quot;google&quot;,
irb(main):004&gt; prompt: &quot;Analyze the search results below and tell me which city I should visit if I&apos;m a foodie. Tell me why as well.&quot;)
=&gt; 
#&lt;SearchAnalysis:0x00007fbc9744f470
...
irb(main):005&gt; search_analysis.run_search
  TRANSACTION (0.1ms)  begin transaction
  SearchAnalysis Create (0.4ms)  INSERT INTO &quot;search_analyses&quot; (&quot;query&quot;, &quot;engine&quot;, &quot;prompt&quot;, &quot;search_results&quot;, &quot;chat_response&quot;, &quot;status&quot;, &quot;created_at&quot;, &quot;updated_at&quot;) VALUES (?, ?, ?, ?, ?, ?, ?, ?)  [[&quot;query&quot;, &quot;italian city with best food&quot;], [&quot;engine&quot;, 0], [&quot;prompt&quot;, &quot;Based solely on these search results, where should I go in Italy for a delicious vacation?&quot;], [&quot;search_results&quot;, &quot;Title: 9 Best Food Cities in Italy\nLink: https://www.celebritycruises.com/blog/best-food-cities-in-italy\nSnippet: 9 Best Food Cities in Italy &#xB7; Bologna &#xB7; Palermo &#xB7; Rome &#xB7; Florence &#xB7; Cagliari &#xB7; Sorrento &#xB7; Genoa &#xB7; Parma. Street view of Parma. Parma. Often ...\n\n...
  TRANSACTION (3.4ms)  commit transaction
=&gt; true</code></pre><p>Next, we&apos;ll create a <code>run_chat</code> method. The logic will effectively be identical to the <code>run_search</code> method.</p><pre><code class="language-ruby"># app/models/search_analysis.rb

  def run_chat
    # We only run the chat if the status is &quot;pending_chat&quot;
    return unless self.status == &quot;pending_chat&quot;
    
    chat_response = OpenAIServices::ChatService.new(self).call
    
    if chat_response.success?
      self.chat_response = chat_response.results
      self.status = &quot;complete&quot;
    else
      self.chat_response = &quot;Failed due to error: #{chat_response.error.to_s}&quot;
      self.status = &quot;failed&quot;
    end
    self.save
  end</code></pre><p>And now the test.</p><pre><code class="language-bash">user@host:~/search-results-ai-analyzer$ rails c
Loading development environment (Rails 7.0.8)
irb(main):001&gt; search_analysis = SearchAnalysis.last
  SearchAnalysis Load (0.2ms)  SELECT &quot;search_analyses&quot;.* FROM &quot;search_analyses&quot; ORDER BY &quot;search_analyses&quot;.&quot;id&quot; DESC LIMIT ?  [[&quot;LIMIT&quot;, 1]]
=&gt; 
#&lt;SearchAnalysis:0x00007f9d61599d30
...
irb(main):002&gt; search_analysis.run_chat
  TRANSACTION (0.1ms)  begin transaction
  SearchAnalysis Update (0.4ms)  UPDATE &quot;search_analyses&quot; SET &quot;chat_response&quot; = ?, &quot;updated_at&quot; = ? WHERE &quot;search_analyses&quot;.&quot;id&quot; = ?  [[&quot;chat_response&quot;, &quot;Based on the search results, the city you should visit if you&apos;re a foodie is Bologna. Bologna consistently appears in multiple search results and is mentioned in titles such as \&quot;9 Best Food Cities in Italy,\&quot; \&quot;10 of the best Italian cities for food,\&quot; and \&quot;Our Favorite Food Cities In Italy.\&quot; Bologna is known for its rich culinary traditions, including dishes like tortellini, ragu, and mortadella. It is also praised for its local markets, food festivals, and vibrant food scene. Therefore, if you want to experience authentic Italian cuisine and immerse yourself in a city with a strong food culture, Bologna is the ideal choice.&quot;], [&quot;updated_at&quot;, &quot;2024-01-24 12:14:40.214658&quot;], [&quot;id&quot;, 6]]
  TRANSACTION (2.8ms)  commit transaction
=&gt; true</code></pre><p>Beautiful. We got a helpful response from ChatGPT telling us we should go to Bologna for our Italian foodie vacation.</p><p>Thanks to our scaffolding, we know our views will already display all this information to the user. We also already tested the <code>create</code> action of our controller.</p><p>We could call the search and chat services from our controller, but I think a cleaner way would be to handle all this in the model. We&apos;ll always save a search analysis request with valid data so we don&apos;t need the controller to worry about any of that. Instead, we can use our <code>run_search</code> and <code>run_chat</code> methods as callbacks that fire after the record is created.</p><pre><code class="language-ruby"># app/models/search_analysis.rb

class SearchAnalysis &lt; ApplicationRecord
  enum :engine, { google: 0, bing: 1, duckduckgo: 2, yahoo: 3, yandex: 4, baidu: 5 }
  enum :status, { pending_search: 0, pending_chat: 1, complete: 2, failed: 3 }
  
  validates :query, presence: true
  validates :engine, presence: true
  validates :prompt, presence: true

  after_create :run_search, :run_chat
  
...
end</code></pre><p>One potential concern here would be the risk of the search failing and then our chat would also fail (or not be useful) because we wouldn&apos;t have any search results to send ChatGPT. Fortunately, we already handled that when we set it so <code>run_chat</code> would fully execute only if the status was <code>pending_chat</code> which is only set if the <code>run_search</code> method was successful.</p><p>Now we can try requesting a search analysis through our form. Rails will automatically fire our methods to call SerpApi and ChatGPT. The form submission will take a few seconds to process because it has to make two API calls. With a little patience we finally see that it all works.</p><p>Rails renders the search results and chat response by default without any newlines so it&apos;s worth adding <code>simple_format</code> to these.</p><pre><code class="language-ruby"># app/views/_search_analysis.html.erb

  &lt;p class=&quot;my-5&quot;&gt;
    &lt;strong class=&quot;block font-medium mb-1&quot;&gt;Search results:&lt;/strong&gt;
    &lt;%= simple_format(search_analysis.search_results) %&gt;
  &lt;/p&gt;

  &lt;p class=&quot;my-5&quot;&gt;
    &lt;strong class=&quot;block font-medium mb-1&quot;&gt;Chat response:&lt;/strong&gt;
    &lt;%= simple_format(search_analysis.chat_response) %&gt;
  &lt;/p&gt;</code></pre><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2024/01/image-3.png" class="kg-image" alt loading="lazy" width="842" height="1109" srcset="https://abarron.com/content/images/size/w600/2024/01/image-3.png 600w, https://abarron.com/content/images/2024/01/image-3.png 842w" sizes="(min-width: 720px) 720px"></figure><p>It&apos;s rather ugly, but everything is now working. Perhaps we can pass this off to some designers to pretty up.</p><h2 id="areas-to-improve">Areas to Improve</h2><p>What we have is a good starting point, but it could be a lot better. Some possible improvements:</p><pre><code>* Process API calls in a background job.
* Display user friendly error messages in case calls to SerpApi or OpenAI fail.
* Pretty up the UI.
* Allows users to store prompts for future use.
* Add support for all the other search engines SerpApi supports like Google News, Finance, Maps, Jobs, etc.
* Allow users to send additional chat messages to ChatGPT and present this in a chatbox.
* Re-try failed search analyses.</code></pre><h2 id="source-code">Source Code</h2><p>The full source code of this application is available on <a href="https://github.com/alexbarron/search-results-ai-analyzer?ref=abarron.com">Github</a>.</p>]]></content:encoded></item><item><title><![CDATA[Getting Started with the Chatbase API]]></title><description><![CDATA[Chatbase offers an API allowing you to create, train, and chat with Chatbase chatbots programmatically. This is invaluable for developers who don't want to maintain an integration with OpenAI, but are technical enough to integrate with Chatbase and customize the end user experience in ways that Chatbase doesn't support yet. ]]></description><link>https://abarron.com/getting-started-with-the-chatbase-api/</link><guid isPermaLink="false">645cf06056f43f07294f2642</guid><category><![CDATA[Guides]]></category><category><![CDATA[Chatbase]]></category><category><![CDATA[OpenAI]]></category><category><![CDATA[AI]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Tue, 23 May 2023 14:15:00 GMT</pubDate><content:encoded><![CDATA[<p><em>The following article contains affiliate links to </em><a href="https://www.chatbase.co/?via=api-maestro&amp;ref=abarron.com"><em>Chatbase</em></a><em>. If you end up signing up for a paid plan with Chatbase, I will get a commission. There&apos;s no additional cost to you.</em></p><p><a href="https://www.chatbase.co/?via=api-maestro&amp;ref=abarron.com">Chatbase</a> is a tool to simplify building a custom chatbot trained with whatever content you want. You can train it with content from files, raw text, or a website.  Chatbase makes it trivial to train a chatbot and then embed it on your website.</p><p>For more technical users, Chatbase offers an API allowing you to create, train, and chat with Chatbase chatbots programmatically. This is invaluable for developers who don&apos;t want to maintain an integration with OpenAI, but are technical enough to integrate with Chatbase and customize the end user experience in ways that Chatbase doesn&apos;t support yet. </p><p>For example, you might want to customize the look and feel of a chatbot in ways Chatbase doesn&apos;t support. Or you might want to integrate Chatbase into other tools your organization uses such as support or internal company chat.</p><p>In this article, we&apos;ll go over what the API can do and cover some of its limitations. You&apos;ll even be able to <a href="#what-next">chat with this very article</a>.</p><h2 id="api-access">API Access</h2><p>The API is restricted to paying users of Chatbase. The lowest paid membership is $19/month and scale up from there offering more chatbots, messages/month, and characters/chatbot. Check out their <a href="https://www.chatbase.co/pricing?via=api-maestro&amp;ref=abarron.com">pricing page</a> for more information.</p><p>Once you&apos;ve signed up for a subscription, navigate to your &quot;Account&quot; page and scroll to the bottom to create a new API key.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2023/05/Screenshot_20230511_160134.png" class="kg-image" alt loading="lazy" width="802" height="254" srcset="https://abarron.com/content/images/size/w600/2023/05/Screenshot_20230511_160134.png 600w, https://abarron.com/content/images/2023/05/Screenshot_20230511_160134.png 802w" sizes="(min-width: 720px) 720px"></figure><p>I suggest copying your API key and pasting it in your terminal as an environment variable to most easily follow along with this guide. </p><pre><code>export CHATBASE_API_KEY=your_api_key_here</code></pre><p>Otherwise, replace where I use <code>$CHATBASE_API_KEY</code> with your API key in the sample API calls I include below.</p><h2 id="crawl-a-website">Crawl a Website</h2><p>Chatbase has an API to crawl a website and get all the site&apos;s URLs for you. This is a cool feature that they didn&apos;t really need to offer, but it&apos;s certainly nice to have. Using the response from this endpoint, you can drop in the list of links as source training data in the create chatbot call we&apos;ll look at next.</p><p>Note that for big websites the crawler may crash and not collect everything. In this case, you&apos;re better off creating your own crawler or referring to <a href="https://platform.openai.com/docs/tutorials/web-qa-embeddings?ref=abarron.com">OpenAI&apos;s tutorial on creating a website Q&amp;A</a> that includes a website crawler and scraper.</p><p>You can also slim down the list of URLs included by passing a source URL to the API that is targeted on a particular path. For example, <code>example.com/developers</code> to only get URLs within the <code>/developers</code> part of a website.</p><p>This API is available through <code>/api/v1/fetch-links</code> and accepts one URL parameter of <code>sourceURL</code> for the URL of the website you want to scrape. </p><pre><code>curl &apos;https://www.chatbase.co/api/v1/fetch-links?sourceURL=https://www.apimaestro.com&apos; \
    --request &quot;GET&quot; \
    -H &quot;Authorization: Bearer $CHATBASE_API_KEY&quot;</code></pre><p>The response body will contain an array called <code>fetchedLinks</code> with objects containing a <code>url</code> and <code>size</code>. The <code>size</code> field contains the number of characters present on the page. This is important to keep in mind because Chatbase limits how many characters you can train each chatbot on. The base $19/month plan is limited to 2 million characters per chatbot. The limit scales up along with the more premium plans. Refer to the <a href="https://www.chatbase.co/pricing?via=api-maestro&amp;ref=abarron.com">Chatbase pricing page</a> for more details.</p><p>The response body will also contain a <code>stoppingReason</code> field indicating why the crawler may have stopped. According to Chatbase&apos;s docs, this most commonly happens when the crawler takes more than 60 seconds. The crawler will stop if it exceeds 60 seconds and will return only what it found in those 60 seconds. Their API docs don&apos;t currently indicate what else you might see in this field.</p><pre><code>{
  &quot;fetchedLinks&quot;: [
    {
      &quot;url&quot;: &quot;https://www.apimaestro.com/&quot;,
      &quot;size&quot;: 4048
    },
    {
      &quot;url&quot;: &quot;https://www.apimaestro.com/about/&quot;,
      &quot;size&quot;: 1405
    },
    {
      &quot;url&quot;: &quot;https://www.apimaestro.com/what-is-openapi/&quot;,
      &quot;size&quot;: 6729
    },
    {
      &quot;url&quot;: &quot;https://www.apimaestro.com/getting-started-with-pocket-bitcoins-api/&quot;,
      &quot;size&quot;: 15527
    },
    {
      &quot;url&quot;: &quot;https://www.apimaestro.com/creating-an-openapi-specification-the-hard-way/&quot;,
      &quot;size&quot;: 18034
    }
  ],
  &quot;stoppingReason&quot;: &quot;&quot;
}</code></pre><h2 id="create-a-chatbot">Create a Chatbot</h2><p>Now we&apos;ll create our first chatbot through the API. This is done making a POST request to the <code>api/v1/create-chatbot</code> endpoint.</p><p>The request body should contain a <code>chatbotName</code> and some source materials to train the bot. The source materials can be sent in 2 different fields:</p><ol><li><code>urlsToScrape</code> to scrape text from web pages. This field should contain an array of URLs.</li><li><code>sourceText</code> to use raw text to train the bot. As of my testing today, this field required at least 100 characters. The maximum number of characters depends on your plan.</li></ol><p>You must provide at least one of the above fields. You can include both if you have a mix of URLs and raw text.</p><pre><code>curl https://www.chatbase.co/api/v1/create-chatbot \
  -H &apos;Content-Type: application/json&apos; \
  -H &quot;Authorization: Bearer $CHATBASE_API_KEY&quot; \
  -d &apos;{&quot;sourceText&quot;: &quot;risus sed vulputate odio ut enim blandit volutpat maecenas volutpat blandit aliquam etiam erat velit scelerisque in dictum non consectetur a erat nam at lectus urna duis convallis convallis tellus id interdum velit laoreet id donec ultrices tincidunt arcu non sodales neque sodales ut etiam sit amet nisl purus in&quot;, &quot;urlsToScrape&quot;: [&quot;https://apimaestro.com/getting-started-with-pocket-bitcoins-api/&quot;, &quot;https://apimaestro.com/what-is-openapi/&quot;], &quot;chatbotName&quot;: &quot;API Maestro&quot;}&apos;</code></pre><p>The response body will contain a <code>chatbotId</code>field with the ID of your chatbot. </p><pre><code>{&quot;chatbotId&quot;: &quot;exampleId-123&quot;}</code></pre><p>You&apos;ll need this ID for all API calls chatting with or modifying your chatbot. If you fail to save the ID from the API response, you can always find it again in the Chatbase UI under your chatbot&apos;s settings page.</p><h3 id="possible-errors-creating-a-chatbot">Possible Errors Creating a Chatbot</h3><p>The Chatbase API docs don&apos;t explain anything about possible errors, but I was able to find a few playing around with this endpoint.</p><ul>
<li>If you don&apos;t pass any training material, you&apos;ll get a 500 error with message &quot;Cannot read properties of undefined (reading &apos;replace&apos;)&quot;</li>
<li>If you&apos;re already at your limit for how many chatbots your plan allows, you&apos;ll get a 403 error with message &quot;Limit to 5 chatbots for your plan. Delete existing chatbots or upgrade plan to increase the limit.&quot;</li>
<li>If you pass too few characters as training material, you&apos;ll get a 403 error with message &quot;Only 51 characters were extracted. undefined&quot;</li>
<li>If you pass too many characters as training material, you&apos;ll get a 403 with message &quot;Exceeded character limit for your plan of 2000000 characters&quot;</li>
</ul>
<h2 id="updating-a-chatbot">Updating a Chatbot</h2><p>The Chatbase API also has an endpoint to update a chatbot via POST <code>/api/v1/update-chatbot-data</code>. Currently, this endpoint only allows you to change the name of your chatbot and replace the training material. Whatever material you include in the update call will get rid of all the past training material you gave your bot</p><p>It&apos;s important to keep this replace function in mind because it means you must store on your side a list of all URLs and text you gave your chatbot and pass them again through this endpoint to ensure you don&apos;t tell your chatbot to forget anything. More on this in the <a href="#challenges-and-limitations">challenges and limitations</a> section of this guide.</p><p>This endpoint takes in all the same fields as the create call except you must also pass in a <code>chatbotId</code> field.</p><pre><code>curl https://www.chatbase.co/api/v1/update-chatbot-data \
  -H &apos;Content-Type: application/json&apos; \
  -H &quot;Authorization: Bearer $CHATBASE_API_KEY&quot; \
  -d &apos;{&quot;chatbotId&quot;: &quot;exampleId-123&quot;, &quot;sourceText&quot;: &quot;Chatbase offers an API allowing you to create, train, and chat with Chatbase chatbots programmatically. This is invaluable for developers who don&apos;t want to maintain an integration with OpenAI, but are technical enough to integrate with Chatbase and customize the end user experience in ways that Chatbase doesn&apos;t support yet.&quot;, &quot;chatbotName&quot;: &quot;API Maestro new name&quot;}&apos;</code></pre><p>The response body is the same as creating a chatbot. The possible errors are also the same from what I can tell.</p><h2 id="chatting-with-your-chatbot">Chatting with Your Chatbot</h2><p>Finally, the moment you&apos;ve been waiting for; chatting with your Chatbase chatbot. This is done through the POST <code>/api/v1/chat</code> endpoint.</p><p>The request body should contain the following:</p><ul>
<li><code>messages</code> which is an array containing each message in the chat. A message object contains a <code>role</code> and <code>content</code>. Supports up to a maximum of 7 messages.
<ul>
<li><code>role</code> can be <code>assistant</code> to refer to the chatbot and <code>user</code> to refer the human user</li>
<li><code>content</code> is the message content from either the chatbot or human user</li>
</ul>
</li>
<li><code>stream</code> which is a boolean where if <code>true</code> will stream the chatbot&apos;s response to you word by word as raw text. If <code>false</code>, it will wait until the full response is generated before sending you the response as JSON.</li>
<li><code>chatbotId</code> which is a string of your chatbot&apos;s ID</li>
</ul>
<pre><code>curl https://www.chatbase.co/api/v1/chat \
  -H &quot;Authorization: Bearer $CHATBASE_API_KEY&quot; \
  -d &apos;{&quot;messages&quot;:[{&quot;content&quot;:&quot;How can I help you?&quot;,&quot;role&quot;:&quot;assistant&quot;},{&quot;content&quot;:&quot;What is API Maestro?&quot;,&quot;role&quot;:&quot;user&quot;}],&quot;chatId&quot;:&quot;exampleId-123&quot;,&quot;stream&quot;:true}&apos;</code></pre><p>The response body, as mentioned previously, will be raw text with the chatbot&apos;s response if you set <code>stream</code> as true. If you put <code>stream</code> as false, you&apos;ll get the following JSON body back.</p><pre><code>{
    &quot;text&quot;: &quot;API Maestro helps API businesses grow by creating delightful developer experiences and documentation.&quot;
}</code></pre><p>To continue chatting with the same context, you append the chatbot&apos;s last message to the <code>messages</code> object and send another chat call. You can continue doing this until you have 7 messages in the <code>messages</code> object according to their docs; although in practice I found I could go beyond 7 messages. This will probably be patched up eventually.</p><pre><code>curl https://www.chatbase.co/api/v1/chat \
  -H &quot;Authorization: Bearer $CHATBASE_API_KEY&quot; \
  -d &apos;{&quot;messages&quot;:[{&quot;content&quot;:&quot;How can I help you?&quot;,&quot;role&quot;:&quot;assistant&quot;},{&quot;content&quot;:&quot;What is API Maestro?&quot;,&quot;role&quot;:&quot;user&quot;},{&quot;content&quot;:&quot;API Maestro helps API businesses grow by creating delightful developer experiences and documentation.&quot;,&quot;role&quot;:&quot;assistant&quot;},{&quot;content&quot;:&quot;Who runs this website?&quot;,&quot;role&quot;:&quot;user&quot;}],&quot;chatId&quot;:&quot;exampleId-123&quot;,&quot;stream&quot;:true}&apos;</code></pre><h2 id="challenges-and-limitations">Challenges and Limitations</h2><p>The Chatbase API has the basics down well, but it&apos;s very apparent the API is lacking in features compared to the UI experience.</p><p>For example, you can&apos;t set or modify anything for your chatbot except the name and training material through the API whereas the UI allows you to customize the prompt and GPT version. </p><p>You also can&apos;t see what training materials you&apos;ve trained your chatbot with. Ideally, there would be a GET chatbot or GET chatbot training materials endpoint that would return the URLs and raw text you&apos;ve used to train a bot. </p><p>Frustratingly, if you create a chatbot through the API, you also can&apos;t look up what you&apos;ve trained it with through the UI either. It&apos;s all a black box whereas chatbots created through the UI will show you in the UI what source materials it&apos;s been trained with.</p><p>This introduces challenges particularly when updating a chatbot&apos;s training materials through the API. Unless you saved the list of URLs and text you sent to Chatbase when you created the chatbot, you run the risk of accidentally deleting old training material when you update your chatbot with new material.</p><p>With the lack of a GET chatbot endpoint, you also can&apos;t check in on your limits to see if you&apos;re near your cap of characters or messages.</p><p>There&apos;s a lot of promise here, but the API needs some work before it&apos;s ready for prime time. Nevertheless, it&apos;s amazing what Chatbase&apos;s creator Yasser has done by himself in just 2 months. I&apos;m sure great things are still to come for both the UI and API experience.</p><h2 id="what-next">What Next?</h2><ol><li>Consider signing up for <a href="https://www.chatbase.co/?via=api-maestro&amp;ref=abarron.com">Chatbase</a>.</li><li>Check out the <a href="https://www.chatbase.co/docs/main?via=api-maestro&amp;ref=abarron.com">official docs</a>.</li><li>Follow the <a href="https://twitter.com/yasser_elsaid_?ref=abarron.com">Chatbase founder&apos;s Twitter</a> for product updates and follow how his business is growing.</li><li>Try chatting with this article:</li></ol>
<!--kg-card-begin: html-->
<iframe src="https://www.chatbase.co/chatbot-iframe/GT0UdroV5uybM5ULBDOo5" width="100%" height="100" frameborder="0"></iframe>
<!--kg-card-end: html-->
]]></content:encoded></item><item><title><![CDATA[Creating an OpenAPI Specification The Hard Way]]></title><description><![CDATA[<p>OpenAPI (formerly Swagger) is an open-source tool to standardize specification of REST APIs. This standardization enables programmatic parsing of an API&apos;s structure and syntax to automatically generate documentation, client libraries, and even server code. Think of OpenAPI as the source of truth for your API; from schemas to</p>]]></description><link>https://abarron.com/creating-an-openapi-specification-the-hard-way/</link><guid isPermaLink="false">63c5287b2a80001955d22394</guid><category><![CDATA[OpenAPI]]></category><category><![CDATA[Guides]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Tue, 25 Apr 2023 10:17:23 GMT</pubDate><content:encoded><![CDATA[<p>OpenAPI (formerly Swagger) is an open-source tool to standardize specification of REST APIs. This standardization enables programmatic parsing of an API&apos;s structure and syntax to automatically generate documentation, client libraries, and even server code. Think of OpenAPI as the source of truth for your API; from schemas to endpoints to security configurations.</p><p>OpenAPI documents are most commonly created as YAML files. You can also use JSON. The resulting file serves as your fundamental text that streamlines production of documentation and client libraries. This saves time and minimizes errors from manually creating reference docs and API wrappers.</p><p>There are numerous ways to create an OpenAPI spec. <a href="https://openapi.tools/?ref=abarron.com">OpenAPI.tools</a> is probably the most comprehensive list of tools to help you. <a href="https://stoplight.io/?ref=abarron.com">Spotlight</a> and <a href="https://getpostman.com/?ref=abarron.com">Postman</a> are two of the best UI tools to create your spec.</p><p>You can also, of course, do it the hard way. In this guide, we&apos;ll take inspiration from Zed Shaw over at <a href="https://learncodethehardway.org/?ref=abarron.com">Learn Code The Hard Way</a> by writing an OpenAPI YAML file by hand. This is obviously more time-consuming and error prone, but sometimes if you want to understand something, doing it the hard, manual way is the best way. Once you understand it at a deeper level, you become a better conductor of the tools that abstract some of the inner workings.</p><h2 id="what-were-building">What We&apos;re Building</h2><p>We need an idea for a simple API before we can get started. Let&apos;s imagine a simple, CRUD dictionary API that allows user to look up, create, and modify definitions of words. We&apos;ll need the following paths:</p><ul><li>POST /definitions</li><li>GET /definitions/{word}</li><li>PUT /definitions/{word}</li><li>DELETE /definitions/{word}</li></ul><p>The API will use JSON in request and response bodies.</p><p>For security, we&apos;ll use Basic Authentication for all endpoints except GET /definitions.</p><h2 id="initial-setup">Initial Setup</h2><p>Create a file called <code>definitions.yaml</code>. We&apos;ll start by defining the version of OpenAPI we&apos;re using (3.0.0) and same basic description <code>info</code> of our API. The <code>info.version</code> field refers to the version of our API.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
info:
  title: Dictionary REST API
  description: An API to look up definitions of words.
  version: 1.0.0</code></pre><p>Next we&apos;ll add our API&apos;s base URL under <code>servers</code>. You can add multiple servers if you want to support production and development instances. We&apos;ll stick to one for now.</p><p>We&apos;ll also define our security scheme of Basic Auth here as well under <code>components</code>. Doing this defines a security scheme that we can then apply to each endpoint that needs this security scheme.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
info:
  title: Dictionary REST API
  description: An API to look up definitions of words.
  version: 1.0.0
servers:
  - url: https://sample-api.apimaestro.com
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic</code></pre><p>If we wanted to set Basic Auth as our global security scheme and apply it to all endpoints, we could do that under <code>security</code> like the example below. We&apos;ll leave this out of our spec to keep GET /definitions unprotected.</p><pre><code class="language-yaml">components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
security:
  - basicAuth: []</code></pre><h2 id="schemas">Schemas</h2><p>Data schemas are defined under <code>components</code>. You can set a <code>title</code>, <code>description</code>, and <code>type</code>. This content will appear in generated reference documentation.</p><p>The <code>type</code> field can be a string, number, integer, boolean, array, or object. You can also define multiple types for a single field.</p><p>We&apos;ll create a definition schema of type object.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
...

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
  schemas:
    Definition:
    	title: Definition
        description: A definition is a statement that explains the meaning of a word or phrase.
        type: object</code></pre><p>The object&apos;s schema will be referenced when we&apos;re setting request and response bodies for our endpoints. We can define the fields that belong to our object under <code>properties</code>. In the example below, I&apos;ll define the following:</p><ul><li><code>id</code> as a read-only integer</li><li><code>word</code> as a string</li><li><code>created_at</code> and <code>updated_at</code> as read-only strings in the <code>date-time</code> format</li><li><code>meanings</code> as an array of objects</li></ul><p>The meanings array will allow us to handle words with multiple definitions. We could split out meanings as its own schema model, but for now, we can define its schema inline with its parent object.</p><p>Our meanings object will have the following properties:</p><ul><li>id as a read-only integer</li><li><code>part_of_speech</code> as a string</li><li><code>meaning</code> as a string</li></ul><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
...

components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
  schemas:
    Definition:
      title: Definition
      description: A definition is a statement that explains the meaning of a word or phrase.
      type: object
      properties:
        id:
          type: integer
          example: 40125
          readOnly: true
        word:
          type: string
          example: tagliatelle
        created_at:
          type: string
          format: date-time
          example: 2022-12-06T04:55:36.887Z
          readOnly: true
        updated_at:
          type: string
          format: date-time
          example: 2022-12-06T04:55:36.887Z
          readOnly: true
        meanings:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
                example: 333
                readOnly: true
              part_of_speech:
                type: string
                example: noun
              meaning:
                type: string
                example: A type of pasta shaped into long, thin, flat strips</code></pre><p>All set here. Now we can start writing our endpoints.</p><h2 id="endpoints">Endpoints</h2><p>All of our endpoints will go under the <code>paths</code> section. For each path, we can set multiple methods. Our simple CRUD API has only 2 paths; /definitions for POST and /definitions{word} for GET, PUT, and DELETE.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
...

paths:
  /definitions/{word}:
    get:
    put:
    delete:
  /definitions:
    post:</code></pre><h3 id="get-a-definition">Get a Definition</h3><p>First, we&apos;ll add our word look up endpoint.</p><p>Similar to schemas, you can add <code>summary</code> and <code>description</code> content here that will appear in documentation.</p><p>I also recommend setting an <code>operationId</code>. This is used to set the method name in auto-generated client libraries. We&apos;ll set <code>operationId: get_definition</code>. We can also use camel case <code>operationId: getDefinition</code>.</p><p>OpenAPI is smart enough to accept either snake or camel case and convert appropriately. In Python or Ruby, the method would remain in snake case, <code>api.get_definition(word)</code>. In camel case languages like Javascript, the method would be <code>api.getDefinition(word)</code>.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
...

paths:
  /definitions/{word}:
    get:
      summary: Get a word&apos;s definition.
      operationId: get_definition
      description: &gt; # We can write a multi-line description using the `&lt;` character.
        The GET /definitions endpoint accepts a word and returns its meaning.
        The endpoint returns multiple meanings if available.</code></pre><p>Our GET /definitions endpoint will accept one path parameter. This will be the word we&apos;re looking up. We define this under <code>parameters</code>.</p><pre><code class="language-yaml"># definitions.yaml
openapi: 3.0.0
...

paths:
  /definitions/{word}:
    get:
      summary: Get a word&apos;s definition.
      operationId: get_definition
      description: &gt; # We can write a multi-line description using the `&lt;` character.
        The GET /definitions endpoint accepts a word and returns its meaning.
        The endpoint returns multiple meanings if available.
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini</code></pre><p>Next, we define how this endpoint responds under <code>responses</code>. We can define multiple responses depending on the response code.</p><p>We need to define the response <code>description</code> and <code>content</code>. We set the <code>schema</code> of the response body by referencing the definition schema we created previously.</p><pre><code class="language-yaml">paths:
  /definitions/{word}:
    get:
      summary: Get a word&apos;s definition.
      operationId: get_definition
      description: &gt; # We can write a multi-line description using the `&lt;` character.
        The GET /definitions endpoint accepts a word and returns its meaning.
        The endpoint returns multiple meanings if available.
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      responses:
        &apos;200&apos;:
          description: OK
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;</code></pre><p>Let&apos;s also add a 404 Not Found response.</p><pre><code class="language-yaml">paths:
  /definitions/{word}:
    get:
      summary: Gets a definition of a word.
      operationId: get_definition
      description: &gt; # We can write a multi-line description using the `&lt;` character.
        The GET /definitions endpoint accepts a word and returns its meaning.
        The endpoint returns multiple meanings if available.
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      responses:
        &apos;200&apos;:
          description: OK
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;404&apos;:
          description: No definition found for the specified word.</code></pre><h3 id="create-a-definition">Create a Definition</h3><p>The structure for creating a POST request doesn&apos;t require much more than our GET request. We start again with the basic description information.</p><pre><code class="language-yaml">  /definitions:
    post:
      summary: Create a word&apos;s definition
      operationId: create_definition
      description: &gt;
        POST /definitions creates a definition of a word.
        Requests to create a duplicate definitions for a word will be rejected with a 409 response code.
        Requests must be authorized with Basic Auth.</code></pre><p>Remember we want to protect this endpoint with Basic Auth to prevent anyone from creating definitions. We can do that by adding a <code>security</code> specification set to <code>basicAuth</code>.</p><pre><code class="language-yaml">  /definitions:
    post:
      summary: Create a word&apos;s definition
      operationId: create_definition
      description: &gt;
        POST /definitions creates a definition of a word.
        Requests to create a duplicate definitions for a word will be rejected with a 409 response code.
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []</code></pre><p>This time we don&apos;t need to set any parameters. Instead, we&apos;ll accept a request body. Similar to the response body we set in the GET request, we can reference our schema definition in the same way to set the request body.</p><pre><code class="language-yaml">  /definitions:
    post:
      summary: Create a word&apos;s definition
      operationId: create_definition
      description: &gt;
        POST /definitions creates a definition of a word. 
        Requests to create a duplicate definitions for a word will be rejected with a 409 response code. 
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: &apos;#/components/schemas/Definition&apos;
        required: true
      responses:</code></pre><p>Finally, we&apos;ll add a few response codes and specify that successful creations respond with the full definition.</p><pre><code class="language-yaml">  /definitions:
    post:
      summary: Create a word&apos;s definition
      operationId: create_definition
      description: &gt;
        POST /definitions creates a definition of a word.
        Requests to create a duplicate definitions for a word will be rejected with a 409 response code.
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: &apos;#/components/schemas/Definition&apos;
        required: true
      responses:
        &apos;201&apos;:
          description: Word created successfully.
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;400&apos;:
          description: Invalid definition data provided.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;409&apos;:
          description: Word already has a definition.
        &apos;422&apos;:
          description: Unprocessable entity.</code></pre><h3 id="update-a-definition">Update a Definition</h3><p>Things get easy here because we don&apos;t need anything new. We can re-use what we set in the GET and POST requests to create our PUT request. We&apos;ll copy the POST request and then adjust the following:</p><ol><li>Change the method.</li><li>Update labels and descriptions.</li><li>Copy the parameter specs from the GET request.</li><li>Update response codes.</li></ol><p>Here&apos;s what the full PUT request should look like.</p><pre><code class="language-yaml">  /definitions/{word}:
    put:
      summary: Update a word&apos;s definition
      operationId: update_definition
      description: &gt;
        PUT /definitions updates a definition of a word.
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      requestBody:
        content:
          application/json:
            schema:
              $ref: &apos;#/components/schemas/Definition&apos;
        required: true
      responses:
        &apos;204&apos;:
          description: Word updated successfully.
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;400&apos;:
          description: Invalid definition data provided.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;404&apos;:
          description: Word not found.
        &apos;422&apos;:
          description: Unprocessable entity.</code></pre><h3 id="delete-a-definition">Delete a Definition</h3><p>Setting the DELETE request is even more trivial than the PUT request. In this case, we copy the GET request and update the following:</p><ol><li>Change the method.</li><li>Update labels and descriptions.</li><li>Add Basic Auth.</li><li>Update response codes.</li></ol><p>Here&apos;s what we&apos;re left with.</p><pre><code class="language-yaml">  /definitions/{word}:
    delete:
      summary: Delete a word&apos;s definition
      operationId: delete_definition
      description: &gt;
        DELETE /definitions/{word} deletes a definition of a word.
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      responses:
        &apos;204&apos;
          description: Word deleted successfully.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;404&apos;:
          description: Word not found.</code></pre><h2 id="conclusion">Conclusion</h2><p>Congratulations. You&apos;ve created an OpenAPI document the hard way. Of course, you&apos;re not going to want to do this a regular exercise. Now that you have a foundation of how OpenAPI works, you&apos;re better prepared to use tools like <a href="https://stoplight.io/?ref=abarron.com">Stoplight</a> to simplify generating an OpenAPI file while also benefitting from their linter.</p><p>Finally, here&apos;s the full spec we created in a single code block:</p><pre><code class="language-yaml">openapi: 3.0.0
info:
  title: Dictionary REST API
  description: An API to look up definitions of words.
  version: 1.0.0
servers:
  - url: &apos;https://sample-api.apimaestro.com&apos;
components:
  securitySchemes:
    basicAuth:
      type: http
      scheme: basic
  schemas:
    Definition:
      title: Definition
      description: A definition is a statement that explains the meaning of a word or phrase.
      type: object
      properties:
        id:
          type: integer
          example: 40125
          readOnly: true
        word:
          type: string
          example: tagliatelle
        created_at:
          type: string
          format: date-time
          example: &apos;2022-12-06T04:55:36.887Z&apos;
          readOnly: true
        updated_at:
          type: string
          format: date-time
          example: &apos;2022-12-06T04:55:36.887Z&apos;
          readOnly: true
        meanings:
          type: array
          items:
            type: object
            properties:
              id:
                type: integer
                example: 333
                readOnly: true
              part_of_speech:
                type: string
                example: noun
              meaning:
                type: string
                example: &apos;A type of pasta shaped into long, thin, flat strips&apos;
paths:
  /definitions/{word}:
    get:
      summary: Get a word&apos;s definition.
      operationId: get_definition
      description: |
        GET /definitions/{word} accepts a word and returns its meaning. The endpoint returns multiple meanings if available.
      parameters:
        - name: word
          in: path
          description: Word to look up.
          schema:
            type: string
          required: true
          example: tortellini
      responses:
        &apos;200&apos;:
          description: OK
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;404&apos;:
          description: No definition found for the specified word.
    put:
      summary: Update a word&apos;s definition
      operationId: update_definition
      description: |
        PUT /definitions updates a definition of a word. Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      requestBody:
        content:
          application/json:
            schema:
              $ref: &apos;#/components/schemas/Definition&apos;
        required: true
      responses:
        &apos;204&apos;:
          description: Word updated successfully.
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;400&apos;:
          description: Invalid definition data provided.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;404&apos;:
          description: Word not found.
        &apos;422&apos;:
          description: Unprocessable entity.
    delete:
      summary: Delete a word&apos;s definition
      operationId: delete_definition
      description: |
        DELETE /definitions/{word} deletes a definition of a word. Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      parameters:
        - name: word
          in: path
          description: Word to look up
          schema:
            type: string
          required: true
          example: tortellini
      responses:
        &apos;204&apos;:
          description: Word deleted successfully.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;404&apos;:
          description: Word not found.
  /definitions:
    post:
      summary: Create a word&apos;s definition
      operationId: create_definition
      description: &gt;
        POST /definitions creates a definition of a word. 
        Requests to create a duplicate definitions for a word will be rejected with a 409 response code. 
        Requests must be authorized with Basic Auth.
      security:
        - basicAuth: []
      requestBody:
        content:
          application/json:
            schema:
              $ref: &apos;#/components/schemas/Definition&apos;
        required: true
      responses:
        &apos;201&apos;:
          description: Word created successfully.
          content:
            application/json:
              schema:
                $ref: &apos;#/components/schemas/Definition&apos;
        &apos;400&apos;:
          description: Invalid definition data provided.
        &apos;401&apos;:
          description: Unauthorized request.
        &apos;409&apos;:
          description: Word already has a definition.
        &apos;422&apos;:
          description: Unprocessable entity.
    parameters: []
</code></pre>]]></content:encoded></item><item><title><![CDATA[Getting Started with Pocket Bitcoin's API]]></title><description><![CDATA[<p>Pocket Bitcoin is a fantastic place to buy Bitcoin if you live in Europe. In addition to being a great place to buy Bitcoin, they also offer API access to developers who want to integrate their products with Pocket. This unlocks a new revenue stream for you through their affiliate</p>]]></description><link>https://abarron.com/getting-started-with-pocket-bitcoins-api/</link><guid isPermaLink="false">63c528c12a80001955d223a3</guid><category><![CDATA[Guides]]></category><category><![CDATA[Bitcoin]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Mon, 24 Apr 2023 13:01:32 GMT</pubDate><content:encoded><![CDATA[<p>Pocket Bitcoin is a fantastic place to buy Bitcoin if you live in Europe. In addition to being a great place to buy Bitcoin, they also offer API access to developers who want to integrate their products with Pocket. This unlocks a new revenue stream for you through their affiliate program and gives your users a smooth, KYC-light purchase experience within a product they&apos;re already using. </p><p>If you&apos;ve never used Pocket before, I recommend trying a purchase through <a href="https://pocketbitcoin.com/?ref=abarron.com">their site</a>. The purchase flow through the API is similar to the UI flow and it&apos;s helpful to have a foundation of how their product works.</p><h2 id="access">Access</h2><p>Pocket requires developers authenticate their API calls with a client ID so your first step is to get a client ID. Head over to Pocket&apos;s <a href="https://pocketbitcoin.com/contact?ref=abarron.com">Contact page</a> and explain why you want API access.</p><p>The team will reply back soon with your client ID or additional questions about your request. Assuming they approve your request, they will send credentials and configure a temporary webhooks receiver. I&apos;ll explain more about webhooks later in this guide.</p><p>Before we move on, make sure your client ID is working with your first request to their API. Their API requires an &apos;Authorization&apos; header with the value containing the prefix &apos;client_id&apos; followed by your client ID.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">curl &quot;https://api.pocketbitcoin.com&quot; \
  -H &quot;Authorization: client_id YOUR_CLIENT_ID&quot;
</code></pre>
<!--kg-card-end: markdown--><p>I recommend adding your client ID to an environment variable which will allow you to copy and paste the curl commands in this guide without having to manually add your client ID each time. Run the following in your terminal, adding your actual client ID where <code>YOUR_CLIENT_ID</code> is.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">export POCKET_CLIENT_ID=YOUR_CLIENT_ID
</code></pre>
<!--kg-card-end: markdown--><p>Test your environment variable.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">echo $POCKET_CLIENT_ID
</code></pre>
<!--kg-card-end: markdown--><p>If your terminal responds with your client ID, you can move on to testing an API call.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">curl &quot;https://api.pocketbitcoin.com&quot; \
    -H &quot;Authorization: client_id &quot;$POCKET_CLIENT_ID&quot;&quot;
</code></pre>
<!--kg-card-end: markdown--><p>If you get a 401 Unauthorized error, double check your environment variable was set correctly. Carry on if you can&apos;t figure it out and add the client ID manually.</p><h2 id="resources">Resources</h2><p>The API has three key resources to understand.</p><ol><li><strong>Challenge</strong>: A randomly generated token used in a signed message confirming ownership over a Bitcoin address. Challenges can be only used once and expire after 24 hours. These can be created and retrieved through the API.</li><li><strong>Order</strong>: A set of payment and payout instructions to be used in a Bitcoin and fiat exchange. Orders can be used indefinitely for multiple purchases. These can be created, updated (with limitations), and retrieved through the API.</li><li><strong>Exchange</strong>: A swap event of Bitcoin and fiat currency. Exchanges are created automatically when a user follows the payment instructions in an Order. Thus, these can only be retrieved through the API. &#xA0;</li></ol><h2 id="high-level-flow">High Level Flow</h2><p>Hopefully you had a chance to try a purchase on their site. Here&apos;s a high level overview of how purchases work through the API. Some details are left out to avoid too much complexity too soon.</p><!--kg-card-begin: html--><ol>
    <li>App creates a challenge token with Pocket Bitcoin.</li>
    <li>App passes challenge token to User.</li>
    <li>User provides the following to App:</li>
    <ul>
        <li>Signed message with challenge token</li>
        <li>User&apos;s Bitcoin address</li>
        <li>User&apos;s IBAN</li>
        <li>User&apos;s currency; EUR or CHF</li>
    </ul>
    <li>App sends the above details to Pocket Bitcoin to create an order.</li>
    <li>Pocket Bitcoin responds with an order containing the following:</li>
    <ul>
        <li>Pocket Bitcoin&apos;s IBAN</li>
        <li>Payment reference number</li>
    </ul>
    <li>App provides order details to User and requests them to make payment to Pocket Bitcoin.</li>
    <li>User sends payment to Pocket Bitcoin and includes the payment reference number in the payment description.</li>
    <li>Pocket Bitcoin creates an exchange once it sees the payment and begins sending status webhooks to App.</li>
    <li>Pocket Bitcoin sends Bitcoin to User at the end of the day.</li>
</ol><!--kg-card-end: html--><h2 id="creating-a-challenge">Creating a Challenge</h2><p>The first call we&apos;ll make is a POST to /v1/challenges to generate a random token. This POST request does not take any data in its request body. You can leave it out of your request if you want.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">curl -X POST &quot;https://api.pocketbitcoin.com/v1/challenges&quot; \
    -H &quot;Authorization: client_id &quot;$POCKET_CLIENT_ID&quot;&quot; \
    -H &quot;Content-Type: application/json&quot; \
    --data-raw &apos;{}&apos;
</code></pre>
<!--kg-card-end: markdown--><p>We should receive the following response:</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
    &quot;id&quot;: &quot;d7a2907b-3921-4bdc-9922-67e0bafa8187&quot;,
    &quot;token&quot;: &quot;qf3GFbmi&quot;,
    &quot;expires_on&quot;: &quot;2022-12-07T15:31:53.259Z&quot;,
    &quot;completed_on&quot;: null
}
</code></pre>
<!--kg-card-end: markdown--><p>Great. We have our token and now we can create a signed message.</p><h2 id="signing-message">Signing Message</h2><p>Before we can create an order, we need to create a signed message using the token from the previous step. The message can say anything, but it must contain a valid token. Pocket&apos;s docs provide this sample message that will work for our purposes too: &quot;I confirm my bitcoin wallet. [qf3GFbmi]&quot;</p><p>For testing purposes, I recommend setting up a wallet in <a href="https://www.sparrowwallet.com/?ref=abarron.com">Sparrow</a> or <a href="https://electrum.org/?ref=abarron.com#home">Electrum</a>. These wallets make it simple to sign a message with a simple UI tool. See <a href="https://abarron.com/signing-messages-with-sparrow/">my guide on signing messages in Sparrow</a> if you need help. My guide assumes you&apos;re using a hardware wallet as a signing device, but if you&apos;re just testing, you can create a hot wallet through Sparrow or Electrum to sign messages.</p><p>Navigate to the &apos;Sign/Verify Message&apos; option under &apos;Tools&apos; and paste the Bitcoin address you want to receive to and your messsage.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/12/signing_message-1.png" class="kg-image" alt loading="lazy" width="572" height="549"></figure><p>Hit the &apos;Sign&apos; button and the &apos;Signature&apos; field will be populated with the signature we&apos;ll need to create an order. Keep this window open. We&apos;ll need the address, message, and signature in the next step.</p><h2 id="creating-an-order">Creating an Order</h2><p>In addition to the 3 items mentioned above, you&apos;ll also need to decide the following:</p><ol><li>A fee rate ranging 1.2% to 4.5% in float form (e.g. <code>0.015</code> for 1.5%). I&apos;ve set the fee to 1.2% in the sample below.</li><li>Currency you&apos;ll pay with; <code>EUR</code> or <code>CHF</code> only.</li><li>IBAN for the bank account you&apos;ll pay Pocket with.</li></ol><p>Replace the values in the sample below with yours. All fields you need to add are prefixed with <code>YOUR_...</code>.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">curl -X POST &quot;https://api.pocketbitcoin.com/v1/orders&quot; \
    -H &quot;Authorization: client_id &quot;$POCKET_CLIENT_ID&quot;&quot; \
    -H &quot;Content-Type: application/json&quot; \
    --data-raw &apos;{
        &quot;active&quot;: true,
        &quot;fee_rate&quot;: 0.012,
        &quot;payment_method&quot;: {
            &quot;currency&quot;: &quot;YOUR_CURRENCY&quot;,
            &quot;debitor_iban&quot;: &quot;YOUR_IBAN&quot;
        },
        &quot;payout_method&quot;: {
            &quot;bitcoin_address&quot;: &quot;YOUR_BITCOIN_ADDRESS&quot;,
            &quot;message&quot;: &quot;I confirm my bitcoin wallet. [YOUR_TOKEN]&quot;,
            &quot;signature&quot;: &quot;YOUR_SIGNATURE&quot;
        }
    }&apos;
</code></pre>
<!--kg-card-end: markdown--><p>If you become a Pocket affiliate partner, you would also include an <code>affiliate_id</code> field to track the customers you send their way.</p><p>A successful response will return 201 Created and a response body containing the details to make payment.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
  &quot;active&quot;: true,
  &quot;created_on&quot;: &quot;2022-12-07T15:32:59.211Z&quot;,
  &quot;fee_rate&quot;: 0.012,
  &quot;payment_method&quot;: {
    &quot;currency&quot;: &quot;eur&quot;,
    &quot;debitor_iban&quot;: &quot;DE75512108001245126199&quot;,
    &quot;creditor_reference&quot;: &quot;RF18GW8K79&quot;,
    &quot;creditor_iban&quot;: &quot;CH9400778214768302002&quot;,
    &quot;creditor_bank_name&quot;: &quot;Luzerner Kantonalbank&quot;,
    &quot;creditor_bank_street&quot;: &quot;Pilatusstrasse 12&quot;,
    &quot;creditor_bank_postal_code&quot;: &quot;6002&quot;,
    &quot;creditor_bank_town&quot;: &quot;Luzern&quot;,
    &quot;creditor_bank_country&quot;: &quot;CH&quot;,
    &quot;creditor_bank_bic&quot;: &quot;LUKBCH2260A&quot;,
    &quot;creditor_name&quot;: &quot;Pocket App&quot;,
    &quot;creditor_street&quot;: &quot;Industriestrasse 33&quot;,
    &quot;creditor_postal_code&quot;: &quot;5242&quot;,
    &quot;creditor_town&quot;: &quot;Lupfig&quot;,
    &quot;creditor_country&quot;: &quot;CH&quot;,
    &quot;swiss_qr_bill_payload&quot;: null
  },
  &quot;payout_method&quot;: {
    &quot;bitcoin_address&quot;: &quot;bc1q5a7uxjq6z5l2wguzu77rzmzk6tyx24yvq5r4fj&quot;,
    &quot;message&quot;: &quot;I confirm my bitcoin wallet. [qf3GFbmi]&quot;,
    &quot;signature&quot;: &quot;IDoGQ1yyj257K5c/eroZcn1zWg1QULuqo/ALqjkRFEfZE8CA8ANp0+IZ2KpQz2PUhn+ryUy4JWI7J+VbYz/aXOM=&quot;
  }
}
</code></pre>
<!--kg-card-end: markdown--><p>You can look up this order at any time through the get order endpoint. You can also update the order&apos;s affiliate ID, fee rate, and active status through the patch endpoint. See <a href="https://pocketbitcoin.com/developers/docs/rest/v1/resources?ref=abarron.com#get-order">Pocket&apos;s docs</a> for examples of those.</p><h2 id="making-payment">Making Payment</h2><p>Payment must be done from your banking app to Pocket&apos;s bank account provided in the order response body. You can theoretically send any amount but it&apos;s advisable to send a small amount first. For a large amount, Pocket suggests contacting them first.</p><p>It&apos;s crucial you include the payment reference number in your payment description to Pocket. This is found in the <code>creditor_reference</code> field. <code>RF18GW8K79</code> in the sample response above. This field is used by Pocket to link your fiat payment to the order and send the payout to the right Bitcoin address.</p><h2 id="exchanges">Exchanges</h2><h3 id="webhooks">Webhooks</h3><p>Pocket uses webhooks to keep developers up to date on exchanges. Listening for webhooks is crucial to any integration because there is no other way to get updates on exchanges. There is an endpoint to fetch exchanges, but if you&apos;re not listening for exchange webhooks, you won&apos;t know the exchange ID needed to look it up. Once you have the ID, you can of course query the exchange endpoint. Hopefully Pocket will soon add an endpoint to search for exchanges with query params such as date ranges or by order ID.</p><p>Pocket will provision a temporary webhooks endpoint for you when you first get API credentials. See the webhooks.site link they sent you to watch for webhooks.</p><p>Contact the Pocket team once you have your own webhook endpoint configured to rotate where you receive notifications.</p><p>Pocket will send a signature header along with every webhook. If you&apos;re building a real production application, use this to verify the webhook really came from Pocket. More information on this is available <a href="https://pocketbitcoin.com/developers/docs/rest/v1/webhooks?ref=abarron.com#signature-validation">here</a>.</p><h3 id="executed-exchange">Executed Exchange</h3><p>Pocket will send an executed exchange webhook once they receive your fiat payment. See the <code>rate</code> field for the Bitcoin price you got.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;id&quot;: &quot;a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot;,
  &quot;order_id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
  &quot;fee_rate&quot;: 0.012,
  &quot;pair&quot;: &quot;btc/eur&quot;,
  &quot;type&quot;: &quot;buy&quot;,
  &quot;cost&quot;: 100.00,
  &quot;executed_on&quot;: &quot;2022-12-07T15:32:59.211Z&quot;,
  &quot;amount&quot;: 0.00494000,
  &quot;rate&quot;: 20000.00,
  &quot;fee&quot;: 1.20,
  &quot;action&quot;: null,
  &quot;reason&quot;: null,
  &quot;payout&quot;: null
}
</code></pre>
<!--kg-card-end: markdown--><h3 id="settled-exchange">Settled Exchange</h3><p>Pocket will send a settled exchange webhook when they have paid out to your Bitcoin address. Exchanges settle at the end of the day in which Pocket received your fiat payment. This is usually around 10pm Central European Time.</p><p>The settled exchange webhook will be the same as above except the <code>payout</code> field will be populated with details on the payout transaction.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;id&quot;: &quot;a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot;,
  &quot;order_id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
  &quot;fee_rate&quot;: 0.012,
  &quot;pair&quot;: &quot;btc/eur&quot;,
  &quot;type&quot;: &quot;buy&quot;,
  &quot;cost&quot;: 100.00,
  &quot;executed_on&quot;: &quot;2022-12-07T15:32:59.211Z&quot;,
  &quot;amount&quot;: 0.00494000,
  &quot;rate&quot;: 20000.00,
  &quot;fee&quot;: 1.20,
  &quot;action&quot;: null,
  &quot;reason&quot;: null,
  &quot;payout&quot;: {
    &quot;txid&quot;: &quot;c01c7a0fd270aa3876119098c0e2d51687a795efab99787e214d8a93ab9f8342&quot;,
    &quot;outpoint&quot;: 1,
    &quot;bitcoin_address&quot;: &quot;bc1q5a7uxjq6z5l2wguzu77rzmzk6tyx24yvq5r4fj&quot;,
    &quot;derivation_path&quot;: null,
    &quot;block_height&quot;: null,
    &quot;confirmed&quot;: false,
    &quot;fee&quot;: 0.00000190,
    &quot;amount&quot;: 0.00049381
  }
}
</code></pre>
<!--kg-card-end: markdown--><h3 id="interrupted-exchange">Interrupted Exchange</h3><p>An interrupted exchange means the exchange is blocked and you must take a particular action in order to unblock it. For example, if you try to buy a very large amount of Bitcoin, you may trigger a request for identification. See the <code>action</code> field.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;event&quot;: &quot;exchange.interrupted&quot;,
  &quot;payload&quot;: {
    &quot;exchange_id&quot;: &quot;a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot;,
    &quot;order_id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
    &quot;fee_rate&quot;: 0.012,
    &quot;pair&quot;: &quot;btc/eur&quot;,
    &quot;type&quot;: &quot;buy&quot;,
    &quot;cost&quot;: 100.00,
    &quot;executed_on&quot;: null,
    &quot;amount&quot;: null,
    &quot;rate&quot;: null,
    &quot;fee&quot;: null,
    &quot;action&quot;: {
      &quot;code&quot;: &quot;identification_required&quot;,
      &quot;identification_id&quot;: &quot;7bf967b5-3d34-4cd6-936c-6631811401d2&quot;
    },
    &quot;reason&quot;: null,
    &quot;payout&quot;: null
  }
}
</code></pre>
<!--kg-card-end: markdown--><h3 id="refunded-exchange">Refunded Exchange</h3><p>This one is fairly simple. You&apos;ll get this notification if a fiat payment had to be refunded. There are several potential refund reasons. The example below shows a refund due to <code>threshold_exceeded</code> in the <code>reason</code> field. The full list is available in <a href="https://pocketbitcoin.com/developers/docs/rest/v1/webhooks?ref=abarron.com#refund-reasons">Pocket&apos;s documentation</a>.</p><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;event&quot;: &quot;exchange.refunded&quot;,
  &quot;payload&quot;: {
    &quot;exchange_id&quot;: &quot;a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot;,
    &quot;order_id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
    &quot;fee_rate&quot;: 0.012,
    &quot;pair&quot;: &quot;btc/eur&quot;,
    &quot;type&quot;: &quot;buy&quot;,
    &quot;cost&quot;: 100.00,
    &quot;executed_on&quot;: null,
    &quot;amount&quot;: null,
    &quot;rate&quot;: null,
    &quot;fee&quot;: null,
    &quot;action&quot;: null,
    &quot;reason&quot;: {
      &quot;code&quot;: &quot;threshold_exceeded&quot;
    },
    &quot;payout&quot;: null
  }
}
</code></pre>
<!--kg-card-end: markdown--><h3 id="fetching-an-exchange">Fetching an Exchange</h3><p>As mentioned previously, you can retrieve exchanges through the API if you have the exchange&apos;s ID.</p><!--kg-card-begin: markdown--><pre><code class="language-shell">curl -X GET &quot;https://api.pocketbitcoin.com/v1/exchanges/a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot; \
    -H &quot;Authorization: client_id &quot;$POCKET_CLIENT_ID&quot;&quot;
</code></pre>
<!--kg-card-end: markdown--><p>The response won&apos;t explicitly tell you the status of the exchange. Based on what fields are populated, you can understand the state of it.</p><!--kg-card-begin: markdown--><ul>
<li>Executed if the following fields are null: <code>action</code>, <code>reason</code>, and <code>payout</code>.</li>
<li>Settled if <code>payout</code> is populated.</li>
<li>Interrupted if <code>action</code> is populated.</li>
<li>Refunded if <code>reason</code> is populated.</li>
</ul>
<!--kg-card-end: markdown--><!--kg-card-begin: markdown--><pre><code class="language-json">{
  &quot;id&quot;: &quot;a2d75998-5ee5-4501-a4cf-4e3788732b7a&quot;,
  &quot;order_id&quot;: &quot;64decab6-4129-4fe7-9f6e-1db68283f5ce&quot;,
  &quot;fee_rate&quot;: 0.012,
  &quot;pair&quot;: &quot;btc/eur&quot;,
  &quot;type&quot;: &quot;buy&quot;,
  &quot;cost&quot;: 100.00,
  &quot;executed_on&quot;: &quot;2022-12-07T15:32:59.211Z&quot;,
  &quot;amount&quot;: 0.00494000,
  &quot;rate&quot;: 20000.00,
  &quot;fee&quot;: 1.20,
  &quot;action&quot;: null,
  &quot;reason&quot;: null,
  &quot;payout&quot;: null
}
</code></pre>
<!--kg-card-end: markdown--><h2 id="what-next">What Next?</h2><p>That wraps up my guide on Pocket&apos;s API. Check out the following resources if you&apos;d like to learn more or start playing around with their API.</p><ol><li><a href="https://pocketbitcoin.com/developers/docs/rest/v1?ref=abarron.com">Pocket&apos;s API Docs</a></li><li><a href="https://www.postman.com/collections/d0efc82bffc4cdec3712?ref=abarron.com">Pocket&apos;s Postman Collection</a></li><li><a href="https://github.com/alexbarron/pocket_bitcoin_openapi?ref=abarron.com">My OpenAPI Config for Pocket</a> (use this to auto-generate an API client library in any language you want)</li></ol>]]></content:encoded></item><item><title><![CDATA[What is OpenAPI?]]></title><description><![CDATA[<figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2023/01/image.png" class="kg-image" alt loading="lazy" width="1759" height="467" srcset="https://abarron.com/content/images/size/w600/2023/01/image.png 600w, https://abarron.com/content/images/size/w1000/2023/01/image.png 1000w, https://abarron.com/content/images/size/w1600/2023/01/image.png 1600w, https://abarron.com/content/images/2023/01/image.png 1759w" sizes="(min-width: 720px) 720px"></figure><p>OpenAPI is a standardized technical specification to make describing the capabilities of the API unambiguous for both humans and machines. Why this is desirable requires a brief tangent on APIs and the problem of asymmetric information. </p><p>APIs are like contracts. The terms of the contract must be clear and unambiguous</p>]]></description><link>https://abarron.com/what-is-openapi/</link><guid isPermaLink="false">63c528a32a80001955d2239b</guid><category><![CDATA[OpenAPI]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Wed, 19 Apr 2023 15:37:15 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2023/01/image.png" class="kg-image" alt loading="lazy" width="1759" height="467" srcset="https://abarron.com/content/images/size/w600/2023/01/image.png 600w, https://abarron.com/content/images/size/w1000/2023/01/image.png 1000w, https://abarron.com/content/images/size/w1600/2023/01/image.png 1600w, https://abarron.com/content/images/2023/01/image.png 1759w" sizes="(min-width: 720px) 720px"></figure><p>OpenAPI is a standardized technical specification to make describing the capabilities of the API unambiguous for both humans and machines. Why this is desirable requires a brief tangent on APIs and the problem of asymmetric information. </p><p>APIs are like contracts. The terms of the contract must be clear and unambiguous to all interested parties. An API provider creates these terms that dictate how a consumer interacts with the API. If the consumer follows the terms correctly, they&apos;ll get what they want. If they don&apos;t, someone will end up disappointed.</p><p>The trouble for the API consumer is source code is generally not accessible to them. Even if it was, it wouldn&apos;t necessarily be easy to understand. Instead, they depend on documentation or client libraries that are more like proxy contracts of the real contract. It&apos;s like asking your lawyer for a contract and their assistant copies it out by hand for you.</p><p>This problem is exacerbated as more parties become involved in managing and using the API; both internally and externally.</p><p>APIs encounter numerous problems trying to keep everyone on the same page.</p><ul><li>The engineer didn&apos;t implement the code and tests according to the design doc.</li><li>The technical writer overlooked something while preparing documentation.</li><li>The API consumer didn&apos;t integrate with the API correctly.</li></ul><p>The poor support engineer is now stuck trying to understand if there&apos;s a bug in the code, the documentation, or the client/partner&apos;s code.</p><h3 id="how-the-openapi-specification-helps">How the OpenAPI Specification Helps</h3><p>The OpenAPI Specification (formerly Swagger) aims to solve all these problems and more by offering a standard, unambiguous way to describe REST APIs in a machine-readable YAML or JSON file. </p><p>This file is called an OpenAPI document and acts as your API&apos;s contract and source of truth for all interested parties to streamline development, documentation, and reduce errors. Using this document, you can do all of the following without having to write any code:</p><ol><li>Auto-generate documentation in multiple file formats.</li><li>Auto-generate SDKs/client libraries in any programming language.</li><li>Create mock servers.</li><li>Generate and run test suites of your API.</li></ol><p>Focusing your development efforts on an OpenAPI document encourages a design-first approach. OpenAPI documents allow you to design your API in a simple way right from the start without getting lost in code or a lengthy design doc no one wants to read. </p><p>Instead, you write a bit of YAML or JSON to define your API endpoints, schemas, requests, and responses in mere minutes. Then use that source file to generate mock servers and documentation to review with all your API stakeholders. </p><p>As someone who has read a lot of confusing API design docs, this is a game changer. There&apos;s no comparison to being able to actually see what the documentation will look like so early on and to make mock requests during the early design phase.</p><p>Since OpenAPI documents are not, strictly speaking, code, it also enables less technical stakeholders to more fully contribute as reviewers and to propose changes.</p><h3 id="openapi-ecosystem-tools">OpenAPI Ecosystem &amp; Tools</h3><p>The OpenAPI ecosystem is very active and supported by many of the largest companies in the space through the <a href="https://www.openapis.org/?ref=abarron.com">OpenAPI Initiative</a>. It is a Linux Foundation Collaborative Project and strong proponent of open source.</p><p>The developer community around OpenAPI is also excellent. The number of tools available to support usage of OpenAPI is truly staggering. </p><p><a href="https://openapi.tools/?ref=abarron.com">OpenAPI.Tools</a> has the most comprehensive list of OpenAPI tools as far as I can tell. They&apos;re an excellent repository to find tools such as</p><ul><li>Auto Generators: Convert your code into OpenAPI documents.</li><li>Converters: Convert to and from OpenAPI and other API formats.</li><li>Description Validators: Validate your OpenAPI document follows the OpenAPI Specification.</li><li>GUI Editors: Visual editors to create OpenAPI documents.</li><li>Mock Servers: Fake servers that respond to requests with examples set in your OpenAPI document.</li><li>SDK Generators: Generate full client libraries in any language to make requests to your API.</li><li>Text Editors: Get visual feedback while you write an OpenAPI document.</li></ul><h3 id="where-to-start-with-openapi">Where to Start with OpenAPI</h3><p>OpenAPI can do so much it&apos;s not always clear where to start. I recommend starting by downloading the classic OpenAPI <a href="https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml?ref=abarron.com">example document of a pet store AP</a>I. Or if you already have a project in Postman, try converting it to OpenAPI with <a href="https://www.npmjs.com/package/postman-to-openapi?ref=abarron.com">this Node package</a>.</p><p>Then try generating HTML docs or a client library in your favorite programming language using the <a href="https://github.com/OpenAPITools/openapi-generator?ref=abarron.com#2---getting-started">OpenAPI command line generator</a>.</p><p>Otherwise, go through the long list of <a href="https://openapi.tools/?ref=abarron.com">OpenAPI tools</a> and see what catches your attention.</p>]]></content:encoded></item><item><title><![CDATA[Accept Your First Bitcoin Payment in 5 Minutes with BTCPay & Voltage]]></title><description><![CDATA[<p>Accepting Bitcoin as payment or as a donation on your website seems like it would be a particularly thorny technical problem for a non-technical person. You might be surprised, however, just how simple it is. Of course if you&apos;re expecting to receive a significant volume of Bitcoin on</p>]]></description><link>https://abarron.com/accept-your-first-bitcoin-payment-in-5-minutes-with-btcpay-voltage/</link><guid isPermaLink="false">65ae37ac56f43f07294f2956</guid><category><![CDATA[Guides]]></category><category><![CDATA[Bitcoin]]></category><category><![CDATA[Voltage]]></category><category><![CDATA[Payments]]></category><category><![CDATA[BTCPay]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Fri, 18 Nov 2022 09:29:57 GMT</pubDate><content:encoded><![CDATA[<p>Accepting Bitcoin as payment or as a donation on your website seems like it would be a particularly thorny technical problem for a non-technical person. You might be surprised, however, just how simple it is. Of course if you&apos;re expecting to receive a significant volume of Bitcoin on your website, you&apos;ll need to consider accounting and tax issues. I&apos;m here to tell you that at least the technical part is less complex than you probably imagined. </p><p>In this tutorial, I&apos;ll walk through how to add a simple Bitcoin donation button to your website in only five minutes. This implementation can also be used with minor tweaks to accept payments for freelance work or even selling digital or physical products. Recurring subscription payments won&apos;t easily fit this model.</p><p>We&apos;ll be using two services to implement our payment interface.</p><ol><li><strong>Voltage</strong>: Simple to use, hosted Bitcoin infrastructure with plug-and-play, open source software. Available as a 7 day free trial and then $0.012/hour which works out to about $8.76/month.</li><li><strong>BTCPay</strong>: A self-hosted, open source cryptocurrency payment processor. It&apos;s secure, private, censorship-resistant and free.</li></ol><h2 id="set-up-voltage">Set Up Voltage</h2><p><br>First, we need to create an account and node on <a href="https://account.voltage.cloud/register?ref=abarron.com">Voltage</a>. Once you&apos;ve registered, you&apos;ll be presented with the following screen. Hit &apos;Nodes&apos;. </p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/01_post_registration.png" class="kg-image" alt loading="lazy" width="607" height="804" srcset="https://abarron.com/content/images/size/w600/2022/11/01_post_registration.png 600w, https://abarron.com/content/images/2022/11/01_post_registration.png 607w"></figure><p>And then &apos;Create Node&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/02_create_node.png" class="kg-image" alt loading="lazy" width="301" height="157"></figure><p>We&apos;ll piggyback off of Voltage&apos;s Bitcoin Core node allowing us to go straight to choosing BTCPay Server.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/03_deploy_btcpayserver.png" class="kg-image" alt loading="lazy" width="687" height="320" srcset="https://abarron.com/content/images/size/w600/2022/11/03_deploy_btcpayserver.png 600w, https://abarron.com/content/images/2022/11/03_deploy_btcpayserver.png 687w"></figure><p>For the moment, BTCPay Server is only available in the US West region. Hopefully they&apos;ll add other regions soon. For now, confirm the cost looks acceptable to you and then carry on to deploy your BTCPay Server.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/04_deployment_location_and_cost.png" class="kg-image" alt loading="lazy" width="598" height="372"></figure><p>Give your store a name. Ignore the text about Lightning nodes unless you&apos;re running a Lightning node on Voltage.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/name_store.png" class="kg-image" alt loading="lazy" width="427" height="361"></figure><p>Easy. Your server has been created and deployed. Store your credentials presented here, but note that the first thing we&apos;ll do once we&apos;re in the BTCPay dashboard is change your password. Proceed to login to continue.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/05_account_created-3.png" class="kg-image" alt loading="lazy" width="674" height="417" srcset="https://abarron.com/content/images/size/w600/2022/11/05_account_created-3.png 600w, https://abarron.com/content/images/2022/11/05_account_created-3.png 674w"></figure><p>Before we get to the fun part, let&apos;s change that default password. Navigate to the bottom left corner of the sidebar and click &apos;Account&apos; and then &apos;Manage Account&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/07_account_menu-1.png" class="kg-image" alt loading="lazy" width="265" height="474"></figure><p>Pop over to &apos;Password&apos; and pick a long, secure password.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/08_change_password.png" class="kg-image" alt loading="lazy" width="810" height="552" srcset="https://abarron.com/content/images/size/w600/2022/11/08_change_password.png 600w, https://abarron.com/content/images/2022/11/08_change_password.png 810w" sizes="(min-width: 720px) 720px"></figure><p>In the sidebar, click &apos;Bitcoin&apos; under &apos;Wallets&apos; to create or link an existing wallet. It&apos;s more secure to connect an existing wallet because creating a new wallet on BTCPay creates a hot wallet that can more easily be hacked. We&apos;ll be following the steps to connect an existing wallet.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/09_choose_wallet.png" class="kg-image" alt loading="lazy" width="799" height="572" srcset="https://abarron.com/content/images/size/w600/2022/11/09_choose_wallet.png 600w, https://abarron.com/content/images/2022/11/09_choose_wallet.png 799w" sizes="(min-width: 720px) 720px"></figure><p>Several import methods exist. In my opinion, entering an extended public key is the simplest if you&apos;re already using a hardware wallet with software that exposes the xpub/zpub easily such as Sparrow. This will route your funds directly to cold storage. Consider creating a new account in your wallet software with a descriptive name related to BTCPay or your website. Make sure you give BTCPay the xpub/zpub from the newly created account instead of your other accounts.</p><p>My second choice would be connecting a hardware wallet. This will require a bit of extra time in order to download and setup BTCPay&apos;s Vault software. Follow the steps for that <a href="https://docs.btcpayserver.org/HardwareWalletIntegration/?ref=abarron.com">here</a>.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/10_add_existing_wallet.png" class="kg-image" alt loading="lazy" width="792" height="797" srcset="https://abarron.com/content/images/size/w600/2022/11/10_add_existing_wallet.png 600w, https://abarron.com/content/images/2022/11/10_add_existing_wallet.png 792w" sizes="(min-width: 720px) 720px"></figure><p>Paste your xpub/zpub if you&apos;re following that option.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/11_enter_xpub.png" class="kg-image" alt loading="lazy" width="772" height="357" srcset="https://abarron.com/content/images/size/w600/2022/11/11_enter_xpub.png 600w, https://abarron.com/content/images/2022/11/11_enter_xpub.png 772w" sizes="(min-width: 720px) 720px"></figure><p>Assuming you copied your xpub/zpub correctly, the next page will list your first 10 addresses calculated off that public key. Be sure to check this against your wallet software. This is very important. Badly entered public keys will cause Bitcoin to &#xA0;go to addresses you don&apos;t control.</p><p>If you use Sparrow, check the &apos;Addresses&apos; menu to compare that list to this one. Other wallet applications should have similar lists with your addresses.</p><p>If not, you can check the addresses from past transactions(must be the first 10 ever used). If it&apos;s a new wallet or account, use the receive functionality of your hardware wallet to generate an address and ensure it&apos;s in the list BTCPay generated.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/12_confirm_addresses.png" class="kg-image" alt loading="lazy" width="765" height="152" srcset="https://abarron.com/content/images/size/w600/2022/11/12_confirm_addresses.png 600w, https://abarron.com/content/images/2022/11/12_confirm_addresses.png 765w" sizes="(min-width: 720px) 720px"></figure><p>Next, we&apos;ll create an app that will function as our payment receiver. In the sidebar, click &apos;New App&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/13_new_app.png" class="kg-image" alt loading="lazy" width="248" height="66"></figure><p>Keep the default &apos;App Type&apos; of &apos;Point of Sale&apos; and add a name for this app. This name doesn&apos;t get displayed to the user.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/14_create_new_app.png" class="kg-image" alt loading="lazy" width="806" height="305" srcset="https://abarron.com/content/images/size/w600/2022/11/14_create_new_app.png 600w, https://abarron.com/content/images/2022/11/14_create_new_app.png 806w" sizes="(min-width: 720px) 720px"></figure><p>Enter a &apos;Display Title&apos; which will be displayed to user and a description. </p><p>You can also adjust the fiat currency your store will use. It looks like a dropdown, but clicking the dropdown arrow, won&apos;t show all the options if USD is still populated. Clear the text and type in the 3 letter code for the currency you want if you&apos;re not using USD.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/15_app_description.png" class="kg-image" alt loading="lazy" width="810" height="554" srcset="https://abarron.com/content/images/size/w600/2022/11/15_app_description.png 600w, https://abarron.com/content/images/2022/11/15_app_description.png 810w" sizes="(min-width: 720px) 720px"></figure><p>There will be several products auto-generated under &apos;Products&apos;. You should delete all but one of these unless you really like tea or something.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/default_products.png" class="kg-image" alt loading="lazy" width="827" height="443" srcset="https://abarron.com/content/images/size/w600/2022/11/default_products.png 600w, https://abarron.com/content/images/2022/11/default_products.png 827w" sizes="(min-width: 720px) 720px"></figure><p>Edit the product you didn&apos;t delete. Put &apos;Donate&apos; or whatever you want as &apos;Title&apos;. Set Price to &apos;Minimum&apos; and &apos;0&apos;. </p><p>The remaining fields are optional. Do as you wish to spruce up your point of sale. Click &apos;Save&apos; once you&apos;re done.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/16_edit_product.png" class="kg-image" alt loading="lazy" width="491" height="876"></figure><p>Leave everything under &apos;Appearance&apos; as is and make sure &apos;Custom Payments&apos; is disabled. Users can still input whatever payment amount they want the way we set it up.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/17_appearence_and_custom_payments.png" class="kg-image" alt loading="lazy" width="789" height="385" srcset="https://abarron.com/content/images/size/w600/2022/11/17_appearence_and_custom_payments.png 600w, https://abarron.com/content/images/2022/11/17_appearence_and_custom_payments.png 789w" sizes="(min-width: 720px) 720px"></figure><p>Under &apos;Additional Options&apos;, you can optionally add a redirect URL after the user completes payment. For example, if you want to send them to a thank you page. </p><p>Once you&apos;re done with all these options, click &apos;Save&apos; in the upper right corner. It&apos;s easy to miss.</p><p>Now, you can finally add this to your website. &#xA0;The iframe option will be the easiest in my opinion. Copy the whole iframe HTML under &apos;Embed Point of Sale via iframe&apos; and paste it into the appropriate page of your website. Make sure you&apos;ve enabled raw HTML access for whatever content management system you&apos;re using.</p><p>If the store doesn&apos;t fit the iframe well(it didn&apos;t for me), you need to adjust the dimensions. In my case, I had to add height by updating the style field. See example below.</p><!--kg-card-begin: markdown--><pre><code class="language-html">&lt;iframe src=&apos;https://btcpay0.voltageapp.io/apps/XKrNPjtnLCq4kuKRpYgzAn4PAxo/pos&apos; style=&apos;height:1000px;max-width: 100%; border: 0;&apos;&gt;&lt;/iframe&gt;
</code></pre>
<!--kg-card-end: markdown--><p>You can also link to the payment page using the payment button HTML BTCPay Server generates for you.</p><p>Or you can simply send people the link to your payment page if you don&apos;t want it on your own site. Grab the link from the iframe HTML after where it says &apos;src&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/19_iframe_code.png" class="kg-image" alt loading="lazy" width="793" height="511" srcset="https://abarron.com/content/images/size/w600/2022/11/19_iframe_code.png 600w, https://abarron.com/content/images/2022/11/19_iframe_code.png 793w" sizes="(min-width: 720px) 720px"></figure><p>Success! You can now accept donations/payments in Bitcoin. Give the flow a test with a small amount and make sure the payment gets routed to an address you control.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/19_iframe_preview.png" class="kg-image" alt loading="lazy" width="345" height="600"></figure><h2 id="voltage-billing">Voltage Billing</h2><p>If you intend to keep using Voltage beyond the free trial or free credit(I received $10 free credit just for creating an account), you&apos;ll need to add credit to your account.</p><p>Head back to your <a href="https://account.voltage.cloud/?ref=abarron.com">Voltage account page</a>. Click &apos;Billing&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/20_account_mgmt_billing.png" class="kg-image" alt loading="lazy" width="560" height="249"></figure><p>On this page, you&apos;ll see a full overview of the costs of running your node and how much credit you have left. Add credit to keep the satoshis flowing in. Voltage accepts Bitcoin, Lightning, and credit card payments.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/21_billing_overview.png" class="kg-image" alt loading="lazy" width="752" height="274" srcset="https://abarron.com/content/images/size/w600/2022/11/21_billing_overview.png 600w, https://abarron.com/content/images/2022/11/21_billing_overview.png 752w" sizes="(min-width: 720px) 720px"></figure>]]></content:encoded></item><item><title><![CDATA[Buying KYC-Light Bitcoin with Pocket Bitcoin]]></title><description><![CDATA[How to buy Bitcoin in Europe while revealing only minimal personal information and receiving your coins directly to cold storage.]]></description><link>https://abarron.com/buying-kyc-light-bitcoin-with-pocket-bitcoin/</link><guid isPermaLink="false">65ae37ac56f43f07294f2954</guid><category><![CDATA[Bitcoin]]></category><category><![CDATA[Guides]]></category><category><![CDATA[Pocket Bitcoin]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Tue, 08 Nov 2022 08:01:50 GMT</pubDate><content:encoded><![CDATA[<p><em>Disclaimer: Note that I have no financial relationship with Pocket Bitcoin besides being a happy customer of theirs.</em></p><p>Know Your Customer(KYC) laws claim to make the world a safer place because it hinders criminals&apos; ability to transact with cryptocurrencies or traditional banks anonymously. That would be true for only the most inept criminals. Any slightly more advanced criminal has already figured out how to stay anonymous and are generally several steps ahead of regulators. The only people actually suffering from the obstacles of KYC are ordinary people like you and me. </p><p>Exchanges demand our sensitive data like IDs and tax numbers and store it in perpetuity in the service of KYC compliance. Their databases with this information are the perfect honeypot for criminals to hack into and steal our identities to be used for identity theft. Thus, criminals can continue to use financial platforms requiring KYC by impersonating you and me until the identity is burnt and they move onto the next victim. We pay the price while legislators get to campaign on cracking down on money launderers and criminals get off free. If you&apos;re interested in understanding the threats of KYC more, see this <a href="https://bitcoinmagazine.com/culture/argument-against-kyc-bitcoin?ref=abarron.com">Bitcoin Magazine article</a>.</p><p>So what do we do about this problem? Well, we must use no-KYC or KYC-light platforms instead. No-KYC is the most anonymous and generally means peer-to-peer trading by buying Bitcoin through a person you know already or meet through an online platform like Bisq or Hodl Hodl. The buyers and sellers negotiate among themselves how much personal information they want to reveal. Buyers pay sellers directly through payment channels unconnected to the platform that introduced them.</p><p>The other category is KYC-light which is the model Pocket Bitcoin uses. In this model, you give them a Bitcoin address, your IBAN number, and an email address(you can spin up an anonymous one). You buy Bitcoin by creating an order with an open-ended amount and then you send funds to their bank account with a reference number tied to your order. Funds can be sent on a one-off or recurring basis with the same reference number. When you pay their bank account, the wire transfer will reveal your name in their records.</p><p>You obviously reveal some personal information in this model so if you need perfect privacy, this isn&apos;t the best option for you. For most people, this model is a great option for revealing minimal sensitive data and only information that can&apos;t be used for identity theft.</p><p>Additionally, their model is entirely non-custodial meaning the Bitcoin you buy gets sent directly to whatever Bitcoin address(es) you want. It eliminates the extra step of having to withdraw Bitcoin from an exchange because Pocket sends your coins directly to your own wallet.</p><h2 id="how-to-buy-on-pocket">How to Buy on Pocket</h2><p>Prerequisites:</p><ol><li>A Bitcoin wallet(hardware or software)</li><li>An email address(ideally an anonymous one tied to no other platforms)</li><li>A bank account in one of the following countries: Andorra, Austria, Belgium, Bulgaria, Croatia, Czech Republic, Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Italy, Latvia, Liechtenstein, Lithuania, Luxembourg, Moldova, Montenegro, Netherlands, North Macedonia, Norway, Poland, Portugal, Romania, San Marino, Serbia, Slovakia, Slovenia, Spain, Sweden, Switzerland, Turkey, United Kingdom</li><li>Euro or Swiss Franc fiat money</li></ol><p>Assuming you have all of the above, you&apos;re ready to make your first purchase. Let&apos;s go.</p><h2 id="wallet-setup">Wallet Setup</h2><p>Head over to <a href="https://pocketbitcoin.com/?ref=abarron.com">Pocket Bitcoin</a> and click &apos;Set up now&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/01_homepage.png" class="kg-image" alt loading="lazy" width="1329" height="729" srcset="https://abarron.com/content/images/size/w600/2022/11/01_homepage.png 600w, https://abarron.com/content/images/size/w1000/2022/11/01_homepage.png 1000w, https://abarron.com/content/images/2022/11/01_homepage.png 1329w" sizes="(min-width: 720px) 720px"></figure><p>The next page will briefly summarize the steps at a high-level. We&apos;ll go through each in detail.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/02_second_page-2.png" class="kg-image" alt loading="lazy" width="448" height="580"></figure><p>Your first action is to select how you want to receive your Bitcoin. Pocket Bitcoin sells hardware wallets like Nano Ledgers and BitBoxes. They also offer Opendimes which are a simple device that stores small amounts of Bitcoin. For this guide, we&apos;re only interested in using an existing wallet so select that.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/03_select_wallet_type.png" class="kg-image" alt loading="lazy" width="1224" height="753" srcset="https://abarron.com/content/images/size/w600/2022/11/03_select_wallet_type.png 600w, https://abarron.com/content/images/size/w1000/2022/11/03_select_wallet_type.png 1000w, https://abarron.com/content/images/2022/11/03_select_wallet_type.png 1224w" sizes="(min-width: 720px) 720px"></figure><p>Here you&apos;ll choose which wallet you&apos;re using.</p><p>In reality, you&apos;re less picking where your Bitcoin will be sent, but rather how you want to provide Pocket a receive address and sign a message confirming that you own that address. If you use your hardware wallet with software like Sparrow or any other wallet that allows signing messages, you can choose &apos;Other&apos;. Otherwise, select your device.</p><p>I&apos;ll be walking through the steps with a hardware wallet like the Ledger and then showing you the steps for the &apos;Other&apos; option.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/04_select_hardware_wallet.png" class="kg-image" alt loading="lazy" width="1006" height="566" srcset="https://abarron.com/content/images/size/w600/2022/11/04_select_hardware_wallet.png 600w, https://abarron.com/content/images/size/w1000/2022/11/04_select_hardware_wallet.png 1000w, https://abarron.com/content/images/2022/11/04_select_hardware_wallet.png 1006w" sizes="(min-width: 720px) 720px"></figure><h2 id="connecting-pocket-to-a-hardware-wallet">Connecting Pocket to a Hardware Wallet</h2><p>I&apos;ll be using a Ledger, but the steps for BitBox, Trezor, and Coldcard should be the same. Once you select your device, you&apos;ll be presented with the following screen. Connect your wallet to your computer at this point and make sure it is unlocked and you&apos;re using the appropriate Bitcoin app, if applicable. Then click &apos;Determine address&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/05_receive_on_ledger.png" class="kg-image" alt loading="lazy" width="980" height="474" srcset="https://abarron.com/content/images/size/w600/2022/11/05_receive_on_ledger.png 600w, https://abarron.com/content/images/2022/11/05_receive_on_ledger.png 980w" sizes="(min-width: 720px) 720px"></figure><p>Your browser will ask permission to let pocketbitcoin.com connect to your wallet. Go ahead and hit &apos;Connect&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/ledger_connection.png" class="kg-image" alt loading="lazy" width="437" height="420"></figure><p>Once it connects, your wallet will walk you through sharing a Bitcoin address with Pocket and signing a message confirming you own that address. Simply follow the steps on the device until you see the following screen.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/09_address_confirmed.png" class="kg-image" alt loading="lazy" width="522" height="564"></figure><h2 id="connecting-with-other">Connecting with &apos;Other&apos;</h2><p>Here we&apos;ll walk through the same steps but through the &apos;Other&apos; path. This can also be done with a <a href="https://abarron.com/migrating-from-ledger-live-to-sparrow/">hardware wallet connected to wallet software like Sparrow</a>. The main prerequisite here is you need a way to sign a message with your wallet. Ledger Live won&apos;t do the trick unfortunately. Check out <a href="https://abarron.com/signing-messages-with-sparrow/">my guide on signing messages with Sparrow</a> if you&apos;re stuck.</p><p>Pocket will prompt you to paste in a Bitcoin address you own. Get this through the Receive function of whatever wallet software you use.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/06_enter_bitcoin_address.png" class="kg-image" alt loading="lazy" width="461" height="495"></figure><p>Next you will confirm your Bitcoin address by signing a message. I go through the full steps of that in the article linked above.</p><p>The message you need to sign will be given to you by Pocket. Simply copy that into your signing tool and then copy the resulting signature back here under &apos;Message Signature&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/07_confirm_address.png" class="kg-image" alt loading="lazy" width="668" height="764" srcset="https://abarron.com/content/images/size/w600/2022/11/07_confirm_address.png 600w, https://abarron.com/content/images/2022/11/07_confirm_address.png 668w"></figure><p>You&apos;ll be presented with this success message if you did everything right.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/09_address_confirmed-1.png" class="kg-image" alt loading="lazy" width="522" height="564"></figure><h2 id="a-note-on-xpubs">A Note on xpubs</h2><p>In the steps above, we shared only one address with Pocket. All future purchases through Pocket will be sent to that address unless you repeat the setup process from zero. This is convenient, but it&apos;s generally bad practice from a privacy perspective to <a href="https://en.bitcoin.it/wiki/Address_reuse?ref=abarron.com">reuse Bitcoin addresses</a>.</p><p>Pocket gives you the option to share your extended public key(xpub) which Pocket can use to send each purchase to a new Bitcoin address. This solves the reuse problem and is very convenient, but introduces a different privacy problem. Giving your xpub to someone allows that entity to see all your addresses associated with that wallet. This means that even if you stopped using Pocket, they could monitor transactions on addresses you never used to receive Bitcoin from them.</p><p>You have to decide for yourself what trade offs you can live with. Here are two options if you&apos;re keen to optimize for privacy over convenience.</p><ol><li>Repeat the setup process with a new address every time you buy Bitcoin with Pocket.</li><li>Use a separate wallet only for Pocket buys, share the xpub with Pocket, and then periodically transfer coins to your cold storage. Pocket could track those subsequent transactions if they wanted, but they wouldn&apos;t have access to all your addresses in your cold storage.</li></ol><p>If you decide to share your xpub, you can do so once you reach the success page after confirming one address. Click the link to &apos;share your entire wallet&apos;.</p><p>If you use Sparrow, you can get your xpub from the &apos;Settings&apos; tab of your wallet. If you use any other wallet, look up the steps in <a href="https://blog.blockonomics.co/how-to-find-your-xpub-key-with-these-8-popular-bitcoin-wallets-ce8ea665ffdc?ref=abarron.com">this article</a> or do an Internet search for &quot;how to find xpub for your_wallet_name&quot;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/enter_xpub.png" class="kg-image" alt loading="lazy" width="662" height="488" srcset="https://abarron.com/content/images/size/w600/2022/11/enter_xpub.png 600w, https://abarron.com/content/images/2022/11/enter_xpub.png 662w"></figure><h2 id="payment-setup">Payment Setup</h2><p>We&apos;re on the home stretch now. Next you need to provide an email address to Pocket. This is only used to send you updates on your status of your orders. There is no account creation in Pocket. I would encourage you to use an anonymous email here if you want to optimize for privacy. Or at least use a unique address so if there was a data leak, it would be harder to match the email provided to any of your accounts elsewhere.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/10_enter_email.png" class="kg-image" alt loading="lazy" width="477" height="456"></figure><p>Here you&apos;ll enter the IBAN of your EUR or CHF account and select what currency you want to pay in. Your IBAN will be available through your bank and is outside of the scope of this article to walk you through. Be sure to choose the appropriate currency because this will change where you send payment to.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/11_enter_bank_account.png" class="kg-image" alt loading="lazy" width="479" height="627"></figure><p>Success! You&apos;re now ready to buy. Pocket will display a bunch of information you&apos;ll need to complete payment. All this information will also be emailed to you.</p><p>Add Pocket&apos;s bank details as a payee in your bank account using the details from the personal Pocket invoice. Then create a transfer for any amount you want. Make sure you put your payment reference(listed in the Pocket invoice) in the transfer description field before you send the payment. This is crucial to ensure your payment is appropriately assigned for pay out to your Bitcoin address.</p><p>Depending on how much you send, Pocket will use Kraken&apos;s API&apos;s to pull the latest price and calculate the amount of Bitcoin they owe you. The price you get is what the price was when your money hits Pocket&apos;s account. Use instant SEPA payments if you&apos;re anxious about future price movements.</p><p>Note that Pocket takes a 1.5% cut. Compared to the premiums one pays on decentralized marketplaces like Hodl Hodl and Bisq, Pocket&apos;s 1.5% cut is a bargain.</p><p>For very large one-time orders, they advise <a href="https://pocketbitcoin.com/faq/are-there-any-limits-with-pocket?ref=abarron.com">contacting them first</a>.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/12_order_ready.png" class="kg-image" alt loading="lazy" width="676" height="1239" srcset="https://abarron.com/content/images/size/w600/2022/11/12_order_ready.png 600w, https://abarron.com/content/images/2022/11/12_order_ready.png 676w"></figure><h2 id="receive-your-coins">Receive Your Coins</h2><p>Once your bank transfer lands in Pocket&apos;s account, you&apos;ll receive a confirmation of your purchase via email.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/order_conf_email.png" class="kg-image" alt loading="lazy" width="572" height="679"></figure><p>Pocket pays out once a day at about 10pm Central European time. This allows them to batch all their pay outs into fewer transactions and reduce transaction fees for everyone involved. Use the &apos;Track your payout&apos; link in the confirmation email to check the status of your pay out.</p><p>If you managed to follow all that, you&apos;re now the proud owner of some KYC-light Bitcoin. Don&apos;t spend it all on place. In fact, don&apos;t spend it at all. Just hodl.</p>]]></content:encoded></item><item><title><![CDATA[Signing Messages with Sparrow Wallet]]></title><description><![CDATA[How to create cryptographically signed messages using the Sparrow Bitcoin Wallet.]]></description><link>https://abarron.com/signing-messages-with-sparrow/</link><guid isPermaLink="false">65ae37ac56f43f07294f2951</guid><category><![CDATA[Sparrow Wallet]]></category><category><![CDATA[Bitcoin]]></category><category><![CDATA[Guides]]></category><category><![CDATA[Ledger]]></category><category><![CDATA[Wallets]]></category><category><![CDATA[Cryptography]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Sat, 05 Nov 2022 11:28:00 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2023/02/sparrow-1.png" class="kg-image" alt loading="lazy" width="1650" height="1650" srcset="https://abarron.com/content/images/size/w600/2023/02/sparrow-1.png 600w, https://abarron.com/content/images/size/w1000/2023/02/sparrow-1.png 1000w, https://abarron.com/content/images/size/w1600/2023/02/sparrow-1.png 1600w, https://abarron.com/content/images/2023/02/sparrow-1.png 1650w" sizes="(min-width: 720px) 720px"></figure><p>There are many reasons to use more feature rich wallets like Sparrow, Electrum, and others instead of the apps shipped alongside hardware wallets(e.g. Ledger Live). One of the particularly nice features is a simple signing tool is baked into these wallets I listed which allow you to create cryptographically signed messages proving to the world that a message was truly written by you. </p><p>In reality, the signature only proves that the message was written by someone who holds a particular private key so you must keep your private key secure at all times to avoid impersonation. Cryptographic signatures work by encrypting a message with your private key that can be verified using your public key. The goal here is not to pass a secret, but rather it is to prove that no one else could have written a message besides the owner of the private key associated with the public key used to verify the message.</p><p>This is most interesting to us as Bitcoiners because this functionality is the basis for how you send Bitcoin to someone else. Your wallet signs a message with transaction data that tells the network that your Bitcoin now belongs to someone else. That&apos;s not all though.</p><p>You can also use signatures to prove to someone you control a particular Bitcoin address. This is useful if you want to prove you hold a certain amount of Bitcoin or prove to a Bitcoin exchange that you own an address to reduce the risk of withdrawing Bitcoin to an address you don&apos;t control. My favorite platform to buy Bitcoin, <a href="https://pocketbitcoin.com/?ref=abarron.com">Pocket Bitcoin</a>, uses this model to send purchased Bitcoin directly to your cold storage.</p><p>The math behind signing messages may be very complex, but Sparrow makes signing a message as simple as copy, paste, and click . First, open up Sparrow Wallet. Check out <a href="https://abarron.com/migrating-from-ledger-live-to-sparrow/">my guide on setting up a Nano Ledger on Sparrow</a> if you haven&apos;t moved to Sparrow yet.</p><p>Navigate to the &apos;Receive&apos; tab to get a Bitcoin address. Copy this to your clipboard.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/receive_address.png" class="kg-image" alt loading="lazy" width="634" height="213" srcset="https://abarron.com/content/images/size/w600/2022/11/receive_address.png 600w, https://abarron.com/content/images/2022/11/receive_address.png 634w"></figure><p>Open &apos;Tools&apos; from the Sparrow menu and select &apos;Sign/Verify Message&apos;.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/tools.png" class="kg-image" alt loading="lazy" width="217" height="205"></figure><p>This will open a window where you paste the address you copied previously and enter the message you want to sign. If you&apos;re doing this to buy from Pocket Bitcoin, this is where you enter the message they ask you to sign. Leave the &apos;Signature&apos; box empty as this is where Sparrow will deposit your signature.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/sign_message_window.png" class="kg-image" alt loading="lazy" width="530" height="542"></figure><p>At this point, make sure your hardware wallet is connected, unlocked, and if applicable, its Bitcoin app is open. </p><p>Next, hit the &apos;Sign&apos; button and you&apos;ll be prompted to choose your signing device.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/signing_device-1.png" class="kg-image" alt loading="lazy" width="489" height="352"></figure><p>Click &apos;Sign Message&apos; and follow the instructions on your device&apos;s screen. For example, Ledgers will ask you to confirm the address and a hash of the message you&apos;re signing. Once you confirm everything, the &apos;Signature&apos; box will show your signed message. Done!</p><p>Note that this same tool can also verify a signature. While all three fields are still filled, try clicking the &apos;Verify&apos; button. You should get confirmation of a good signature.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/verification_success-1.png" class="kg-image" alt loading="lazy" width="352" height="186"></figure><p>Finally, try making a change to either the message or the signature such as adding or removing a character. Try verifying again and you&apos;ll see it fails.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/11/verification_fail-1.png" class="kg-image" alt loading="lazy" width="350" height="199"></figure>]]></content:encoded></item><item><title><![CDATA[Connecting a Nano Ledger to Sparrow Wallet]]></title><description><![CDATA[How to hook up a Nano Ledger or any other hardware wallet to Sparrow Wallet for better Bitcoin self-custody.]]></description><link>https://abarron.com/migrating-from-ledger-live-to-sparrow/</link><guid isPermaLink="false">65ae37ac56f43f07294f2952</guid><category><![CDATA[Bitcoin]]></category><category><![CDATA[Sparrow Wallet]]></category><category><![CDATA[Ledger]]></category><category><![CDATA[Wallets]]></category><category><![CDATA[Guides]]></category><dc:creator><![CDATA[Alex Barron]]></dc:creator><pubDate>Thu, 20 Oct 2022 17:22:00 GMT</pubDate><content:encoded><![CDATA[<figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2023/02/sparrow-2.png" class="kg-image" alt loading="lazy" width="1650" height="1650" srcset="https://abarron.com/content/images/size/w600/2023/02/sparrow-2.png 600w, https://abarron.com/content/images/size/w1000/2023/02/sparrow-2.png 1000w, https://abarron.com/content/images/size/w1600/2023/02/sparrow-2.png 1600w, https://abarron.com/content/images/2023/02/sparrow-2.png 1650w" sizes="(min-width: 720px) 720px"></figure><p>I&apos;ve recently been taking my Bitcoin custody and privacy more seriously. That means different things to different people, but for me it means moving towards services that better embody the ethos of Bitcoin; open sourced, decentralized, and self-custodied. Like many of you, I started my crypto journey holding coins on exchanges. Soon I learned hardware wallets were a better tool to secure your coins so I got myself a Nano Ledger S. </p><p>Knowing what I know now, I probably would have purchased a different hardware wallet, but my Ledger has nevertheless performed well. For a new user, a Ledger is a great hardware wallet. Their software Ledger Live is also perfectly fine for a new user with its simplicity, but if you&apos;re looking to take your Bitcoin custody more seriously, there are better options such as open source and privacy focused <a href="https://www.sparrowwallet.com/?ref=abarron.com">Sparrow</a>.</p><p>In the developer&apos;s words, &quot;Sparrow is a Bitcoin wallet for those who value financial self sovereignty. Sparrow&#x2019;s emphasis is on security, privacy and usability.&quot; It&apos;s also designed for both beginners and advanced users in that it does not hide any data from you the way tools like Ledger Live does. If you want to micromanage your UTXOs(which you should), it empowers you to do that. On the other hand, if you don&apos;t even know what a UTXO is, that is fine and you can still use Sparrow.</p><p>For our purposes here, the important thing is that it integrates cleanly with Ledger devices. Here&apos;s the simple process to switch away from Ledger Live to Sparrow. These steps are virtually the same even if you&apos;re using another hardware wallet.</p><h2 id="download-verify-sparrow">Download &amp; Verify Sparrow</h2><p>Head over to <a href="https://sparrowwallet.com/download/?ref=abarron.com">Sparrow&apos;s download page</a> and follow the download and verification instructions. I know you&apos;ll be tempted to skip the verification steps. Take it seriously and learn something. You&apos;ll encounter this challenge again in your Bitcoin journey so give it a go.</p><h2 id="connect-sparrow-to-the-bitcoin-network">Connect Sparrow to the Bitcoin Network</h2><p>You&apos;ll be presented with a welcome screen when you first open Sparrow that explains a bit about Sparrow&apos;s offline and online modes. Offline mode is primarily for more serious privacy and security focused use cases. For most users who want to simply receive and send transactions without too much friction, leaving Sparrow in online mode will be fine.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-17.37.45.png" class="kg-image" alt loading="lazy" width="596" height="512"><figcaption>Sparrow welcome page</figcaption></figure><p>Online mode requires connecting to the Bitcoin network via a node that will be your relay for downloading and broadcasting your transactions. Sparrow will show you three server options: Public Server, Bitcoin Core node, and Private Electrum Server.</p><p>In an ideal world, you have your own node, but that is a more advanced setup to consider in the future. The easiest option is to connect to a public server. There are some privacy downsides to using a public server, but if you&apos;re coming from Ledger Live, you&apos;ve already revealed the same information through their servers. Moving to Sparrow is a good first step to better privacy. Take the next step with your own node or Electrum server when you&apos;re ready.</p><p>Once you go through the connection options, you&apos;ll be asked to configure your connection. Choose &apos;Public Server&apos; and click &apos;Create New Wallet&apos;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-17.38.47.png" class="kg-image" alt loading="lazy" width="743" height="624" srcset="https://abarron.com/content/images/size/w600/2022/10/Screenshot-2022-10-25-at-17.38.47.png 600w, https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-17.38.47.png 743w" sizes="(min-width: 720px) 720px"><figcaption>Configuring a public server connection</figcaption></figure><h2 id="create-your-wallet">Create Your Wallet</h2><p>Give your wallet a short and descriptive name.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.00.11.png" class="kg-image" alt loading="lazy" width="454" height="194"><figcaption>Wallet name</figcaption></figure><p>Now you&apos;ll need to choose a &apos;Keystore&apos; for your wallet. Select &apos;Connected Hardware Wallet&apos; to configure your Ledger.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.00.48.png" class="kg-image" alt loading="lazy" width="882" height="699" srcset="https://abarron.com/content/images/size/w600/2022/10/Screenshot-2022-10-25-at-18.00.48.png 600w, https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.00.48.png 882w" sizes="(min-width: 720px) 720px"></figure><p>Your Ledger device must be unlocked and the Bitcoin app must be open to be discoverable by Sparrow. If your Ledger is not in this state, you&apos;ll get a &apos;No hardware wallets found&apos; error. Assuming the scan is a success, you&apos;ll see your Ledger pop up in the list and you can click &apos;Import Keystore&apos;. This imports your public keys and Bitcoin addresses into Sparrow. Private keys never leave hardware wallets.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.02.34.png" class="kg-image" alt loading="lazy" width="461" height="70"></figure><p>You can set a password for additional security.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.04.28.png" class="kg-image" alt loading="lazy" width="376" height="254"></figure><p>If all goes well, you&apos;ll see the following screen. Hit &apos;Apply&apos; to complete the import.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.16.08.png" class="kg-image" alt loading="lazy" width="795" height="444" srcset="https://abarron.com/content/images/size/w600/2022/10/Screenshot-2022-10-25-at-18.16.08.png 600w, https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.16.08.png 795w" sizes="(min-width: 720px) 720px"></figure><p>Done! Sparrow will use your public key to calculate all your addresses and then look up past transactions which will be displayed in the &apos;Transactions&apos; and &apos;UTXOs&apos; tabs.</p><h2 id="add-accounts-optional">Add Accounts (Optional)</h2><p>If you&apos;ve ever created additional Bitcoin accounts in Ledger Live, you&apos;ll need to import these as well. This is very simple. Go to the &apos;Settings&apos; tab and look for the &apos;Add Account&apos; button.</p><figure class="kg-card kg-image-card"><img src="https://abarron.com/content/images/2022/10/Screenshot-2022-10-25-at-18.06.43.png" class="kg-image" alt loading="lazy" width="395" height="225"></figure><p>Sparrow will show you a list of account numbers. &apos;Account #1&apos; will be the second account you created in Ledger Live. &apos;Account #2&apos; will be the third and so on. Adding each account works the same as the initial wallet setup you did in the previous step.</p><h2 id="what-next">What Next?</h2><p>Take a look around Sparrow and take note of how much more detailed information it provides you compared to Ledger Live. This information, when used correctly, can significantly improve your Bitcoin management and general understanding of how Bitcoin works under the hood. Check out <a href="https://www.sparrowwallet.com/docs/?ref=abarron.com">Sparrow&apos;s documentation</a> if you&apos;d like to learn more.</p>]]></content:encoded></item></channel></rss>