Skip to navigation

Authentication

Every Upload API request identifies the project with the public key, sent as the UPLOADCARE_PUB_KEY parameter or pub_key where the endpoint says so.

Signed access

A project with Signed Uploads turned on additionally requires a credential generated on your backend with the project secret key. You create secret keys yourself in the API keys section of your Dashboard. A project can have more than one.

There are two credential schemes:

  • Token, a JSON Web Token in the Authorization header. It can restrict which endpoints the client may call and how many operations it may spend.
  • Signature (legacy), the signature and expire request parameters. It allows every upload endpoint until it expires. AI image endpoints accept tokens only. The scheme stays supported, but use tokens for new integrations.
Note: If an upload request carries both a signature and an Authorization header, the signature is checked and the header is ignored. AI image endpoints read only the header.

Credentials are checked only when the project has Signed Uploads or Signed AI image generation turned on. The signed uploads guide explains how to enable them and which credential to choose.

Token

A token is a JSON Web Token (RFC 7519) sent in the Authorization header with the Bearer scheme:

Authorization: Bearer <jwt>

Signing

The token MUST be signed with HS256. The signing key is the SHA-256 digest of the project secret key, as raw 32 bytes rather than their hex representation. Any of the project’s secret keys works.

Hash the secret key as the characters you copied from the dashboard, not as the bytes that hex string decodes to. Both that and signing with the digest’s hex form produce Invalid token. Reason: signature does not match, which is the most common way a hand-rolled token fails.

Claims

Standard claims sit at the root of the payload. Everything Uploadcare interprets sits under the uc claim.

ClaimRequiredDescription
iatYesIssue time, a Unix timestamp in seconds. MUST NOT be later than the current server time, with a 30-second clock-skew allowance. A token issued further in the future is rejected with AccessTokenInvalidError.
expYesExpiration time, a Unix timestamp in seconds. The token lifetime, exp minus iat, MUST NOT exceed 24 hours. A 30-second clock skew is tolerated.
issNoAny string identifying the issuer. Not interpreted.
subNoAny string identifying the token user, such as an internal user identifier. Not interpreted, but it makes the token unique, which keeps its operation count separate.
jtiNoUnique token identifier. Not interpreted, but it makes the token unique, which keeps its operation count separate.
uc.restrictions.scopeNoThe endpoints the token opens, as a list of paths. See Scope and limits. Without it, the token opens every signed endpoint.
uc.restrictions.limits.operationsNoAn integer from 1 to 100000, the maximum number of operations the token may spend during its lifetime. Without it, there is no limit.

nbf, if present, is enforced: a token used before that time is rejected with AccessTokenInvalidError. Other standard claims are ignored. Keys under uc other than the ones in that table are not allowed: such a token is rejected.

Payload example:

{
"iat": 1700000000,
"exp": 1700003600,
"sub": "example_user_01",
"uc": {
"restrictions": {
"scope": ["/base/", "/multipart/*"],
"limits": { "operations": 10 }
}
}
}

Scope and limits

