curl javascript ruby python rust java go haskell

Introduction

ApproveAPI gives developers a simple, single API to request user's real-time approval for any workflow on every platform via email, SMS, Slack, in-app mobile Push SDK.

ApproveAPI is organized as a standard REST that uses resource-oriented URLs, accepts both form-encoded and JSON-encoded request bodies, returns JSON-encoded responses, and uses standard HTTP response codes.

The API conforms to the standard OpenAPI 3.0 specification (found here) which means you can auto-generate client libraries for ApproveAPI in almost any language.

We provide first-class library support for Ruby, Python, Node, Rust, Java, Go, and Haskell.

Authentication

Example request:

$ curl https://approve.sh/ \
    -u "sk_test_yourapikeyhere:"

# The colon tells curl the password is blank.    
# Replace `sk_test_yourapikeyhere` with your API key
var ApproveAPI = require('approveapi')
var client = ApproveAPI.createClient('sk_test_YOURAPIKEYHERE')
require 'approveapi'
client = ApproveAPI::create_client('sk_test_yourapikeyhere')
import approveapi
client = approveapi.create_client('sk_test_yourapikeyhere')
extern crate approveapi; // Optional for Rust >= 1.31 
let client = approveapi::create_client('sk_test_yourapikeyhere');
//  import com.approveapi.*;
//  import com.approveapi.auth.HttpBasicAuth;

HttpBasicAuth auth = (HttpBasicAuth) Configuration.getDefaultClient().getAuthentication("apiKey");
auth.setUsername("sk_test_yourapikeyhere");
import "github.com/approveapi/approveapi-go"
client := approveapi.CreateClient("sk_test_yourapikeyhere")
import qualified ApproveApi as A
...
config <- (`A.addAuthMethod` A.AuthBasicApiKey "sk_test_yourapikeyhere" "")
          <$> A.newConfig 

ApproveAPI uses HTTP Basic Authentication where the username is your API key and the password is blank.

Each account has two API Keys: a test key prefixed by sk_test_ and production key prefixed by sk_live_.

Both test and live keys can be rotated/reset through the dashboard. The purpose of the test key is to make it easier to rotate without affecting production deployments.

Prompt

A Prompt object represents an outgoing approval request that was sent to a user, including the user's answer if the request has been responded to. A prompt may be in a pending or completed state, which can be determined by the answer field.

ApproveAPI optionally supports long polling which means you can create a prompt and have the request hang synchronously until the user has answered the prompt.

Sending a prompt

POST /prompt

Example Request:

$ curl https://approve.sh/prompt \
    -u "sk_test_bdtqjUrqIXSIyA1Mtkk0:" \
    -d user="alice@approveapi.com" \
    -d body="A transfer of $1337.45 from acct 0294 to acct 1045 \
             has been initiated. Do you want to authorize this \
             transfer?" \
    -d approve_text="Authorize" \
    -d reject_text="Reject" \
    -d expires_in=600 \
    -d metadata[location]="San Francisco, CA" \
    -d metadata[timestamp]="4:45pm, Feb 21 2019"
var params = {
    'user': 'alice@approveapi.com',
    'body': 'A transfer of $1337.45 from acct 0294 to acct 1045 has\
        been initiated. Do you want to authorize this transfer?',
    'approve_text': 'Authorize',
    'reject_text': 'Reject',
    'expires_in': 600,
    'long_poll': true
};

client.createPrompt(params).then(
    (response) => {
        if (response.answer) {
            if (response.answer.result) {
                console.log("Request approved");
            } else {
                console.log("Request rejected");
            }
        } else {
            console.log("No response from user");
        }
    },
    (error) => {
        console.error(error);
    }
);
begin
  response = client.create_prompt(ApproveAPI::CreatePromptRequest.new({
    :body => """A transfer of $1337.45 from acct 0294 to acct 1045 has\
been initiated. Do you want to authorize this transfer?""",
    :user => 'alice@approveapi.com',
    :approve_text => 'Authorize',
    :reject_text => 'Reject',
    :long_poll => true, # Wait for the user's answer
  }))
  if response.answer
    if response.answer.result
      p "Request approved"
    else
      p "Request rejected"
    end
  else
    p "No response from user"
  end
