How to upload an image with the Grist API?

Hi everyone,

I’ve run into a problem uploading images with the API and would appreciate any help on this. Thank you in advance.

I find it confusing because the error message says that I’d need to have a JSON body in the request when the documentation calls for a multipart/form-data body.

I tried using the API console for the same purpose but ended up with the same error so I wonder what’s going on here.

I think the error tells you to authenticate.
So add the “Authorization” header to your request.

Thanks for the idea. I can see why you’ve suggested so.

The thing is I’ve already authenticated my API requests with bearer authentication. Also, I suppose you wouldn’t get any authentication errors in the API console since you have to be logged in first before making those calls.

I do not know if the token is “xxxxxx” out in the example curl call, but at least what i can see in your example the token is missing:


it would also be interesting, if the Authorization header is set in that thing:

I must say that you have a sharp eye for detail, making you an excellent troubleshooting partner.

There’s nothing in the header you suggested for review, for good reason. Software platforms deliberately leave out your authentication info in the response to avoid exposing it in any way.

The API console is no different, which explains why you see the X’s in place of your API key. You could try making your own calls in the console to know what I mean.

Edit: The input screenshot below shows how I’ve authenticated my API requests.

The next thing i would do is to make that request to a listening netcat (or similar) and check if
its really correct.

When i redirect the upload api call in my api client to localhost it looks like this (and would work):

POST /api/docs/redactedfooo/attachments HTTP/1.1
Accept: */*
Authorization: Bearer aredactedredactedredactedredactedredactf
Content-Type: multipart/form-data; boundary=TucL0TS570jxHkU9KyqTQ7cxDrZqzcpM
user-agent: Puppy
accept-encoding: gzip
Content-Length: 150

Content-Disposition: form-data; name="upload"; filename="test.txt"


The only difference i see is, that in the original request i post to “” but i guess “mimer” is your org?

1 Like

Thanks, @enthus1ast . :raised_hands:

Now, what you’ve suggested I do next is something truly out of my wheelhouse since I’m a no-coder.

But I don’t mind doing that to speed things up so long as there are simple how-to guides. I’d be grateful if you could point me to such resources.

Yup, that’s the acronym I’ll be using pending a decision on the project name.

I just did it with (on a linux with netcat installed) :

nc -l 7878

this starts a netcat thats listening on your local tcp port 7878.
Then you point the api call to your localhost:port.
But when the api tool that you’re using is in the web/cloud then you cannot point it directly to your localhost.

But, maybe others have a better idea, this debugging workflow works for me since i mostly work on my own infrastructure, it might not be the best for you.

Thanks for laying out the details but I’ll have to admit you lost me there. :joy:

Still, I appreciate your time and effort in making things clear to me and anyone else following this topic.

Let me come back to this confusing error message:

“Unauthenticated requests require one of the headers ‘Content-Type: application/json’ or ‘X-Requested-With: XMLHttpRequest’”

This is in fact misleading, and I think it’s a misleading message about an issue that’s specific to the API console, making it doubly confusing. The upload call must have, in fact, Content-Type: multipart/form-data. It must also contain Authorization: Bearer <API_KEY> (see REST API usage - Grist Help Center for instructions where to find that).

With these two headers, it should work correctly to upload an attachment (or even several attachments), and get back an array of attachment IDs. These can then be uploaded into cells of type Attachments using values of the form ["L", 3, 4] (to upload attachment IDs 3 and 4). The "L" identifies the value as a list. (Alternatively, setting a value like "3,4" also works; since it gets parsed as a list of numbers.)

1 Like

Thanks for joining the conversation.

Let me assure you that I’ve rechecked those inputs - Authorization header and Content-Type body - multiple times. Yet, I’m still seeing that error message from within

Within the API console, this is the endpoint - Upload attachments to a doc - that generates the error every time I test it by uploading an attachment.

So I think it’s two separate issues.

The API console’s problem is a separate bug. The API console doesn’t actually use the API key: since it’s being used for a signed-in user, it relies on the session cookie for authentication. That works fine for most requests, but doesn’t work for uploads, presumably because of the different content-type and a bug related to that. Then it increases confusion by reporting a misleading error about it. That’s a bug we should fix – I just reported it internally.

The error in Make is probably entirely different. I can’t tell what headers are set from your screenshot, but if the Authorization header is missing or incorrect in any way, you’d see this error as a symptom.

What I recommend trying, if you have access to a terminal, is a curl command:

curl -X 'POST' \
  "<DOC-ID>/attachments" \
  -H 'Authorization: Bearer <API-KEY>' \
  -H 'Content-Type: multipart/form-data' \
  -F 'upload=@<PATH-TO-FILE>'

Thanks for your clear explanation on how the API console works.

OK, I don’t have access to a terminal but here’s what I did. I rechecked my inputs and saw where I went wrong in the Authorization header setup. I should have entered the API Key parameter name as Authorization instead of ApiKey.

I apologize if the mistake added to the confusion, but I’ve managed to upload an attachment after correcting it.