Each signed endpoint has a scope path. A scope item matches a path exactly, or as a prefix when it ends with /*. A bare /* matches every endpoint. The list holds from 1 to 16 items, each up to 64 characters and starting with /.

EndpointScope pathOperations spent
Direct upload/base/One per file in the request
Multipart upload start/multipart/start/One per request
Upload from URL/from_url/One per request
Create group/group/None
AI image generation/derivative/image/generate/One per request
AI image edit/derivative/image/edit/One per request
AI image outpaint/derivative/image/outpaint/One per request
AI image upscale/derivative/image/upscale/One per request
Warning: A scope must list every path your client calls. File Uploader also uses /multipart/start/ for files over multipartMinFileSize (25 MB by default), /from_url/ for URL and social sources, and /group/ when groupOutput is on. The AI Image Editor plugin sends the same token, so add its paths too. A path that’s missing fails with ScopeForbiddenError.

The AI image endpoints are checked only when Signed AI image generation is on, and they accept auth tokens only, not signatures. Polling a generation’s status is not checked.

The operations counter lives as long as the token. A request that would exceed the limit is refused. A request refused before any file is saved or any job is started consumes nothing. Once a direct upload saves its first file, the whole request’s operations are spent, even if a later file in the same request fails. A multipart upload that is started but never completed still consumes its operation.

One counter per token

Uploadcare keys the counter by the token itself, not by its claims. Two tokens minted in the same second with the same claims are byte-identical, so they share one counter, and one user can spend another’s limit. If you set operations, add a sub (for example, the user ID) or a jti (a random ID) so every token is unique. Uploadcare doesn’t read either claim.

Generate a token

Use the Uploadcare Node SDK, or any JWT library that supports HS256. The samples set operations, so they also set sub to give each token its own counter:

@uploadcare/signed-uploads mints the token for you, and validates the claims before signing rather than letting the API reject them:

// npm install @uploadcare/signed-uploads
import { randomUUID } from 'node:crypto';
import { generateAuthToken } from '@uploadcare/signed-uploads';
const token = generateAuthToken('YOUR_SECRET_KEY', {
lifetime: 30 * 60 * 1000, // 30 minutes, in milliseconds
// `sub` and `jti` keep this token's operation count separate.
subject: 'user_123',
tokenId: randomUUID(),
scope: ['/base/'],
operations: 5,
});

Pass expire (a millisecond timestamp or a Date) in place of lifetime for an absolute expiry. subject sets sub and tokenId sets jti. When you pass operations, set at least one of them so each token gets its own count. Signed uploads covers handing the token to File Uploader, Upload Client or the AI Image Editor.

Upload example

curl -H "Authorization: Bearer YOUR_TOKEN" \
-F "UPLOADCARE_PUB_KEY=YOUR_PUBLIC_KEY" \
-F "file=@image.jpg" \
"https://upload.uploadcare.com/base/"
{
"file": "c0d776d4-8c8e-47df-9e92-03b68b99c2ba"
}

Errors

With Accept: application/json (exactly) or the jsonerrors=1 query parameter, errors come back as HTTP 200 with the real status in status_code. The one exception is DerivativeQuotaExceededError, which keeps its 429. Upload Client sends jsonerrors=1 and reads it. Otherwise, the HTTP status is the real one. Either way, branch on error_code.

Error codeHTTP statusMessageCause
AccessTokenInvalidError401Invalid token. Reason: …Wrong header format, unreadable token, signature that matches no secret key, or a bad claim. The reason names which.
AccessTokenExpiredError401Expired token.exp is in the past, beyond the 30-second clock-skew allowance.
ScopeForbiddenError403uc.restrictions.scope does not allow …The endpoint is outside the token’s scope. The message names the required path.
OperationsLimitExceededError403The operation limit of the token is exhausted.The token has spent all of its operations.
SignatureRequiredError400signature is required.The upload request carried no credential at all. The name predates tokens: a project with Signed Uploads on answers this way whether you meant to send a token or a signature.

On uploads, the JavaScript libraries handle two of these for you: given a way to fetch a token, they answer AccessTokenExpiredError and OperationsLimitExceededError by dropping the refused token and retrying the request once. The AI image endpoints are not retried. See when a credential is refused.

Signature

Warning: The signature scheme is legacy but still supported. It can’t be limited to particular endpoints or a number of operations, so use tokens for new integrations.

The signature and expire parameters are sent together with the request, either in the query string or as form fields.

ParameterDescription
expireA Unix timestamp in seconds after which the signature stops working.
signatureA hex-encoded HMAC-SHA256 digest computed from two inputs: the project secret key (UTF-8 encoded) as the key, and the expire value as a decimal string, for example "1700000000", as the message.

Any of the project’s secret keys works.

Generate a signature

Warning: The @uploadcare/signed-uploads package accepts milliseconds and converts to seconds internally. If you implement signing without this package, expire must be in seconds, not milliseconds.
// Option 1: by expiration timestamp (milliseconds since epoch)
import { generateSecureSignature } from '@uploadcare/signed-uploads'
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
expire: Date.now() + 60 * 30 * 1000 // expire in 30 minutes
})
// Option 2: by expiration date
import { generateSecureSignature } from '@uploadcare/signed-uploads'
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
expire: new Date("2099-01-01") // expire on 2099-01-01
})
// Option 3: by lifetime
import { generateSecureSignature } from '@uploadcare/signed-uploads'
const { secureSignature, secureExpire } = generateSecureSignature('YOUR_SECRET_KEY', {
lifetime: 60 * 30 * 1000 // expire in 30 minutes
})

Upload example

curl -F "UPLOADCARE_PUB_KEY=YOUR_PUBLIC_KEY" \
-F "signature=YOUR_SIGNATURE" \
-F "expire=YOUR_EXPIRE" \
-F "file=@image.jpg" \
"https://upload.uploadcare.com/base/"

Errors

Error codeHTTP statusMessageCause
SignatureRequiredError400signature is required.Missing signature parameter.
SignatureExpirationRequiredError400expire is required.Missing expire parameter.
SignatureExpirationInvalidError400expire must be a UNIX timestamp.expire is not a valid integer.
SignatureExpirationError403Expired signature.expire is in the past.
SignatureInvalidError403Invalid signature.The HMAC matches no project secret key.

For the complete list of Upload API errors, see Errors.