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 enabled additionally requires a credential generated on your backend with the project secret key.

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 (deprecated), the signature and expire request parameters. It allows every endpoint until it expires. This is the legacy scheme; use tokens for new integrations.
Note: If a request carries both a signature and an Authorization header, the signature is checked and the header is ignored.

Credentials are checked only when signed uploads are enabled for the project. 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.

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.
jtiNoUnique token identifier. Not interpreted.
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.

Other standard claims are ignored. Keys under uc other than the ones listed above are not allowed: such a token is rejected.

Note: Use sub or jti claims, especially if you generate tokens for multiple clients within a short period of time. Having all the same claims for different clients can lead to incorrect accounting of the client quota (the shared quota for different clients).

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

The operations counter lives as long as the token. A request that would exceed the limit is refused. Requests that fail with a 4xx response do not consume operations. A multipart upload that is started but never completed still consumes its operation.

Generate a token

Use any JWT library that supports HS256:

const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const secret = 'YOUR_SECRET_KEY';
const key = crypto.createHash('sha256').update(secret, 'utf8').digest();
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
iat: now,
exp: now + 60 * 30, // expire in 30 minutes
uc: {
restrictions: {
scope: ['/base/'],
limits: { operations: 5 },
},
},
},
key,
{ algorithm: 'HS256' }
);

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

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.

Signature

Warning: The signature scheme stays supported. However, for your control, security and to preserve the billing balance, we recommend using 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. The key is the project secret key, UTF-8 encoded. The message is the expire value as a decimal string, for example "1700000000".

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.