rescue ApproveAPI::ApiError => e
  puts "Exception when calling ApproveApi->create_prompt: #{e}: #{e.response_body}"
end
from approveapi import CreatePromptRequest 

try:
  response = client.create_prompt(CreatePromptRequest(
      user='alice@approveapi.com', 
      body='''A transfer of $1337.45 from acct 0294 to acct 1045 has
      been initiated. Do you want to authorize this transfer?''',
      approve_text='Authorize',
      reject_text='Reject',
      expires_in=600,
      long_poll=True, # Wait for the user's answer
  ))

  if response.answer:
      if response.answer.result:
          print("Request approved")
      else:
          print("Request rejected")
  else:
      print("No response from user")
except approveapi.ApiException as e:
  print("Exception when calling ApproveApi->create_prompt: %s\n" % e)
use approveapi::{CreatePromptRequest};
let mut prompt_request = CreatePromptRequest::new(
    r#"A transfer of $1337.45 from acct 0294 to acct 1045
          has been initiated. Do you want to authorize this
          transfer?"#.to_string(),
    "alice@approveapi.com".to_string(),
);
prompt_request.approve_text = Some("Authorize".to_string());
prompt_request.reject_text = Some("Reject".to_string());
prompt_request.long_poll = Some(true);
match client.create_prompt(prompt_request).sync() {
    Ok(prompt) => {
        if let Some(answer) = prompt.answer {
            if answer.result {
                println!("Request approved");
            } else {
                println!("Request rejected");
            }
        } else {
            println!("No response from user");
        }
    }
    Err(e) => println!("ApproveAPI->create_prompt error: {:?}", e),
};
//  import com.approveapi.*;

Configuration.getDefaultClient().setReadTimeout(11*60*1000); // for long-polling
ApproveApi apiInstance = new ApproveApi();
try {
    Prompt prompt = apiInstance.createPrompt(
            new CreatePromptRequest()
                    .user("alice@approveapi.com")
                    .body("A transfer of $1337.45 from acct 0294 to acct 1045" +
                          " has been initiated. Do you want to authorize this" +
                          " transfer?")
                    .approveText("Authorize")
                    .rejectText("Reject")
                    .longPoll(true));
    if (prompt.getAnswer() != null) {
        if (prompt.getAnswer().getResult()) {
            System.out.println("Request approved");
        } else {
            System.out.println("Request rejected");
        }
    } else {
        System.out.println("No response yet");
    }
} catch (ApiException e) {
    System.out.println(e.getResponseBody());
}
longPoll := true
prompt, _, err := client.ApproveApi.CreatePrompt(
    approveapi.CreatePromptRequest {
      User: "alice@approveapi.com",
      Body: `A transfer of $1337.45 from acct 0294 to acct 1045
          has been initiated. Do you want to authorize this transfer?`,
      ApproveText: "Authorize",
      RejectText: "Reject",
      LongPoll: &longPoll,
    },
)
if err != nil {
    fmt.Printf("%#v\n", err)
    return
}
if prompt.Answer != nil {
    if prompt.Answer.Result {
        fmt.Println("Request approved")
    } else {
        fmt.Println("Request rejected")
    }
} else {
    fmt.Println("No response yet")
}
import qualified Network.HTTP.Client.TLS as NH
...
let body = "Your AcmeBank credit card was just charged $3249.99 at APPLE STORE,\
\ San Francisco. Authorize this transaction?"
let prompt = (A.mkCreatePromptRequest "alice@approveapi.com" body) {
                A.createPromptRequestLongPoll = Just True,
                    A.createPromptRequestApproveText = Just "Authorize",
                    A.createPromptRequestRejectText = Just "Reject"
              }
let createPromptRequest = A.createPrompt prompt

mgr <- NH.newTlsManager
createPromptResponse <- A.dispatchMime mgr config createPromptRequest

flip mapM_ createPromptResponse (\r ->
  case A.promptAnswer r of 
    Nothing -> putStrLn $ "No response yet"
    Just answer -> case A.promptAnswerResult answer of
      True -> putStrLn $ "Request approved"
      False -> putStrLn $ "Request rejected"
  )

