Get an upload URL
https://api.granska.cloud/v1/upload-urlIssues a job id and a pre-signed URL to upload a PDF to, for documents too large to inline.
Use this, not base64
There are three ways to give the engine a document, and this is the one to build against. The other
two — pdfBase64 and pdfUrl on POST /v1/analyze — exist and work, but they make the analysis
call carry the document.
That matters because POST /v1/analyze is meant to answer in milliseconds. When the document arrives
inline as base64, the gateway has to receive the whole encoded string, decode it and store it before
it can queue anything; when it arrives as a URL, the gateway has to fetch it first. Both turn a queue
operation into a transfer, use far more memory, and put a file-sized transfer inside a request with an
HTTP timeout on it. A large document is exactly where that goes wrong, and a large document is the
normal case here.
The two-step flow moves the transfer off the API entirely. You ask for a URL, you PUT the file
straight at cloud storage, and the analysis call then carries a job id and nothing else.
curl -X POST https://api.granska.cloud/v1/upload-url \
-H "Authorization: Bearer $TOKEN"
# Then PUT the document straight at the returned uploadUrl:
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/pdf" \
--data-binary @utredning.pdf{
"success": true,
"jobId": "job_7d41c9",
"uploadUrl": "https://storage.googleapis.com/uploads/job_7d41c9.pdf?X-Goog-Signature=..."
}The two steps
POST /v1/upload-urlwith no body. It answers with ajobIdand anuploadUrl.PUTthe file atuploadUrlwithContent-Type: application/pdf. The content type is baked into the URL when it is issued, so sending a different one fails the upload.
Then call POST /v1/analyze with that jobId. Passing jobId implies pdfUploaded: true, so you
do not have to send both.
The jobId is issued here and is the same id you poll on afterwards — an upload URL and its analysis
share one identity from the beginning.
JOB=$(curl -s -X POST https://api.granska.cloud/v1/upload-url \
-H "Authorization: Bearer $TOKEN")
curl -X PUT "$(echo "$JOB" | jq -r .uploadUrl)" \
-H "Content-Type: application/pdf" \
--data-binary @utredning.pdf
The Origin header, if a browser does the upload
The upload URL is a resumable-upload session, and cloud storage takes the allowed origin from the call that created the session — this one — rather than from the bucket's CORS configuration.
So if your server fetches the URL and hands it to a browser on a different origin, the browser's
PUT fails CORS even though the URL is valid. Send the browser's origin as the Origin header on
this call and it is baked into the session correctly.
A server-to-server upload never meets this: with no browser involved there is no CORS check.
| Parameter | Description |
|---|---|
Originstring·header | The browser origin that will perform the upload. Baked into the returned URL as the only origin allowed to complete it.GCS's resumable-upload protocol takes the origin from this call, not from the bucket's CORS rules — so a server that fetches the URL and hands it to a browser on another origin gets a CORS failure on the upload. |
Errors
This endpoint spends no quota and validates nothing beyond your token, so the only failures are authentication and an internal fault.
| Error | When |
|---|---|
401UNAUTHORIZED | The Authorization header is missing, is not a readable bearer token, or names no tenant. |
401TOKEN_EXPIRED | The access token was issued by this gateway and has since expired. Not probed: it needs a token older than its own lifetime. |
500INTERNAL_ERROR | An unexpected server-side failure. Not probable from outside — reaching it means something is wrong. |
Send it without writing a client
If your organisation already has an account, an administrator can request an upload URL from the API
tester at /admin/api-tester and see the response before writing any code.