Google Cloud Vision: send an image as Base64
Drop the image, copy the images.annotate body, send it.
Cloud Vision takes the raw Base64 payload only — no data: prefix — in the
image.content field. The tool below emits exactly that string.
100% client-side. The image never leaves your device.
Drop an image to build the annotate payload
or paste from your clipboard · everything stays in your browser
The images.annotate request body
The REST endpoint is POST https://vision.googleapis.com/v1/images:annotate, authenticated
with an API key (?key=YOUR_API_KEY) or an OAuth bearer token. The body is a
requests array; each entry pairs an image with the detections you want:
{
"requests": [{
"image": { "content": "iVBORw0KGgoAAAANS..." },
"features": [
{ "type": "TEXT_DETECTION" },
{ "type": "LABEL_DETECTION", "maxResults": 5 }
]
}]
}
Each features entry names one detection: TEXT_DETECTION /
DOCUMENT_TEXT_DETECTION for OCR, LABEL_DETECTION for scene labels,
FACE_DETECTION, OBJECT_LOCALIZATION, LOGO_DETECTION,
SAFE_SEARCH_DETECTION, and so on. One image can carry several features in a single request.
Raw Base64 only — the prefix causes the error
The content field is a bytes field in the Image proto. JSON has no native bytes
type, so the API uses Base64 as the transport encoding — which means the string you paste must be
nothing but Base64. A data:image/png;base64, prefix is not valid bytes, and the request
fails with a payload/encoding error instead of a helpful hint about the prefix.
This is the opposite of the OpenAI vision API, where the value must be a complete data URI. If you feed both APIs from the same pipeline, split the string at the first comma and keep the right half for Cloud Vision — or let the API payload builder emit both shapes from one encode.
Python example
Standard library plus requests — encode the file, post the body:
import base64
import requests
with open("receipt.png", "rb") as f:
b64 = base64.b64encode(f.read()).decode("utf-8")
resp = requests.post(
"https://vision.googleapis.com/v1/images:annotate",
params={"key": API_KEY},
json={
"requests": [{
"image": {"content": b64},
"features": [{"type": "DOCUMENT_TEXT_DETECTION"}],
}]
},
)
annotation = resp.json()["responses"][0]
print(annotation.get("fullTextAnnotation", {}).get("text", ""))
The official google-cloud-vision client library wraps the same call — pass the bytes and
it handles the Base64 transport for you. The prefix pitfall only bites when you build the JSON by hand.
Limits: 20 MB, 75 megapixels, and when to use a URI instead
Google's documentation caps image requests at 20 MB and 75 megapixels.
Base64 works against you twice: the string is about a third larger than the file, and it all counts
against the request body. For anything large, Google recommends a different route — put the file in a
Cloud Storage bucket (or any reachable HTTPS URL) and pass
"source": { "imageUri": "gs://bucket/receipt.png" } instead of content.
If both fields are present, content takes precedence and the API ignores the
source — worth knowing when a config merge leaves both populated.
Batching several images
One call can carry multiple requests entries — the response mirrors them in order as a
responses array. Two practical notes: inline Base64 for every image multiplies the body
size, and a single malformed entry fails its own response rather than the whole batch, so check each
element for an error key instead of assuming the array is uniform.
Where the encoded string usually breaks
- A leftover prefix. Copying the full data URI from a browser devtools panel and pasting it into
content— thedata:head must go. - Wrapped lines. Some tools emit Base64 with line breaks; raw newlines are invalid inside a JSON string. This tool emits a single line.
- A quoted-inside-quoted body. Assemble JSON with a serializer, not string concatenation, so the Base64 characters (
+/=) land unescaped and intact. - Size creep. A 4 MB scan becomes a 5.3 MB string; two of them plus a verbose features list pushes the request over the cap. The compressor and the size calculator keep you honest.
Why encode in the browser
Documents run through OCR are exactly the material that should not be pasted into an upload-based
converter: invoices, contracts, ID scans, internal reports. This page encodes with the browser's
FileReader API — no upload step exists, which you can verify in the Network tab: converting
fires zero requests.
Related tools and guides
Frequently asked questions
How do I send a Base64 image to Google Cloud Vision?
POST to images:annotate with a requests array; each entry carries the image under image.content as raw Base64 and names its detections in features.
Does Cloud Vision want the data: prefix or raw Base64?
Raw Base64 only — content is a bytes field, so a data: prefix is invalid there. OpenAI is the opposite: it wants the full data URI.
What are the image size limits?
20 MB per image request and 75 megapixels, per Google's documentation. Base64 adds about a third to the byte count; for large files use source.imageUri with a Cloud Storage or HTTPS URI.
What happens if I pass both content and source?
content wins — the Image proto documents that inline bytes take precedence and the source is ignored.
Can I annotate several images in one request?
Yes — multiple entries in the requests array, answered in order by responses. Every inline payload counts against the body size, and each entry can fail independently, so check for per-entry error keys.
Which feature types can I request?
OCR (TEXT_DETECTION, DOCUMENT_TEXT_DETECTION), LABEL_DETECTION, FACE_DETECTION, OBJECT_LOCALIZATION, LOGO_DETECTION, SAFE_SEARCH_DETECTION and more — one entry per feature in the array.