Example Response:

{
  "id": "prompt_veC5W1pLecGpUTdSJk",
  "sent_at": 1549336110,
  "is_expired": false,
  "request": {
    "user": "alice@approveapi.com",
    "body": "A transfer of $1337.45 from acct 0294 to acct 1045 has been initiated. Do you want to authorize this transfer?",
    "approve_text": "Authorize",
    "reject_text": "Reject",
    "expires_in": 600,
    "metadata": {
      "location": "San Francisco, CA",
      "timestamp": "4:45pm, Feb 21 2019"
    }
  },
  "answer": null
}

Creates a prompt and pushes it to the user (sends via email, sms, or other supported protocols).

Arguments

user string The user to which to send the approval request to. The user can be any of the type of values specified in the User Types section. required
body string The body of the approval request to show the user. required
title string The title of an approval request. Defaults to an empty string.
approve_text string The approve action text. Defaults to 'Approve'.
approve_redirect_url string An HTTPS URL to redirect the user to if the prompt is approved. This URL is kept secret until the user is redirected to it.
reject_text string The reject action text. If not specified the reject action will NOT be rendered, and the user will only see an approve action button.
reject_redirect_url string An HTTPS URL to redirect the user to if the prompt is rejected. If this field is present but reject_text is omitted, reject_text defaults to 'Reject'.
expires_in integer The number of seconds after which answers to this prompt will no longer be accepted. Defaults to 86400 (24 hours).
internal_data object Use this field to associate a prompt with key-value pairs for your own internal use. This data is kept private and will not be shown to the user.
metadata object Add additional details to show the user in prompt approval request. See the metadata object for supported values.
long_poll boolean If true, the request waits (long-polls) either until the user responds to the approval request, the prompt expires, or more than 10 minutes pass. Defaults to false.

Attach internal data to a prompt

Example Internal Data

"internal_data": {
  "transaction_id": "abcd1234",
  "user_id": "9eRhRa9OR"
}

In some cases, you may want to link a prompt with some custom, internal identifying data. For example, for a transaction confirmation approval prompt you may want to tag the prompt with an internal transaction id from your own system.

You can use the internal_data field to attach an object with any number of custom key-value pairs to further link this approval prompt to internal object or action in your back-end.

Sending the metadata object in a prompt

Optionally add details about the approval request into the prompt.

For example, suppose you are asking a user to confirm a credit card transaction that occurred a few days ago in Oakland, CA at 11:30am, you might want to include these details to give the user more context about what they are approving. All the metadata fields are optional and will only be rendered in the prompt if you supply them.

// Optionally add metadata to the user prompt
var metadata = {
    'location': 'San Francisco, CA',
    'ip_address': '117.23.1.81',
};

var params = {
    'user': 'alice@approveapi.com',
    'body': 'A transfer of $1337.45 from acct 0294 to acct 1045 has\
        been initiated. Do you want to authorize this transfer?',
    'metadata': metadata,
    'long_poll': true
};
# Optionally add metadata to the user prompt
from approveapi import CreatePromptRequest, PromptMetadata

metadata = PromptMetadata(
  location='San Francisco, CA',
  ip_address='117.23.1.81',
)
request = CreatePromptRequest(
  user='alice@approveapi.com', 
  body='''A transfer of $1337.45 from acct 0294 to acct 1045 has
  been initiated. Do you want to authorize this transfer?''',
  metadata=metadata,
  long_poll=True,
)
# Optionally add metadata to the user prompt
metadata = ApproveAPI::PromptMetadata.new({
  :location => 'San Francisco, CA',
  :ip_address => '117.23.1.81',
})
request = ApproveAPI::CreatePromptRequest.new({
  :user => 'alice@approveapi.com', 
  :body => '''A transfer of $1337.45 from acct 0294 to acct 1045 has\
been initiated. Do you want to authorize this transfer?''',
  :metadata => metadata,
  :long_poll => true,
})
# Optionally add metadata to the user prompt
use approveapi::PromptMetadata;
let mut metadata = PromptMetadata::new();
metadata.location = Some("San Francisco, CA".to_string());
metadata.ip_address = Some("117.23.1.81".to_string());
let mut prompt_request = CreatePromptRequest::new(
    r#"A transfer of $1337.45 from acct 0294 to acct 1045
          has been initiated. Do you want to authorize this
          transfer?"#.to_string(),
    "alice@approveapi.com".to_string(),
);
prompt_request.long_poll = Some(true);
prompt_request.metadata = Some(metadata);
// Optionally add metadata to the user prompt
PromptMetadata metadata = new PromptMetadata()
    .location("San Francisco, CA")
    .ipAddress("117.23.1.81");

