Skip to content
Developers

Uploads

Upload images and documents through a one-time upload URL, then attach the returned upload ID to a profile, post, resource or application.

Use uploads to set a profile picture or banner, add a cover image to an event or post, attach a résumé to a job application, or add a file to quest evidence from a script. The API never accepts file bytes in its own JSON requests. You ask for a one-time upload URL, send the file to that URL, and then pass the storageId it returns in the request that uses the file.

API access needs a Honey Bee plan or above; see the developer overview. Errors and rate limits follow Requests, errors and limits.

#How an upload works

  1. Request an upload URL

    Call POST /api/v1/uploads/url with the file name, type and size. You get a URL that is valid for 10 minutes and can be used once.

  2. Send the file

    POST the raw bytes to the returned uploadUrl. The response contains the upload ID as storageId.

  3. Attach the upload

    Send the storageId in the field that expects it, for example imageStorageId on a profile. Attach it within 24 hours; uploads that are not attached are deleted after that.

#Request an upload URL

POST/api/v1/uploads/urlScopeaccount:write

Checks the file metadata against the upload policy and issues an upload URL for the key owner. Uploads always belong to the key owner, even when you later attach them to a managed account. Limited to 60 requests per minute and 1,000 per day per account.

NameInTypeRequiredNotes
purposebodystringYesimage or document.
fileNamebodystringYesMust end in an extension allowed for the purpose; see Limits.
contentTypebodystringYesMIME type allowed for the purpose; case is ignored.
byteSizebodynumberYesFile size in bytes. Positive, and not above the limit for the purpose.
bash
curl -X POST https://app.hive3.tech/api/v1/uploads/url \
  -H "Authorization: Bearer $HIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"purpose":"image","fileName":"avatar.png","contentType":"image/png","byteSize":24000}'

Returns 201. maxBytes is the size limit for the purpose. Use uploadUrl exactly as returned.

json
{
  "data": {
    "uploadUrl": "https://example.com/upload?token=0123abcd",
    "purpose": "image",
    "fileName": "avatar.png",
    "contentType": "image/png",
    "maxBytes": 8388608
  }
}
  • 400 invalid_request: a field is missing or invalid, for example fileName must end with one of: .gif, .jpeg, .jpg, .png, .webp, contentType must be one of: …, or byteSize exceeds the 8388608 byte image limit.
  • 429 rate_limited: the per-account budget is used up, or the upload service is busy; wait for Retry-After.

#Send the file

Send the file bytes with a POST to the uploadUrl from the previous step. This request goes to the upload URL, not to the API: do not send the Authorization header, and set Content-Type to the file's MIME type. The URL works once, even when the upload fails, so request a new URL to retry.

bash
curl -X POST "$UPLOAD_URL" \
  -H "Content-Type: image/png" \
  --data-binary @avatar.png

Returns 200. This response is not wrapped in data, and errors are a plain error string rather than the API error object.

json
{ "storageId": "upload_123" }
StatuserrorWhen
403invalid_upload_grantThe URL is unknown, expired or already used.
400unsupported_content_typeThe Content-Type is not allowed for the purpose.
400content_type_mismatchImage bytes do not match the declared image type.
400empty_uploadThe body is empty.
413payload_too_largeThe body is larger than maxBytes.
409quotaThe account already keeps 500 uploads or 1 GiB.
429rate_limitedThe 250 MiB daily upload budget is used up; wait for Retry-After.

#Attach an upload

Pass the storageId where a request accepts an upload ID. The upload must belong to the same key owner and have been requested with a matching purpose: image fields need an image upload, and attachment fields accept either. When the upload is attached, the file type and size are checked again against the stored file.

WhereFields
ProfileimageStorageId, heroImageStorageId (images); attachmentStorageId (image or document)
Posts and events, courses, content, quests and jobsimageStorageId, thumbnailStorageId, heroImageStorageId, bannerStorageId, employerLogoStorageId (images); attachmentStorageId (image or document)
Job applications and quest evidence (participation workflows)storageId inside the file objects the request describes

For example, set a profile picture with the upload ID from the previous step:

bash
curl -X PATCH https://app.hive3.tech/api/v1/profile \
  -H "Authorization: Bearer $HIVE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"imageStorageId":"upload_123"}'
  • 400 invalid_request: the upload is not a valid ID, or its type or size is not allowed for that field (uploaded file type or size is not allowed by the upload policy).
  • 404 not_found: the upload does not exist, has expired, belongs to another account, or was requested with a purpose the field does not accept (upload was not found).

#Limits

LimitValue
ImagesGIF, JPEG, PNG or WebP (image/gif, image/jpeg, image/png, image/webp), up to 8 MiB (8,388,608 bytes). Extensions .gif, .jpeg, .jpg, .png, .webp.
DocumentsPDF, DOC, DOCX, Markdown or plain text (application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document, text/markdown, text/plain), up to 25 MiB (26,214,400 bytes). Extensions .doc, .docx, .md, .pdf, .txt.
Upload URLValid for 10 minutes, one use.
Upload URL requests60 per minute and 1,000 per day per account.
Uploaded bytes250 MiB per day per account.
Stored uploads500 files and 1 GiB per account, counting uploads that are attached or still waiting to be attached.
Unattached uploadsDeleted 24 hours after upload.

#AI assistant documents

Training documents for the AI assistant use their own route, POST /api/v1/ai-assistant/documents/upload-url, with different file types and a smaller size limit. It shares the account's upload URL budget. See AI assistant.

The hive3 upload command runs the whole flow for you; see the CLI command reference.