I'm trying to upload a file in an API that just says:
REQUEST
The request body should contain the contents of the file.
https://developer.fortnox.se/documentation/resources/inbox/
What I've tried so far:
headers = {
"Access-Token": settings.FORTNOX_ACCESS_TOKEN,
"Client-Secret": settings.FORTNOX_CLIENT_SECRET,
"Content-Type": "multipart/form-data",
"Accept": "application/json",
}
file = open(invoice.file.path, 'rb').read()
r = requests.post("https://api.fortnox.se/3/inbox", data=file, headers=headers)
This gives me an error:
Ingen fil var uppladdad. (No file was uploaded)
headers = {
"Access-Token": settings.FORTNOX_ACCESS_TOKEN,
"Client-Secret": settings.FORTNOX_CLIENT_SECRET,
"Content-Type": "multipart/form-data",
"Accept": "application/json",
}
h = httplib2.Http()
file = open(invoice.file.path, 'rb').read()
resp, content = h.request('https://api.fortnox.se/3/inbox', "POST", body=file, headers=headers)
This gives me the same error:
Ingen fil var uppladdad. (No file was uploaded)
Are there any other ways to add the file to the request body, or am I doing something wrong here?
Thanks for any help.
I finally got it working, based on the answer by mee. This did the trick:
multipart_data = MultipartEncoder(
fields={
'file': (invoice.file.path, open(invoice.file.path, 'rb'), 'text/plain')
}
)
headers = {
"Access-Token": settings.FORTNOX_ACCESS_TOKEN,
"Client-Secret": settings.FORTNOX_CLIENT_SECRET,
"Content-Type": multipart_data.content_type,
"Accept": "application/json",
}
r = requests.post("https://api.fortnox.se/3/inbox", headers=headers, data=multipart_data)
In my case, I was able to upload file from put request like this:
import requests
from requests_toolbelt.multipart.encoder import MultipartEncoder
def upload_localfile(filepath,server_data):
multipart_data = MultipartEncoder(
fields={
'file': (filepath, open(filepath, 'rb'), 'text/plain')
}
)
response=requests.put(
server_data,
data=multipart_data,
headers={'Content-Type': multipart_data.content_type}
)
You should use this implementation for fortnox:
files = [
('file', ('somename.pdf', data.file -> NamedTemporaryFile in my case))
]
response = requests.post(
url,
headers={"Authorization": f"Bearer {token}",}
data={},
files=files,
)
Related
I am requesting to mindbodyapi to get token with the following code using requests library
def get_staff_token(request):
URL = "https://api.mindbodyonline.com/public/v6/usertoken/issue"
payload = {
'Api-Key': API_KEY,
'SiteId': "1111111",
'Username': 'user#xyz.com',
'Password': 'xxxxxxxx',
}
r = requests.post(url=URL, params=payload)
print(r.text)
return HttpResponse('Done')
gives a response as follows
{"Error":{"Message":"Missing API key","Code":"DeniedAccess"}}
But if I request the following way it works, anybody could tell me, what I am doing wrong on the above code.
conn = http.client.HTTPSConnection("api.mindbodyonline.com")
payload = "{\r\n\t\"Username\": \"username\",\r\n\t\"Password\": \"xxxxx\"\r\n}"
headers = {
'Content-Type': "application/json",
'Api-Key': API_KEY,
'SiteId': site_id,
}
conn.request("POST", "/public/v6/usertoken/issue", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
In the second one, you are passing the API Key in headers and the credentials in the body of the request. In the first, you are sending both the API Key and credentials together in the query string, not the request body. Refer to requests.request() docs
Just use two dictionaries like in your second code and the correct keywords, I think it should work:
def get_staff_token(request):
URL = "https://api.mindbodyonline.com/public/v6/usertoken/issue"
payload = {
'Username': 'user#xyz.com',
'Password': 'xxxxxxxx',
}
headers = {
'Content-Type': "application/json",
'Api-Key': API_KEY,
'SiteId': "1111111",
}
r = requests.post(url=URL, data=payload, headers=headers)
print(r.text)
return HttpResponse('Done')
I'm trying to upload PDF-file in the Xero account using the python request library (POST method) and Xeros FilesAPI said "Requests must be formatted as multipart MIME" and have some required fields (link) but I don't how to do that exactly...If I do GET-request I'm getting the list of files in the Xero account but having a problem while posting the file (POST-request)...
My Code:
post_url = 'https://api.xero.com/files.xro/1.0/Files/'
files = {'file': open('/home/mobin/PycharmProjects/s3toxero/data/in_test_upload.pdf', 'rb')}
response = requests.post(
post_url,
headers={
'Authorization': 'Bearer ' + new_tokens[0],
'Xero-tenant-id': xero_tenant_id,
'Accept': 'application/json',
'Content-type': 'multipart/form-data; boundary=JLQPFBPUP0',
'Content-Length': '1068',
},
files=files,
)
json_response = response.json()
print(f'Uploading Responsoe ==> {json_response}')
print(f'Uploading Responsoe ==> {response}')
Error Mesage/Response:
Uploading Responsoe ==> [{'type': 'Validation', 'title': 'Validation failure', 'detail': 'No file is was attached'}]
Uploading Responsoe ==> <Response [400]>
As I see you're improperly set the boundary. You set it in the headers but not tell to requests library to use custom boundary. Let me show you an example:
>>> import requests
>>> post_url = 'https://api.xero.com/files.xro/1.0/Files/'
>>> files = {'file': open('/tmp/test.txt', 'rb')}
>>> headers = {
... 'Authorization': 'Bearer secret',
... 'Xero-tenant-id': '42',
... 'Accept': 'application/json',
... 'Content-type': 'multipart/form-data; boundary=JLQPFBPUP0',
... 'Content-Length': '1068',
... }
>>> print(requests.Request('POST', post_url, files=files, headers=headers).prepare().body.decode('utf8'))
--f3e21ca5e554dd96430f07bb7a0d0e77
Content-Disposition: form-data; name="file"; filename="test.txt"
--f3e21ca5e554dd96430f07bb7a0d0e77--
As you can see the real boundary (f3e21ca5e554dd96430f07bb7a0d0e77) is different from what was passed in the header (JLQPFBPUP0).
You can actually directly use the requests module to controll boundary like this:
Let's prepare a test file:
$ touch /tmp/test.txt
$ echo 'Hello, World!' > /tmp/test.txt
Test it:
>>> import requests
>>> post_url = 'https://api.xero.com/files.xro/1.0/Files/'
>>> files = {'file': open('/tmp/test.txt', 'rb')}
>>> headers = {
... 'Authorization': 'Bearer secret',
... 'Xero-tenant-id': '42',
... 'Accept': 'application/json',
... 'Content-Length': '1068',
... }
>>> body, content_type = requests.models.RequestEncodingMixin._encode_files(files, {})
>>> headers['Content-type'] = content_type
>>> print(requests.Request('POST', post_url, data=body, headers=headers).prepare().body.decode('utf8'))
--db57d23ff5dee7dc8dbab418e4bcb6dc
Content-Disposition: form-data; name="file"; filename="test.txt"
Hello, World!
--db57d23ff5dee7dc8dbab418e4bcb6dc--
>>> headers['Content-type']
'multipart/form-data; boundary=db57d23ff5dee7dc8dbab418e4bcb6dc'
Here boundary is the same as in the header.
Another alternative is using requests-toolbelt; below example taken from this GitHub issue thread:
from requests_toolbelt import MultipartEncoder
fields = {
# your multipart form fields
}
m = MultipartEncoder(fields, boundary='my_super_custom_header')
r = requests.post(url, headers={'Content-Type': m.content_type}, data=m.to_string())
But it is better not to pass bundary by hand at all and entrust this work to the requests library.
Update:
A minimal working example using Xero Files API and Python request:
from os.path import abspath
import requests
access_token = 'secret'
tenant_id = 'secret'
filename = abspath('./example.png')
post_url = 'https://api.xero.com/files.xro/1.0/Files'
files = {'filename': open(filename, 'rb')}
values = {'name': 'Xero'}
headers = {
'Authorization': f'Bearer {access_token}',
'Xero-tenant-id': f'{tenant_id}',
'Accept': 'application/json',
}
response = requests.post(
post_url,
headers=headers,
files=files,
data=values
)
assert response.status_code == 201
I've tested this with Xero's Files API to upload a file called "helloworld.rtf" in the same directory as my main app.py file.
var1 = "Bearer "
var2 = YOUR_ACCESS_TOKEN
access_token_header = var1 + var2
body = open('helloworld.rtf', 'rb')
mp_encoder = MultipartEncoder(
fields={
'helloworld.rtf': ('helloworld.rtf', body),
}
)
r = requests.post(
'https://api.xero.com/files.xro/1.0/Files',
data=mp_encoder, # The MultipartEncoder is posted as data
# The MultipartEncoder provides the content-type header with the boundary:
headers={
'Content-Type': mp_encoder.content_type,
'xero-tenant-id': YOUR_XERO_TENANT_ID,
'Authorization': access_token_header
}
)
looks like you got it solved. For reference and any future developers who are using the Xero supported package (https://github.com/XeroAPI/xero-python)
We just added the files_api example code to the sample app so the following would upload a file if you were using the Python SDK
https://github.com/XeroAPI/xero-python-oauth2-app/pull/29/files
name = "my-image"
filename= "my-image.jpg"
mime_type = "image/jpg"
with open('my-image.jpg', 'rb') as f:
body = f.read()
try:
file_object = files_api.upload_file(
xero_tenant_id,
name = name,
filename= filename,
mime_type = mime_type,
body=body
)
except AccountingBadRequestException as exception:
json = jsonify(exception.error_data)
else:
json = serialize_model(file_object)
I am trying to upload a file via Python requests and i receive an error code 400 (Bad request)
#Update ticket with upload of CSV file
header_upload_file = {
'Authorization': 'TOKEN id="' + token + '"',
'Content-Type': 'multipart/form-data'
}
files = {
'name': 'file',
'filename': open(main_path + '/temp/test.txt', 'rb'),
'Content-Disposition': 'form-data'
}
response = requests.post(baseurl + '/incidents/number/' + ticket_number + '/attachments/', headers=header_upload_file, data=files, verify=certificate)
If i try via Postman this is successfull using following code.
url = "https://<url>"
payload = "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"file\"; filename=\"C:\\Users\\<filename>\"\r\nContent-Type: text/csv\r\n\r\n\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
headers = {
'content-type': "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW",
'Authorization': "TOKEN id="3e9d095d-a47b-48b5-a0b8-ae8b8ad9ae74"",
'cache-control': "no-cache",
'Postman-Token': "bb155176-b1b8-47a6-8fb3-46f5740cf9e0"
}
response = requests.request("POST", url, data=payload, headers=headers)
print(response.text)
What do i wrong?
You should use the files parameter instead. Also, don't explicitly set Content-Type in the header so that requests can set it for you with a proper boundary:
header_upload_file = {
'Authorization': 'TOKEN id="' + token + '"'
}
response = requests.post(
baseurl + '/incidents/number/' + ticket_number + '/attachments/',
headers=header_upload_file,
files={'file': ('file', open(main_path + '/temp/test.txt', 'rb'), 'text/csv')},
verify=certificate
)
I am trying to upload a photo (jpg image) using the new Google Photos API.
I am able to get an uploadToken but when I try to create the media item I get the following error:
{
"newMediaItemResults": [
{
"uploadToken": "CAIS+QIAkor2Yr4JcvYMMx..... ",
"status": {
"code": 3,
"message": "NOT_IMAGE: There was an error while trying to create this media item."
}
}
]
}
Here is a snippet of my code:
import sys
import json
import requests
pic = 'image.jpg'
fname = 'read_write_token_creds.json'
with open(fname) as f:
data = json.load(f)
tok = data['access_token']
# Step 1 get an upload token
URL = 'https://photoslibrary.googleapis.com/v1/uploads'
headers = {
'Content-type': 'application/octet-stream',
'X-Goog-Upload-File-Name': pic,
'X-Goog-Upload-Protocol': 'raw',
'Authorization': 'Bearer ' + tok,
}
files = {'file': open(pic, 'rb')}
r = requests.post(URL, headers=headers, files=files)
upload_token = r.text
# Step 2
album_id = 'AG.....7u'
URL = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate'
header = {
'Content-type': 'application/json',
'Authorization': 'Bearer ' + tok
}
payload = {
'albumId': album_id,
'newMediaItems': [
{
'description': 'Desc.',
'simpleMediaItem': { 'uploadToken': upload_token }
}
]
}
r = requests.post(URL, headers=header, data=json.dumps(payload))
When I look at r.text from the requests module, I receive the error message which was given at the top of he message.
This worked for me.
How to authenticate user see here https://developers.google.com/photos/library/guides/upload-media
def upload(service, file):
f = open(file, 'rb').read();
url = 'https://photoslibrary.googleapis.com/v1/uploads'
headers = {
'Authorization': "Bearer " + service._http.request.credentials.access_token,
'Content-Type': 'application/octet-stream',
'X-Goog-Upload-File-Name': file,
'X-Goog-Upload-Protocol': "raw",
}
r = requests.post(url, data=f, headers=headers)
print '\nUpload token: %s' % r.content
return r.content
def createItem(service, upload_token, albumId):
url = 'https://photoslibrary.googleapis.com/v1/mediaItems:batchCreate'
body = {
'newMediaItems' : [
{
"description": "test upload",
"simpleMediaItem": {
"uploadToken": upload_token
}
}
]
}
if albumId is not None:
body['albumId'] = albumId;
bodySerialized = json.dumps(body);
headers = {
'Authorization': "Bearer " + service._http.request.credentials.access_token,
'Content-Type': 'application/json',
}
r = requests.post(url, data=bodySerialized, headers=headers)
print '\nCreate item response: %s' % r.content
return r.content
# authenticate user and build service
upload_token = upload(service, './path_to_image.png')
response = createItem(service, upload_token, album['id'])
I'm trying to import contacts into a contact list in Qualtrics. I am using python to do this.
Token = 'MyToken' #when running the code I put in my actual token and id
ContactsID = 'MyContactsID'
data = open('contacts.json', 'rb')
headers = {'X-API-TOKEN': Token, 'Content-Type':'application/json',}
r = requests.post('https://az1.qualtrics.com/API/v3/mailinglists/' + ContactsID +'/contactimports', headers=headers, data=data)
r.text
This code gives me the following error: '{"meta":{"httpStatus":"400 - Bad Request","error":{"errorMessage":"Invalid Content-Type. expected=multipart/form-data found=application/json","errorCode":"RP_0.1"},"requestId":null}}'
I changed the content type to multipart/form-data that it says it is expecting and received the response "413", which qualtrics explains means "The request body was too large. This can also happen in cases where a multipart/form-data request is malformed."
I have tested my json and verified that it is valid. Also, I don't know why the request body would be too large because it's only 13 contacts that I'm trying to import. Any ideas?
With the help of Qualtrics Support, I was eventually able to get the following code to work:
Token = 'MyToken' #when running the code I put in my actual token and id
ContactsID = 'MyContactsID'
url = "https://az1.qualtrics.com/API/v3/mailinglists/" + ContactsID + "/contactimports/"
headers = {
'content-type': "multipart/form-data; boundary=---BOUNDRY",
'x-api-token': "Token"
}
files = {'contacts': ('contacts', open('contacts.json', 'rb'), 'application/json')}
request = requests.post(url, headers=headers, files=files)
print(request.text)
Please note that if you want to use this code, you will need to change "az1" in the URL to your own Qualtrics datacenter ID.
You need to use files = .. for a multipart request:
Token = 'MyToken' #when running the code I put in my actual token and id
ContactsID = 'MyContactsID'
data = open('contacts.json', 'rb')
headers = {'X-API-TOKEN': Token}
r = requests.post('https://az1.qualtrics.com/API/v3/mailinglists/' + ContactsID +'/contactimports',files={"file":data}, headers=headers)
r.text
Once you do requests will take care of the rest:
In [36]: url = 'http://httpbin.org/post'
In [37]: headers = {'X-API-TOKEN': "123456789"}
In [38]: files = {'file': open('a.csv', 'rb')}
In [39]: r = requests.post(url, files=files, headers=headers)
In [40]: print r.text
{
"args": {},
"data": "",
"files": {
"file": "a,b,c\n1,2,3"
},
"form": {},
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate",
"Content-Length": "152",
"Content-Type": "multipart/form-data; boundary=3830dbe5fa6141f69d3d85dee4ba6e78",
"Host": "httpbin.org",
"User-Agent": "python-requests/2.10.0",
"X-Api-Token": "123456789"
},
"json": null,
"origin": "51.171.98.185",
"url": "http://httpbin.org/post"
}
In [41]: print(r.request.body)
--3830dbe5fa6141f69d3d85dee4ba6e78
Content-Disposition: form-data; name="file"; filename="a.csv"
a,b,c
1,2,3
--3830dbe5fa6141f69d3d85dee4ba6e78--
looking at the docs, you actually want something closer to:
Token = 'MyToken' #when running the code I put in my actual token and id
ContactsID = 'MyContactsID'
data = open('contacts.json', 'rb')
files = {'file': ('contact', data ,'application/json', {'X-API-TOKEN': Token})}
r = requests.post('https://az1.qualtrics.com/API/v3/mailinglists/' + ContactsID +'/contactimports',files=files)