CreatePromptRequest promptRequest = new CreatePromptRequest()
        .user("alice@approveapi.com")
        .body("A transfer of $1337.45 from acct 0294 to acct 1045" +
              " has been initiated. Do you want to authorize this" +
              " transfer?")
        .longPoll(true)
        .metadata(metadata);
// Optionally add metadata to the user prompt
location := "San Francisco, CA"
ipAddress := "117.23.1.81"
approveapi.CreatePromptRequest {
    //  ...
    Metadata: &approveapi.PromptMetadata {
        Location: &location,
        IpAddress: &ipAddress,
    },
}
-- Optionally add metadata to the user prompt
let body = "Your AcmeBank credit card was just charged $3249.99 at APPLE STORE,\
\ San Francisco. Authorize this transaction?"
let prompt = (A.mkCreatePromptRequest "alice@approveapi.com" body) {
                A.createPromptRequestLongPoll = Just True,
                A.createPromptRequestApproveText = Just "Authorize",
                A.createPromptRequestRejectText = Just "Reject",
                  A.createPromptRequestMetadata = Just A.mkPromptMetadata {
                    A.promptMetadataIpAddress = Just "117.23.1.81",
                    A.promptMetadataLocation = Just "San Francisco, CA"
                  }
                }

Arguments

location string A physical location, like Oakland, CA for an action
time string A time string, like Feb 21 at 4:45pm for an action
ip_address string An ip address like 17.43.555.12 of the machine initiating a digital action
browser string The type of browser, like Chrome initiating a digital action
operating_system string The type of operating system, like Mac OS X, initiating a digital action

Returns

The Prompt object if the call succeeds. Note that if the long_poll parameter is not set to true, it is very likely that the prompt object will have a null answer field. If long polling is enabled and the user responds to the approval request, the returned prompt object will have answer set to the user's answer in a prompt answer object. This call returns an error if the user field is not a valid phone number or email address.

Discussion

For the example approval request above, the user would be presented with the following rendered prompt.

The prompt object

Example Response:

{
  "id": "prompt_veC5W1pLecGpUTdSJk",
  "sent_at": 1549336110,
  "is_expired": false,
  "request": {
    "user": "alice@approveapi.com",
    "body": "A transfer of $1337.45 from acct 0294 to acct 1045 has been initiated. Do you want to authorize this transfer?",
    "approve_text": "Authorize",
    "reject_text": "Reject",
    "expires_in": 600,
    "metadata": {
      "location": "San Francisco, CA",
      "timestamp": "4:45pm, Feb 21 2019"
    }
  },
  "answer": {
    "result": true,
    "time": 1549336241,
    "metadata": {
      "ip_address": "172.17.54.22",
      "browser": "Chrome",
      "operating_system": "Windows 10"
    }
  }
}

Attributes

id string A unique id for this prompt request
sent_at timestamp The unix timestamp when the prompt was sent
is_expired boolean A boolean indicating if this prompt request expired before a user was able to answer it. If a prompt is answered, this value will always be false.
request object The verbatim prompt request that was sent.
answer object An object describing the user's answer to this approval request. If the user has yet to respond or the prompt is past expiration, the answer field will be null

The prompt answer

answer

result boolean The user's answer to whether or not they approve this prompt.
time timestamp The unix timestamp when the user answered this prompt.
metadata object Metadata object about the device the user answered the prompt on

The prompt answer metadata

metadata

ip_address string The ip address of the device the user responded to the prompt on
browser string The browser used by the user to respond to the prompt
operating_system string The operating system used by the user to respond to the prompt

