Xero has changed its API to require OAuth2 connections instead of OAuth1.
I had a working solution in OAuth1, but the examples for Oauth2 are scarce at best, but mainly for web sites.
I'm another developer who managed to create an Oauth1 solution, that successfully worked as a machine to machine solution with no web server involved.
Xero has some examples that run in Postman, that easily work in my test environment.
I'm attempting to reproduce the postman action of refreshing the token in Python3.
My basic code below is where I currently am:
#client_id = "xxxxx"
#client_secret = "xxxxx"
callback_url = "https://api.xero.com/connections"
re_directURI = "https://developer.xero.com"
scopes = "offline_access accounting.contacts accounting.transactions"
refresh_url = "https://identity.xero.com/connect/token"
access_token = open('AccessToken.txt').read()
old_refresh_token = open('RefreshToken.txt','r').read()
# Refresh Token code...
import requests
#def refresh_xero_token(refresh_token):
headers = {
'grant_type': 'refresh_token',
'Content-Type': 'application/json',
}
data = {
'grant_type': 'refresh_token',
'refresh_token': old_refresh_token,
'client_id': client_id,
'client_secret': client_secret
}
print(data,headers)
response = requests.post(refresh_url, headers=headers, data=data)
#return response.json()
print(response.text)
I have so far failed to find an example that works without a web server, just using python to communicate with the Xero servers to transfer local data into the Zero API.
Using xoauth,.exe (windows) to get the access_token, and then in postman I can run the examples for refresh token, connections, invoices etc to the demo company.
and I believe just being able to replicate these examples will provide me with what I need to get a working solution.
currently with this python code I only get
{"error":"invalid_request"}
So, clearly I am missing something.
I'll class myself as a newbie with Python or Oauth2, but have chosen this path due to my previous success with an Oauth1 connected solution.
I would ask the Xero developer community, but I'm writing this for a user of our software to push data into their Xero accounts, and so for testing I only have a trial account, which does not give me access to the Xero developer community.
Which by itself is really annoying.
Xero support seems of little use also, I tried.
If there is anyone out there able to assist, that would be fabulous.
Thank you in advance for any help given.
After using the xoauth application and setting up the connection, I have since found that with the refresh token, running this function keeps the connection up and running.
def refresh_xero_token():
refresh_url = "https://identity.xero.com/connect/token"
old_refresh_token = open('RefreshToken.txt','r').read()
tokenb4 = f"{client_id}:{client_secret}"
basic_token = base64.urlsafe_b64encode(tokenb4.encode()).decode()
headers = {
'Authorization': f"Basic {basic_token}",
'Content-Type': 'application/x-www-form-urlencoded',
}
data = {
'grant_type': 'refresh_token',
'refresh_token': old_refresh_token
}
try:
response = requests.post(refresh_url, headers=headers, data=data)
results = response.json()
open('RefreshToken.txt','w').write(results["refresh_token"])
open('AccessToken.txt','w').write(results["access_token"])
except Exception as err:
print("ERROR ! Refreshing token error?")
print(response.text)
As additional information, I can then also use this connection to, for example create a contact in Xero:
In this example the irContact is a SQLAlchemy row of data from a SQL table.
def create_contact( connection, irContact, access_token):
#Setup new contact
address1 = {"AddressType": "POBOX"}
if irContact['addressline1'] is not None: address1.update({"AddressLine1": irContact['addressline1']})
if irContact['addressline2'] is not None: address1.update({"AddressLine2": irContact['addressline2']})
if irContact['addressline3'] is not None: address1.update({"AddressLine3": irContact['addressline3']})
if irContact['addressline4'] is not None: address1.update({"AddressLine4": irContact['addressline4']})
if irContact['city'] is not None: address1.update({"City": irContact['city']})
if irContact['region'] is not None: address1.update({"Region": irContact['region']})
if irContact['postalcode'] is not None: address1.update({"PostalCode": irContact['postalcode']})
if irContact['country'] is not None: address1.update({"Country": irContact['country']})
if irContact['attentionto'] is not None: address1.update({"AttentionTo": irContact['attentionto']})
#print (address1.values())
addresses = []
addresses.append(address1)
phones = []
if irContact['phonenumber'] is not None:
phone1 = {"PhoneType": "DEFAULT"}
#phone1.update({"PhoneType": "DEFAULT"})
if irContact['phonenumber'] is not None: phone1.update({"PhoneNumber": irContact['phonenumber']})
if irContact['phoneareacode'] is not None: phone1.update({"PhoneAreaCode": irContact['phoneareacode']})
if irContact['phonecountrycode'] is not None: phone1.update({"PhoneCountryCode": irContact['phonecountrycode']})
phones.append(phone1)
#print (phone1.values())
if irContact['mobilenumber'] is not None:
phone2 = {"PhoneType": "MOBILE"}
if irContact['phonenumber'] is not None: phone2.update({"PhoneNumber": irContact['mobilenumber']})
if irContact['phoneareacode'] is not None: phone2.update({"PhoneAreaCode": irContact['mobileareacode']})
if irContact['phonecountrycode'] is not None: phone2.update({"PhoneCountryCode": irContact['mobilecountrycode']})
phones.append(phone2)
#print (phone2.values())
contact = { "Name": irContact['name'],
"ContactNumber": irContact['contactnumber'],
"AccountNumber": irContact['accountnumber'],
#"ContactStatus": "ACTIVE",
"FirstName": irContact['firstname'],
"LastName": irContact['lastname'],
#"EmailAddress": irContact['emailaddress'],
"Addresses": addresses,
#"Phones":phones
}
contacts = [contact]
#print(contacts)
contacts_url = "https://api.xero.com/api.xro/2.0/Contacts"
headers = {
'Authorization': f"Bearer {access_token}",
'Accept': 'application/json',
'Content-Type': 'application/json',
'xero-tenant-id': tenant_id,
}
data = {
'Contacts': [contact],
}
#print(data)
try:
response = requests.post(contacts_url, headers=headers, json=data)
except Exception as err:
print("ERROR! Contact: %s" % (str(err) ))
print(response.text)
return 0
#print(response.text)
results = response.json()
if 'Contacts' in results:
newcontacts = results['Contacts']
for newcontact in newcontacts: break
query = "update xero_contact set errortext='', ContactID='%s' where id=%d" % (newcontact["ContactID"], irContact['id'])
connection.execute(query)
query = "update xero_invoice_header set ContactID='%s' where OurContactID=%d and (InvoiceID='' or InvoiceID is null ) " % ( newcontact["ContactID"], irContact['id'] )
connection.execute(query)
I believe, with this amount of information, anyone can be capable of creating their own Xero machine to machine interface...
Realising that other records can be read and created using minimal tweaks to the header and or data element of the requests call.
I found the lack of this information so very frustrating, if people can find this, it may help them in future.
Related
I have a Django App, and I am trying to use Auth0 for my Django App's authentication. I have successfully integrated Login and Logout using social-auth-app-django library. But I am facing a lot of hurdles in implementing Auth0's Management API to use direct password change feature in Django? I tried to follow what this Auth0's official documentation's link says to implement Auth0's Management API for direct password change feature, but it is throwing Error Code 500, and I am not understanding what I did wrong.
The link shows the following example to implement Auth0 Management API for firect password change feature in Django:
import http.client
conn = http.client.HTTPSConnection("")
payload = "{\"password\": \"NEW_PASSWORD\",\"connection\": \"CONNECTION_NAME\"}"
headers = { 'content-type': "application/json" }
conn.request("PATCH", "/dev-9fj5kydc.us.auth0.com/api/v2/users/USER_ID", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
And my implementation for the same is:
import http.client
user = self.request.user
NEW_PASSWORD = form.cleaned_data["new_password1"]
CONNECTION_NAME = settings.AUTH0_CONNECTION_NAME
DOMAIN = settings.SOCIAL_AUTH_AUTH0_DOMAIN
USER_ID = user.auth0_id
URL = f"/{DOMAIN}/api/v2/users/{USER_ID}"
conn = http.client.HTTPSConnection("")
payload = f"{{\"password\": \"{NEW_PASSWORD}\",\"connection\": \"{CONNECTION_NAME}\"}}" # Here I am using 'Username-Password-Authentication' as Connection Name
headers = { 'content-type': "application/json" }
conn.request("PATCH", URL, payload, headers)
res = conn.getresponse()
data = res.read()
I have double checked that everything is fine by printing values in console and all variables hold correct values. Can someone help me and tell what wrong I am doing?
New-ish to python and I'm stumped on this. I literally cannot seem to find a clear and correct answer anywhere online. I honestly feel stupid because this is a pretty basic python skill.
My issue:
I've been tasked with writing a python test suite of asyncronous api requests chained together. I need to fire the requests, save values from the response json, then pass those values into subsequent requests.
Below is a list of what I WANT the test to do, and what I would expect it to do based on how I've written the test file:
Send GET to /users endpoint
Save first customerId from response payload as customer_id
Send POST to /customer/{customer_id}/addresses endpoint to create new customer address
Save addressId from response payload as address_id
Send PUT to /orders/{customer_id} endpoint to update order with the address_id from step #3.
Example Code:
(This is the same code I have on my machine, but I've sanitized the file with fake info.
import requests
import json
def first_request_get(self):
url = 'https://api.example.net/users'
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer asdfasdfasdfasdfasdfasdfasdf'
}
payload = {}
resp = requests.get(url, headers=headers, data=json.dumps(payload))
json_str = json.dumps(payload)
resp_body = resp.json()
customer_id = resp_body['customer'][0]['id']
assert resp_body['customer'][0]['id'] is not None
assert resp.status_code == 200
return customer_id
def second_request_post(self):
customer_id = self.first_request_get()
url = 'https://api.example.net/customers/{}/addresses'.format(customer_id)
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer asdfasdfasdfasdfasdfasdfasdf'
}
payload = {"address1": "285 Beandip Ln",
"city": "Lincoln",
"province": "Nebraska",
"zip": "68510",
}
resp = requests.post(url, headers=headers, data=json.dumps(payload))
resp_body = resp.json()
address_id = resp_body['address']['id']
print('address_id == ')
print(address_id)
assert resp.status_code == 200
assert resp_body['address']['id'] is not None
return address_id
def third_request_put(self):
address_id = self.second_request_post()
customer_id = self.first_request_get()
url = 'https://api.example.net/orders/{}'.format(customer_id)
headers = {'Content-Type': 'application/json',
'Authorization': 'Bearer asdfasdfasdfasdfasdfasdfasdf'
}
payload = {"address_id": address_id,
"next_charge_scheduled_at": "2022-01-31T00:00:00",
"price": 6.85,
"quantity": 2,
}
resp = requests.put(url, headers=headers, data=json.dumps(payload))
resp_body = resp.json()
subscription_id = resp_body['order']['id']
print('order_id == ')
print(subscription_id)
assert resp_body['order']['id'] is not None
assert resp.status_code == 200
return subscription_id
The result of executing this test file ends up looping through the file in a weird order rather than doing what I would expect it to do. Why do I have to call an earlier function just to access the variable I've already extracted from the json and assigned to a variable in an earlier function?
Unfortunately, it's not advised to design your tests this way.
Testcases should be completely independent of one another and have no side effects. The order of execution should not matter. The Pytest documentation among other sources makes reference to this (and takes steps to disallow violating this principle).
I can also recommend the book "The Art of Software Testing" by Glenford J Myers.
As such, to make this test work, you're going to have to place it all in one method...
If you'd like to be more explicit about the point of failure (even though pytest does a pretty good job already), you'll want to break this test up into multiple cases, but mock the data you're not explicitly testing at each stage.
I'm fairly new to using web APIs and pulling data and i'm also pretty new with python. My goal is to make a stat-tracker app but I keep getting a 401 when I try and pull the data.
I've printed out the entire url just to make sure I didn't get it wrong. I copied and pasted the API key exactly so that shouldn't be a problem
api_token = 'api key in python file'
api_url_base = 'https://public-api.tracker.gg/v2/apex/standard/'
headers = {'Content-Type' : 'application/json',
'Authorization' : 'Bearer {}'.format(api_token)}
def get_player_profile():
api_url = '{}profile/psn/Daltoosh'.format(api_url_base)
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
return json.loads(response.content.decode('utf-8'))
else:
return response.status_code, api_url
print(get_player_profile())
#player_profile = get_player_profile()
#if player_profile is not None:
# print("Career Stats:")
# for k, v in player_profile['profile/psn/Daltoosh'].items():
# print('{0}:{1}.format(k, v)')
#else:
# print('[!] Data Request Failed [!]')
I expected a status code of 200 but there seems to be a problem authenticating.
I'm not too well versed in the web API that you are using, but I think you might be using the API token incorrectly. I don't think that specific API requires a Bearer token, but instead a separate header called TRN-Api-Key.
So maybe write something like this:
headers = {'Content-Type' : 'application/json', 'TRN-Api-Key' : api_token}
If you look here, you should be able to read up on how to set up authentication.
I am trying to make a request to the Proactive Events API by using the requests module of Python.
However I always receive a response that the scope is invalid.
Can anyone help? What am I doing wrong? My code looks like this:
#ask.launch
def launch():
content_type = "application/x-www-form-urlencoded;charset=utf-8"
client_id = "amzn1.application-oa2-client.6a48XXXXXXX408"
client_secret = "592XXXXxxxxxxx6"
scope = "alexa::proactive_events"
grant_type = "client_credentials"
data = {"grant_type": grant_type, "client_id": client_id, "client_secret": client_secret, "scope": scope}
r = requests.post("https://api.amazon.com/auth/O2/token", data=data, headers={"content-type": content_type})
speech = render_template("welcome")
reprompt = render_template("welcome_reprompt")
return question(speech).reprompt(reprompt)
That is the response I get:
{'error_description': 'The request has an invalid parameter : scope', 'error': 'invalid_scope'}
Since one of the reason you get the invalid scope is that you dont have the events included in your skill manifest I include some steps here. I found quite cumbersome to use the SMAPI to update the skill manifest so instead I used ask cli.
install ask-cli: get authorization code for your amazon acount. In my case the backend is not an AWS lambda function but an external web server
get the skill manifest in json format:
ask api get-skill -s "amzn1.ask.skill.ZZZYYYZZ" --stage development > skill.json
Add the notifications permission and the events elements to the manifest:
{
"name": "alexa::devices:all:notifications:write"
}
and
"events": {
"publications": [
{
"eventName": "AMAZON.AAABBBCC"
}
],
"endpoint": {
"uri": "https://XXXYYYZZ:443/whatevercontext"
}
}
update the manifest:
ask api update-skill -s "amzn1.ask.skill.ZZZYYYZZ" --stage development -f skill.json
enable the notifications in the alexa app for your specific skill
Now you should be able to get the token and next step is to send the notification to the device
Have you tried making the API call via any other method? I just tried that with Postman and it worked for me.
My Python's a bit rusty, but here's the self generated code from Postman for Python. May be this should help?
import http.client
conn = http.client.HTTPConnection("api,amazon,com")
payload = "grant_type=client_credentials&client_id=amzn1.application-oa2-client.whatever-value&client_secret=client-secret&scope=alexa%3A%3Aproactive_events&undefined="
headers = {
'Content-Type': "application/x-www-form-urlencoded"
}
conn.request("POST", "auth,O2,token", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
As Karthik asked previously have you tried the call via postman? I first suggest trying to via postman before you trying to code it.
If the issue still persists its most likely because you haven't included the necessary permissions required for Proactive Events in your Skill Manifest.
To add the necessary permissions to Skill Manifest you need to use the Skill Management API & ASK Cli.
Follow this section of the documentation and visit the links referenced there to correctly add the required permissions to your skill - https://developer.amazon.com/docs/smapi/proactive-events-api.html#onboard-smapi
Once you have successfully added the events and publications with a skill schema you should be able to successfully generate a token.
Please feel to ask if you want me to elaborate more on the exact steps.
Cheers!
This is what I have tried and it worked:
amazon_token_url = "https://api.amazon.com/auth/O2/token"
headers = {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
body = "grant_type=client_credentials&client_id=amzn1.application-oa2-client.XXXYYYZZ&client_secret=ZZZYYYXXX&scope=alexa::proactive_events"
log.debug("Sending token request with body: %s", body)
resp = requests.post(amazon_token_url, data=body, headers=headers)
resp_json = json.loads(resp.content.decode('utf-8'))
if (resp.status_code < 299) and (resp.status_code > 199):
log.debug("token received from Amazon")
log.debug("Content : %s", resp.content)
token = resp_json["access_token"]
return token
Quick question: I'm trying to use the Discord API to make a backup of all the messages on a server (or a guild, if you use the official term).
So I implemented OAuth without any problems, I have my access token and I can query some endpoints (I tried /users/#me, /users/#me/guilds). Though, most of them don't work. For example, if I query /users/#me/channels (which is supposed to be the DMs) I get a 401 Unauthorized response from the API. It's the same if I gather a guild id from /users/#me/guilds and then try to list the channels in it with /guilds/guild.id/channels.
The really weird thing is that I do have all the scopes required (I think so, I didn't take the RPC ones since I don't think it's required for what I want to do) and I can't figure it out myself... What is also weird is that on the OAuth authorization screen, I have those two things:
It kind of counterdicts itself... :(
Do you have any ideas you'd like to share ?
Thanks!
Note: I'm using Python but I don't think it's related here, since some endpoints do work with the headers and tokens I have...
Here is my "authentication code":
baseUrl = "https://discordapp.com/api"
def authorize():
scopes = [
"guilds",
"email",
"identify",
"messages.read",
"guilds.join",
"gdm.join",
"connections"
]
urlAuthorize = "{}/oauth2/authorize?client_id={}&scope={}&response_type=code".format(baseUrl, clientid, ('+'.join(scopes)))
pyperclip.copy(urlAuthorize)
code = input("Code: ")
return code
def getAccessToken(code):
url = "{}/oauth2/token".format(baseUrl)
params = {
"client_id" : clientid,
"client_secret" : clientsecret,
"redirect_uri" : "http://localhost",
"grant_type":"authorization_code",
"code" : code,
}
req = requests.post(url, params = params)
return json.loads(req.text)
And the code related to an API request:
def getHeaders():
return {
"Authorization" : "{} {}".format("Bearer", config["accessToken"]),
# "user-agent" : "DiscordBackup/0.0.1"
}
def getRequest(endpoint, asJson = True, additional = None):
url = "{}/{}".format(baseUrl, endpoint)
req = requests.get(url, headers = getHeaders())
print()
print(getHeaders())
print(url)
print(req.text)
if asJson:
return json.loads(req.text)
else:
return req.text
def getMe(): # this works
endpoint = "users/#me"
return getRequest(endpoint)
def getMyDMs(): # this gives me a code 401 Unauthorized
endpoint = "/users/#me/channels"
return getRequest(endpoint)
I came across this post when encountering this issue, and to put it bluntly, there's no way to resolve it.
The messages.read permission is for a local RPC server; https://discordapp.com/developers/docs/topics/oauth2#shared-resources-oauth2-scopes
However, local RPC servers are in private beta and you must sign up/get accepted to use this.
I wanted to create a DM exporter, but that doesn't look likely now.