├── Procfile
├── runtime.txt
├── requirements.txt
├── .gitignore
├── .idea
└── .gitignore
├── config.json
├── Dockerfile
├── README.md
├── app.py
└── binanceFutures.py
/Procfile:
--------------------------------------------------------------------------------
1 | web: gunicorn app:app
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.11.2
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pybit @ git+https://github.com/CryptoGnome/pybit-gnome.git@e86059a8a7a36be6b49f173f44affbb515603337
2 | flask
3 | gunicorn
4 | ccxt==3.0.74
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .idea/inspectionProfiles/profiles_settings.xml
3 | .idea/inspectionProfiles/Project_Default.xml
4 | .idea/misc.xml
5 | .idea/modules.xml
6 | .idea/Tradingview-Webhook-Bot.iml
7 | .idea/vcs.xml
8 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "KEY": 12345,
3 | "EXCHANGES": {
4 | "BYBIT": {
5 | "API_KEY": "api-key-goes-here",
6 | "API_SECRET": "api-secret-goes-here",
7 | "ENABLED": true,
8 | "TESTNET": false
9 | },
10 | "BINANCE-FUTURES": {
11 | "API_KEY": "api-key-goes-here",
12 | "API_SECRET": "api-secret-goes-here",
13 | "ENABLED": false,
14 | "TESTNET": false
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Use an official Python base image
2 | FROM python:3.11
3 |
4 | # Set the working directory inside the container
5 | WORKDIR /app
6 |
7 | # Copy the requirements.txt file into the container
8 | COPY requirements.txt ./
9 |
10 | # Install any needed packages specified in requirements.txt
11 | RUN apt-get update && apt-get install -y nano && \
12 | pip install --trusted-host pypi.python.org -r requirements.txt
13 |
14 | # Copy the rest of your project's source code into the container
15 | COPY . .
16 |
17 | # Make port 5000 available to the world outside this container
18 | EXPOSE 5005
19 |
20 | # Define environment variable
21 | ENV FLASK_APP=app.py
22 |
23 | # Run your application when the container launches
24 | CMD ["flask", "run", "--host=0.0.0.0", "--port=5005"]
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | This is a tradingview webhook designed to be free & open source. This bot is written using Python & Flask and is designed to run a free heroku server. It will allow you to create custom alerts in tradingview and send them to your own private webhook server that can place trades on your account via the api.
5 |
6 | #### Support can be requested in our Discord:
7 |
8 | https://discord.gg/Qb9unmxD6D
9 |
10 | #### Current Exchanges
11 | - [Bybit](https://partner.bybit.com/b/webhookbot)
12 | - [Binance Futures](https://www.binance.com/en/register?ref=LMFD8MJ5)
13 | - More will be done on request or can be added by submitting a pull request.
14 |
15 |
16 |
17 | ***Help keep this tool free by creating a new account using our referral links below:***
18 | -------------------
19 | [Tradingview](https://www.tradingview.com/?offer_id=10&aff_id=9584)
20 |
21 | [Create Bybit Account](https://partner.bybit.com/b/webhookbot)
22 |
23 | [Create Binance Futures Account](https://www.binance.com/en/register?ref=LMFD8MJ5)
24 |
25 |
26 |
27 |
28 |
29 | #### Video Tutorial:
30 | https://youtu.be/VX68RrMvM5Q
31 |
32 |
33 |
34 |
35 |
36 | # How to Webhook Server on Heroku
37 |
38 | 1.) Clone Project to Desktop
39 |
40 | 2.) [Create a Heroku Account](https://www.heroku.com/)
41 |
42 | *Heroku was free, now costs only $7 but is worth it... you are investing you money here and you want to do it right.*
43 |
44 | 3.) Edit config.json to add your own api keys & add a custom key to protect the server.
45 |
46 | ```You need to create new keys on Bybit & give them the correct acess to trade and see token balance```
47 |
48 | 4.) Open a terminal in the cloned directory:
49 |
50 | 5.) Install Heroku CLI so you can work connect you your webserver.
51 |
52 | https://cli-assets.heroku.com/heroku-x64.exe
53 |
54 |
55 | 6.) Submit the following lines into the terminal and press ENTER after each one to procces the code:
56 |
57 |
58 | ``git init``
59 |
60 | ``heroku login``
61 |
62 | ``heroku create --region eu tv-trader-yourservernamehere``
63 |
64 | ``git add .``
65 |
66 | ``git commit -m "Initial Commit"``
67 |
68 | ``git push heroku master``
69 |
70 |
71 | ***Anytime you need to make a change to the code or the API keys, you can push a new build to Heroku:***
72 |
73 | ``git add .``
74 |
75 | ``git commit -m "Update"``
76 |
77 | ``git push heroku master``
78 |
79 | # How to send alerts from TradingView to your new Webserver
80 |
81 | After starting you server, you shoudl see an address that will allow you to access it like below:
82 |
83 | [https://tv-trader-gnome.herokuapp.com/webhook](https://tv-trader-gnome.herokuapp.com/webhook)
84 |
85 | #### You will want to add this when you create a new alert like show below:
86 |
87 | 
88 |
89 | #### You will then want to create you syntax based on the format shownb below and place it in the alert msg field
90 |
91 | 
92 |
93 |
94 | _Now when your alerts fire off they should go strait to your server and get proccessed on the exchange almost instantly!_
95 |
96 |
97 | # TradingView Alerts Format
98 |
99 | ```
100 | {
101 | "key": "678777",
102 | "exchange": "bybit",
103 | "symbol": "ETHUSD",
104 | "type": "Market",
105 | "side": "Buy",
106 | "qty": "1",
107 | "price": "1120",
108 | "close_position": "False",
109 | "cancel_orders": "True",
110 | "order_mode": "Both",
111 | "take_profit_percent": "1",
112 | "stop_loss_percent": "0.5"
113 | }
114 | ```
115 |
116 |
117 |
118 | ---
119 | | Constant |Settings Keys |
120 | |--|--|
121 | |key| unique key that protects your webhook server|
122 | |exchange | bybit, binacne-futures |
123 | |symbol | Exchange Specific ** See Below for more |
124 | |side|Buy or Sell |
125 | |type | Market or Limit |
126 | |order_mode| Both(Stop Loss & Take Profit Orders Used), Profit ( Omly Take Profit Orders), Stop (Only Stop Loss orders)|
127 | |qty| amount of base currency to buy |
128 | |price| ticker in quote currency |
129 | |close_position| True or False |
130 | |cancel_orders|True or False |
131 | |take_profit_percent| any float (0.5) |
132 | |stop_loss_Percent |and float (0.5) |
133 |
134 |
135 | #### ** SYMBOLS
136 | | EXCHANGE | SYMBOL EXAMPLE |
137 | |--|--|
138 | |BYBIT INVERSE| BTCUSD|
139 | |BYBIT PERP | BTCUSDT|
140 | |Binance Futures | BTC/USDT|
141 |
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | import json
2 | from flask import Flask, render_template, request, jsonify
3 | from pybit import HTTP
4 | import time
5 | import ccxt
6 | from binanceFutures import Bot
7 |
8 | def validate_bybit_api_key(session):
9 | try:
10 | result = session.get_api_key_info()
11 | return True
12 | except Exception as e:
13 | print("Bybit API key validation failed:", str(e))
14 | return False
15 |
16 | def validate_binance_api_key(exchange):
17 | try:
18 | result = exchange.fetch_balance()
19 | return True
20 | except Exception as e:
21 | print("Binance API key validation failed:", str(e))
22 | return False
23 |
24 | app = Flask(__name__)
25 |
26 | # load config.json
27 | with open('config.json') as config_file:
28 | config = json.load(config_file)
29 |
30 | ###############################################################################
31 | #
32 | # This Section is for Exchange Validation
33 | #
34 | ###############################################################################
35 |
36 | use_bybit = False
37 | if 'BYBIT' in config['EXCHANGES']:
38 | if config['EXCHANGES']['BYBIT']['ENABLED']:
39 | print("Bybit is enabled!")
40 | use_bybit = True
41 |
42 | session = HTTP(
43 | endpoint='https://api.bybit.com',
44 | api_key=config['EXCHANGES']['BYBIT']['API_KEY'],
45 | api_secret=config['EXCHANGES']['BYBIT']['API_SECRET']
46 | )
47 |
48 | use_binance_futures = False
49 | if 'BINANCE-FUTURES' in config['EXCHANGES']:
50 | if config['EXCHANGES']['BINANCE-FUTURES']['ENABLED']:
51 | print("Binance is enabled!")
52 | use_binance_futures = True
53 |
54 | exchange = ccxt.binance({
55 | 'apiKey': config['EXCHANGES']['BINANCE-FUTURES']['API_KEY'],
56 | 'secret': config['EXCHANGES']['BINANCE-FUTURES']['API_SECRET'],
57 | 'options': {
58 | 'defaultType': 'future',
59 | },
60 | 'urls': {
61 | 'api': {
62 | 'public': 'https://testnet.binancefuture.com/fapi/v1',
63 | 'private': 'https://testnet.binancefuture.com/fapi/v1',
64 | }, }
65 | })
66 | exchange.set_sandbox_mode(True)
67 |
68 | # Validate Bybit API key
69 | if use_bybit:
70 | if not validate_bybit_api_key(session):
71 | print("Invalid Bybit API key.")
72 | use_bybit = False
73 |
74 | # Validate Binance Futures API key
75 | if use_binance_futures:
76 | if not validate_binance_api_key(exchange):
77 | print("Invalid Binance Futures API key.")
78 | use_binance_futures = False
79 |
80 | @app.route('/')
81 | def index():
82 | return {'message': 'Server is running!'}
83 |
84 | @app.route('/webhook', methods=['POST'])
85 | def webhook():
86 | print("Hook Received!")
87 | data = json.loads(request.data)
88 | print(data)
89 |
90 | if int(data['key']) != config['KEY']:
91 | print("Invalid Key, Please Try Again!")
92 | return {
93 | "status": "error",
94 | "message": "Invalid Key, Please Try Again!"
95 | }
96 |
97 | ##############################################################################
98 | # Bybit
99 | ##############################################################################
100 | if data['exchange'] == 'bybit':
101 |
102 | if use_bybit:
103 | if data['close_position'] == 'True':
104 | print("Closing Position")
105 | session.close_position(symbol=data['symbol'])
106 | else:
107 | if 'cancel_orders' in data:
108 | print("Cancelling Order")
109 | session.cancel_all_active_orders(symbol=data['symbol'])
110 | if 'type' in data:
111 | print("Placing Order")
112 | if 'price' in data:
113 | price = data['price']
114 | else:
115 | price = 0
116 |
117 |
118 | if data['order_mode'] == 'Both':
119 | take_profit_percent = float(data['take_profit_percent'])/100
120 | stop_loss_percent = float(data['stop_loss_percent'])/100
121 | current_price = session.latest_information_for_symbol(symbol=data['symbol'])['result'][0]['last_price']
122 | if data['side'] == 'Buy':
123 | take_profit_price = round(float(current_price) + (float(current_price) * take_profit_percent), 2)
124 | stop_loss_price = round(float(current_price) - (float(current_price) * stop_loss_percent), 2)
125 | elif data['side'] == 'Sell':
126 | take_profit_price = round(float(current_price) - (float(current_price) * take_profit_percent), 2)
127 | stop_loss_price = round(float(current_price) + (float(current_price) * stop_loss_percent), 2)
128 |
129 | print("Take Profit Price: " + str(take_profit_price))
130 | print("Stop Loss Price: " + str(stop_loss_price))
131 |
132 | session.place_active_order(symbol=data['symbol'], order_type=data['type'], side=data['side'],
133 | qty=data['qty'], time_in_force="GoodTillCancel", reduce_only=False,
134 | close_on_trigger=False, price=price, take_profit=take_profit_price, stop_loss=stop_loss_price)
135 |
136 | elif data['order_mode'] == 'Profit':
137 | take_profit_percent = float(data['take_profit_percent'])/100
138 | current_price = session.latest_information_for_symbol(symbol=data['symbol'])['result'][0]['last_price']
139 | if data['side'] == 'Buy':
140 | take_profit_price = round(float(current_price) + (float(current_price) * take_profit_percent), 2)
141 | elif data['side'] == 'Sell':
142 | take_profit_price = round(float(current_price) - (float(current_price) * take_profit_percent), 2)
143 |
144 | print("Take Profit Price: " + str(take_profit_price))
145 | session.place_active_order(symbol=data['symbol'], order_type=data['type'], side=data['side'],
146 | qty=data['qty'], time_in_force="GoodTillCancel", reduce_only=False,
147 | close_on_trigger=False, price=price, take_profit=take_profit_price)
148 | elif data['order_mode'] == 'Stop':
149 | stop_loss_percent = float(data['stop_loss_percent'])/100
150 | current_price = session.latest_information_for_symbol(symbol=data['symbol'])['result'][0]['last_price']
151 | if data['side'] == 'Buy':
152 | stop_loss_price = round(float(current_price) - (float(current_price) * stop_loss_percent), 2)
153 | elif data['side'] == 'Sell':
154 | stop_loss_price = round(float(current_price) + (float(current_price) * stop_loss_percent), 2)
155 |
156 | print("Stop Loss Price: " + str(stop_loss_price))
157 | session.place_active_order(symbol=data['symbol'], order_type=data['type'], side=data['side'],
158 | qty=data['qty'], time_in_force="GoodTillCancel", reduce_only=False,
159 | close_on_trigger=False, price=price, stop_loss=stop_loss_price)
160 |
161 | else:
162 | session.place_active_order(symbol=data['symbol'], order_type=data['type'], side=data['side'],
163 | qty=data['qty'], time_in_force="GoodTillCancel", reduce_only=False,
164 | close_on_trigger=False, price=price)
165 |
166 | return {
167 | "status": "success",
168 | "message": "Bybit Webhook Received!"
169 | }
170 | ##############################################################################
171 | # Binance Futures
172 | ##############################################################################
173 | if data['exchange'] == 'binance-futures':
174 | if use_binance_futures:
175 | bot = Bot()
176 | bot.run(data)
177 | return {
178 | "status": "success",
179 | "message": "Binance Futures Webhook Received!"
180 | }
181 |
182 | else:
183 | print("Invalid Exchange, Please Try Again!")
184 | return {
185 | "status": "error",
186 | "message": "Invalid Exchange, Please Try Again!"
187 | }
188 |
189 | if __name__ == '__main__':
190 | app.run(debug=False)
191 |
192 |
193 |
--------------------------------------------------------------------------------
/binanceFutures.py:
--------------------------------------------------------------------------------
1 | import json
2 | import time
3 | import ccxt
4 | import random
5 | import string
6 |
7 | with open('config.json') as config_file:
8 | config = json.load(config_file)
9 |
10 |
11 | if config['EXCHANGES']['binance-futures']['TESTNET']:
12 | exchange = ccxt.binance({
13 | 'apiKey': config['EXCHANGES']['binance-futures']['API_KEY'],
14 | 'secret': config['EXCHANGES']['binance-futures']['API_SECRET'],
15 | 'options': {
16 | 'defaultType': 'future',
17 | },
18 | 'urls': {
19 | 'api': {
20 | 'public': 'https://testnet.binancefuture.com/fapi/v1',
21 | 'private': 'https://testnet.binancefuture.com/fapi/v1',
22 | }, }
23 | })
24 | exchange.set_sandbox_mode(True)
25 | else:
26 | exchange = ccxt.binance({
27 | 'apiKey': config['EXCHANGES']['binance-futures']['API_KEY'],
28 | 'secret': config['EXCHANGES']['binance-futures']['API_SECRET'],
29 | 'options': {
30 | 'defaultType': 'future',
31 | },
32 | 'urls': {
33 | 'api': {
34 | 'public': 'https://fapi.binance.com/fapi/v1',
35 | 'private': 'https://fapi.binance.com/fapi/v1',
36 | }, }
37 | })
38 |
39 | class Bot:
40 |
41 | def __int__(self):
42 | pass
43 |
44 | def create_string(self):
45 | N = 7
46 | # using random.choices()
47 | # generating random strings
48 | res = ''.join(random.choices(string.ascii_uppercase +
49 | string.digits, k=N))
50 | baseId = 'x-40PTWbMI'
51 | self.clientId = baseId + str(res)
52 | return
53 |
54 | def close_position(self, symbol):
55 | position = exchange.fetch_positions(symbol)[0]['info']['positionAmt']
56 | self.create_string()
57 | params = {
58 | "newClientOrderId": self.clientId,
59 | 'reduceOnly': True
60 | }
61 | if float(position) > 0:
62 | print("Closing Long Position")
63 | exchange.create_order(symbol, 'Market', 'Sell', float(position), price=None, params=params)
64 | else:
65 | print("Closing Short Position")
66 | exchange.create_order(symbol, 'Market', 'Buy', -float(position), price=None, params=params)
67 |
68 | def set_risk(self, symbol, data, stop_loss, take_profit):
69 | position = exchange.fetch_positions(symbol)
70 | print(position)
71 | price = float(position[0]['info']['entryPrice'])
72 | size = abs(float(position[0]['info']['positionAmt']))
73 | markPrice = float(exchange.fetch_ticker(data['symbol'])['last'])
74 |
75 | if data['order_mode'] == 'Both':
76 | if data['side'] == 'Buy':
77 | self.create_string()
78 | exchange.create_order(symbol, 'STOP_MARKET', 'Sell', size, params={
79 | "newClientOrderId": self.clientId,
80 | 'reduceOnly': True,
81 | 'stopPrice': stop_loss,
82 | })
83 | self.create_string()
84 | exchange.create_order(symbol, 'TAKE_PROFIT', 'Sell', size, params={
85 | "newClientOrderId": self.clientId,
86 | 'reduceOnly': True,
87 | 'stopPrice': take_profit,
88 | })
89 | else:
90 | self.create_string()
91 | exchange.create_order(symbol, 'STOP_MARKET', 'Buy', size, params={
92 | "newClientOrderId": self.clientId,
93 | 'reduceOnly': True,
94 | 'stopPrice': stop_loss,
95 | })
96 | self.create_string()
97 | exchange.create_order(symbol, 'TAKE_PROFIT', 'Buy', size, take_profit, params={
98 | "newClientOrderId": self.clientId,
99 | 'reduceOnly': True,
100 | 'stopPrice': take_profit,
101 | })
102 |
103 | elif data['order_mode'] == 'Profit':
104 | if data['side'] == 'Buy':
105 | self.create_string()
106 | exchange.create_order(symbol, 'TAKE_PROFIT', 'Sell', size, params={
107 | "newClientOrderId": self.clientId,
108 | 'reduceOnly': True,
109 | 'stopPrice': take_profit,
110 | })
111 | else:
112 | self.create_string()
113 | exchange.create_order(symbol, 'TAKE_PROFIT', 'Buy', size, take_profit, params={
114 | "newClientOrderId": self.clientId,
115 | 'reduceOnly': True,
116 | 'stopPrice': take_profit,
117 | })
118 | elif data['order_mode'] == 'Stop':
119 | if data['side'] == 'Buy':
120 | self.create_string()
121 | exchange.create_order(symbol, 'STOP_MARKET', 'Sell', size, params={
122 | "newClientOrderId": self.clientId,
123 | 'reduceOnly': True,
124 | 'stopPrice': stop_loss,
125 | })
126 | else:
127 | self.create_string()
128 | exchange.create_order(symbol, 'STOP_MARKET', 'Buy', size, params={
129 | "newClientOrderId": self.clientId,
130 | 'reduceOnly': True,
131 | 'stopPrice': stop_loss,
132 | })
133 |
134 |
135 |
136 |
137 | def run(self, data):
138 | print(data['close_position'])
139 | if data['close_position'] == 'True':
140 | print("Closing Position")
141 | self.close_position(symbol=data['symbol'])
142 | else:
143 | if 'cancel_orders' in data:
144 | print("Cancelling Order")
145 | exchange.cancel_all_orders(symbol=data['symbol'])
146 | if 'type' in data:
147 | print("Placing Order")
148 | if 'price' in data:
149 | price = data['price']
150 | else:
151 | price = 0
152 |
153 | if data['order_mode'] == 'Both':
154 | take_profit_percent = float(data['take_profit_percent']) / 100
155 | stop_loss_percent = float(data['stop_loss_percent']) / 100
156 | current_price = exchange.fetch_ticker(data['symbol'])['last']
157 | if data['side'] == 'Buy':
158 | take_profit_price = round(float(current_price) + (float(current_price) * take_profit_percent),
159 | 2)
160 | stop_loss_price = round(float(current_price) - (float(current_price) * stop_loss_percent), 2)
161 | elif data['side'] == 'Sell':
162 | take_profit_price = round(float(current_price) - (float(current_price) * take_profit_percent),
163 | 2)
164 | stop_loss_price = round(float(current_price) + (float(current_price) * stop_loss_percent), 2)
165 |
166 | print("Take Profit Price: " + str(take_profit_price))
167 | print("Stop Loss Price: " + str(stop_loss_price))
168 |
169 | self.create_string()
170 | params = {
171 | "newClientOrderId": self.clientId,
172 | 'reduceOnly': False
173 | }
174 | if data['type'] == 'Limit':
175 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
176 | price=float(price), params=params)
177 | else:
178 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
179 | params=params)
180 |
181 | self.set_risk(data['symbol'], data, stop_loss_price, take_profit_price)
182 |
183 |
184 | elif data['order_mode'] == 'Profit':
185 | take_profit_percent = float(data['take_profit_percent']) / 100
186 | current_price = exchange.fetch_ticker(data['symbol'])['last']
187 |
188 | if data['side'] == 'Buy':
189 | take_profit_price = round(float(current_price) + (float(current_price) * take_profit_percent),
190 | 2)
191 | elif data['side'] == 'Sell':
192 | take_profit_price = round(float(current_price) - (float(current_price) * take_profit_percent),
193 | 2)
194 |
195 | print("Take Profit Price: " + str(take_profit_price))
196 |
197 | self.create_string()
198 | params = {
199 | "newClientOrderId": self.clientId,
200 | 'reduceOnly': False
201 | }
202 |
203 | if data['type'] == 'Limit':
204 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
205 | price=float(price), params=params)
206 | else:
207 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
208 | params=params)
209 |
210 | self.set_risk(data['symbol'], data, 0, take_profit_price)
211 |
212 |
213 | elif data['order_mode'] == 'Stop':
214 | stop_loss_percent = float(data['stop_loss_percent']) / 100
215 | current_price = exchange.fetch_ticker(data['symbol'])['last']
216 |
217 | if data['side'] == 'Buy':
218 | stop_loss_price = round(float(current_price) - (float(current_price) * stop_loss_percent), 2)
219 | elif data['side'] == 'Sell':
220 | stop_loss_price = round(float(current_price) + (float(current_price) * stop_loss_percent), 2)
221 |
222 | print("Stop Loss Price: " + str(stop_loss_price))
223 |
224 | self.create_string()
225 | params = {
226 | "newClientOrderId": self.clientId,
227 | 'reduceOnly': False
228 | }
229 |
230 | if data['type'] == 'Limit':
231 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
232 | price=float(price), params=params)
233 | else:
234 | exchange.create_order(data['symbol'], data['type'], data['side'], float(data['qty']),
235 | params=params)
236 |
237 | self.set_risk(data['symbol'], data, stop_loss_price, 0)
238 |
239 | else:
240 | return {
241 | 'status': 'error'
242 | }
243 |
--------------------------------------------------------------------------------