Retrieve a prompt

GET /prompt/:id

Example Request:

$ curl 
  https://approve.sh/prompt/prompt_ZtKTSRNzb9HLI6mOtoVh/?long_poll=true \
    -u "sk_test_bdtqjUrqIXSIyA1Mtkk0:"
client.getPrompt(prompt_id) //prompt_id returned from a previous call to createPrompt
prompt_id = response.id # returned from create_prompt()

client.get_prompt(prompt_id)
prompt_id = response.id # returned from create_prompt()

client.get_prompt(prompt_id)
let prompt_id = response.id; // returned from create_prompt()

client.get_prompt(prompt_id, false /* don't long_poll */).sync();
apiInstance.getPrompt(prompt.getId(), false /* don't long poll */); // returned from createPrompt()
updatedPrompt, _, err := client.ApproveApi.GetPrompt(prompt.Id, nil)    //  returned from CreatePrompt()
let promptId = A.Id (A.promptId r) -- r returned from A.createPrompt
let getPromptRequest = A.getPrompt promptId
getPromptResponse <- A.dispatchMime mgr config getPromptRequest

Example Response:

{
  "id": "prompt_ZtKTSRNzb9HLI6mOtoVh",
  "sent_at": 1549348422,
  "is_expired": false,
  "request": {
    "user": "alice@approveapi.com",
    "body": "A transfer of $1337.45 from acct 0294 to acct 1045 has been initiated. Do you want to authorize this transfer?",
    "approve_text": "Authorize",
    "reject_text": "Reject",
    "expires_in": 600,
    "metadata": {
      "location": "San Francisco, CA",
      "timestamp": "4:45pm, Feb 21 2019"
    }
  },
  "answer": {
    "time": 1549348957,
    "metadata": {
      "ip_address": "172.17.54.22",
      "browser": "Safari",
      "operating_system": "Mac OS X"
    }
  }
}

Retrieves a prompt object with the given ID.

Arguments

id string The identifier for a pending or completed prompt. This is returned when you create a prompt. required
long_poll boolean If true, the request waits (long-polls) until either the user responds to the approval request, the prompt request expires, or more than 10 minutes pass. Defaults to false.

Returns

The Prompt object if the call succeeds. This request supports long polling with the the long_poll parameter. The answer field may or not be null depending on if the user has responded to the prompt or not.

Check a prompt status

GET /prompt/:id/status

Example Request:

$ curl 
  https://approve.sh/prompt/prompt_veC5W1pLecGpUTdSJk/status
client.getPromptStatus(prompt_id) //prompt_id returned from a previous call to createPrompt
prompt_id = response.id # returned from create_prompt()

client.get_prompt_status(prompt_id)
prompt_id = response.id # returned from create_prompt()

client.get_prompt_status(prompt_id)
let prompt_id = response.id; // returned from create_prompt()

client.get_prompt_status(prompt_id).sync();
apiInstance.getPromptStatus(prompt.getId()); // returned from createPrompt()
promptStatus, _, err := client.ApproveApi.GetPrompt(prompt.Id)  //  returned from CreatePrompt()
let promptId = A.Id (A.promptId r)
let getPromptStatusRequest = A.getPromptStatus promptId
getPromptStatusResponse <- A.dispatchMime mgr config getPromptStatusRequest

Example Response:

{
  "is_answered": true,
  "is_expired": false,
}

This is a convenience endpoint that does NOT require authentication for checking if a particular prompt has been answered or not.

Arguments

id string The identifier for a pending or completed prompt. This is returned when you create a prompt. required

Returns

Attributes

is_answered boolean A boolean indicating whether or not the prompt with the supplied prompt ID has an answer.
is_expired boolean A boolean indicating whether or not the prompt with the supplied prompt ID has expired before being answered.

Note that if the prompt ID does not exist, the API will always return false for is_answered and false for is_expired.

Discussion

This endpoint is useful for front-end applications where you do not have access to the secret API key. This enables front-end code to periodically check or poll if a particular prompt has been answered, and then trigger some action when it does get answered.

For example, a web application using Approve API for sending new device confirmations will trigger sending a prompt from the server but might not want to long poll on the back-end. Instead, the back-end can rendered the prompt ID on the front-end and have javascript periodically ping the /prompt/:id/status endpoint.

The front-end will learn when the user answers, and can then POST to the back-end which will do the final check to retrieve the prompt object and verify the user's answer.

User Types

ApproveAPI supports multiple user field types for sending prompts to various platforms like email, SMS, Slack, and in-app mobile push via the Push SDK. Below we specify how to format the various user types for the user field in Sending a prompt.

Platform Format for the user field Examples
Email A standard email address alice@acme.co
SMS An E.164 format telephone number +16175654123
Slack The string @slack:<user> where <user> is one of the following: a Slack username, user email address, channel id, or channel name @slack:jon@acme.co
Push SDK The string @push:<user-id> where <user-id> is an attribute you associate a user to via the API @push:kat@acme.co

Slack

Note you must invite "ApproveBot" to any non-user or private channel you want to send to, more information is available in the Slack Bot section.

Push SDK

Contact us to request early access to the Push SDK API.

Webhooks

ApproveAPI supports adding a webhook URL that is triggered whenever a user answers an approval prompt.

Add a Webhook URL

You can specify a webhook URL from the dashboard. To add a webhook, sign-in to the ApproveAPI Dashboard and go to the Webhooks Tab.

Live + Test mode

You can specify separate webhook URLs for use in live mode and test mode. Authenticating to ApproveAPI with a live API key will use the live webhook URL.

Callback Format

Example Callback

POST 

https://api.acme.co/prompt_answer/?prompt_id=prompt_veC5W1pLecGpUTdSJk

# This is a web hook callback request sent by ApproveAPI

ApproveAPI fires a HTTP POST request to the webhook URL you specify and appends the prompt_id of the Prompt object that a user has answered as a query string parameter.

Arguments

prompt_id string Query String Parameter. The identifier for a completed prompt. This is returned when you create a prompt. required

Discussion

You can use the prompt_id to fetch the corresponding prompt object and get the user's answer to the approval prompt.

You should store the prompt_id along with the pending user action (i.e. in a database row or in a signed session cookie).

When ApproveAPI triggers the web hook, you will then be able to associate the user's approval or rejection of the prompt with the corresponding action you are waiting to authorize.

Slack Bot Integration

Approval prompts can be sent to a Slack user or channel using the same Prompt endpoint. First, install the ApproveBot Slack app to your Slack workspace from the ApproveAPI Dashboard. Once ApproveBot is installed to your Slack workspace, Slack approval prompts will be enabled for your ApproveAPI account. See the User Types section for how to specify a Slack user or channel.

Multi-person approvals

Use a Slack channel to allow one of a group of users to be able to authorize a single request:

  1. Create a new Slack channel
  2. Invite those who are allowed to respond to the request, as well as the ApproveBot user, to the Slack channel
  3. Set the user field of the Prompt to @slack:<CHANNEL_ID>. You can find the ID of a Slack channel in the part of the URL after /messages. If you are on a native desktop Slack client, you can right-click the channel and click Copy Link, then paste the link to see the URL.

Libraries

$ npm install approveapi
# Terminal
$ gem install approveapi

# Gemfile with rubygems.org source
  gem 'approveapi'
$ pip install -U approveapi
// Cargo.toml
[dependencies]
...
approveapi = "^1"
//  The ApproveAPI java client is hosted on the Maven Central repository.

// pom.xml
<dependencies>
  ...
  <dependency>
    <groupId>com.approveapi</groupId>
    <artifactId>approveapi-client</artifactId>
    <version>1.0.6</version>
  </dependency>

// build.gradle
implementation 'com.approveapi:approveapi-client:1.+'
$ go get "github.com/approveapi/approveapi-go"
-- add to your package.yaml / *.cabal file
approveapi >=0.1.0.0 && <0.2

We provide first-class client library support for the following languages

OpenAPI 3.0

We also provide an OpenAPI v3 specification which can be used to generate client libraries in a number of other languages in addition to those above. You can find our specification and client generation code here.

Help

For support or help with ApproveAPI please send an email to support@approveapi.com.