Storage — Boto Credentials
curl --request POST \
--url https://api.minerva.io/storage/boto \
--header 'x-api-key: <api-key>'import requests
url = "https://api.minerva.io/storage/boto"
headers = {"x-api-key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.minerva.io/storage/boto', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.minerva.io/storage/boto",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.minerva.io/storage/boto"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.minerva.io/storage/boto")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.minerva.io/storage/boto")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"credentials": {
"aws_region": "us-east-1",
"aws_key_id": "ASIAIOSFODNN7EXAMPLE",
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"aws_session_token": "FwoGZXIvYXdzEJr...truncated",
"aws_session_expiration": "2026-06-29T16:30:00+00:00"
},
"bucket_info": {
"name": "prod-minerva-client-acme",
"prefix": "acme/",
"uri": "s3://prod-minerva-client-acme/acme/"
}
}
{
"code": "unauthorized",
"message": "Unauthorized",
"api_request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
{
"code": "forbidden",
"message": "Forbidden",
"api_request_id": "b2c3d4e5-f6a7-1234-bcde-f12345678901"
}
{
"code": "internal_error",
"message": "An unknown error occurred, please contact help@minerva.io if problem persists",
"api_request_id": "c3d4e5f6-a7b8-2345-cdef-012345678901"
}
Storage
Storage — Boto Credentials
Mint short-lived AWS credentials scoped to your org’s storage area for use with boto3 / the AWS SDKs.
POST
/
storage
/
boto
Storage — Boto Credentials
curl --request POST \
--url https://api.minerva.io/storage/boto \
--header 'x-api-key: <api-key>'import requests
url = "https://api.minerva.io/storage/boto"
headers = {"x-api-key": "<api-key>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.minerva.io/storage/boto', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.minerva.io/storage/boto",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.minerva.io/storage/boto"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.minerva.io/storage/boto")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.minerva.io/storage/boto")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"credentials": {
"aws_region": "us-east-1",
"aws_key_id": "ASIAIOSFODNN7EXAMPLE",
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"aws_session_token": "FwoGZXIvYXdzEJr...truncated",
"aws_session_expiration": "2026-06-29T16:30:00+00:00"
},
"bucket_info": {
"name": "prod-minerva-client-acme",
"prefix": "acme/",
"uri": "s3://prod-minerva-client-acme/acme/"
}
}
{
"code": "unauthorized",
"message": "Unauthorized",
"api_request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
{
"code": "forbidden",
"message": "Forbidden",
"api_request_id": "b2c3d4e5-f6a7-1234-bcde-f12345678901"
}
{
"code": "internal_error",
"message": "An unknown error occurred, please contact help@minerva.io if problem persists",
"api_request_id": "c3d4e5f6-a7b8-2345-cdef-012345678901"
}
Quick Answer
How do I upload or download files to/from Minerva? Call this endpoint to receive short-lived AWS credentials scoped to your org’s storage area, then use any AWS SDK (boto3, AWS CLI, etc.) to read/write objects.Common questions this endpoint answers:- How do I send large files into Minerva?
- How do I retrieve exported / batch results?
- How do I authenticate to Minerva’s S3 storage?
- How long are storage credentials valid?
- Can I use the AWS CLI / boto3 directly?
x-api-key).What you get back: Temporary AWS credentials (access key, secret, session token, expiration) plus the S3 bucket name and your org’s prefix.Overview
This endpoint mints short-lived AWS STS credentials scoped to your org’s directory inside the Minerva client storage bucket. Use them withboto3,
the AWS CLI, or any AWS SDK to upload files for processing and download
exports.
Each org has a dedicated bucket; credentials are additionally scoped via
session policy to {your_org}/* only, so other orgs’ data is unreachable
even if a key were misused.
By convention the org directory has two subfolders:
Incoming/— files you send in (resolve/enrich batches, segment uploads, …).Outgoing/— files placed for you to read (exports, batch results, …).
The Python SDK wraps this endpoint behind
mc.storage — upload,
download, list, plus bulk helpers — and handles credential
refresh automatically. See the
Storage SDK guide. The boto endpoint is
documented here for AWS-SDK / CLI integrations and other languages.Request
Headers
string
required
Your Minerva API key.
Request Body
No body. SendPOST with an empty body (or {}).
Response
Response Structure
object
Temporary AWS credentials scoped to your org’s storage area.
object
Bucket name + prefix to scope your AWS-SDK calls to.
credentials
string
AWS region the bucket lives in. Always
us-east-1 today.string
Temporary access key ID.
string
Temporary secret access key.
string
Temporary session token. Required alongside the access key / secret for
STS credentials.
string
ISO 8601 timestamp at which the credentials expire. Typically 1 hour
from issue. Refresh by calling this endpoint again before expiry.
bucket_info
string
S3 bucket name. Pass this as the
Bucket= parameter on boto3 calls.string
Your org’s directory inside the bucket — every key you read or write
must start with this prefix. Looks like
<your-minerva-org-id>/.string
Convenience
s3://{name}/{prefix} URI. Useful as a base for the AWS
CLI (aws s3 cp ./local.csv $URI/Incoming/local.csv).{
"credentials": {
"aws_region": "us-east-1",
"aws_key_id": "ASIAIOSFODNN7EXAMPLE",
"aws_secret_access_key": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"aws_session_token": "FwoGZXIvYXdzEJr...truncated",
"aws_session_expiration": "2026-06-29T16:30:00+00:00"
},
"bucket_info": {
"name": "prod-minerva-client-acme",
"prefix": "acme/",
"uri": "s3://prod-minerva-client-acme/acme/"
}
}
{
"code": "unauthorized",
"message": "Unauthorized",
"api_request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
{
"code": "forbidden",
"message": "Forbidden",
"api_request_id": "b2c3d4e5-f6a7-1234-bcde-f12345678901"
}
{
"code": "internal_error",
"message": "An unknown error occurred, please contact help@minerva.io if problem persists",
"api_request_id": "c3d4e5f6-a7b8-2345-cdef-012345678901"
}
Error Responses
401— Unauthorized: invalid or missing API key.403— Forbidden: API key isn’t entitled to storage, or org identity couldn’t be resolved.500— Internal Server Error: credential provisioning failed.
Example — boto3
import boto3
import requests
resp = requests.post(
"https://api.minerva.io/storage/boto",
headers={"x-api-key": "<your-api-key>"},
).json()
creds = resp["credentials"]
bucket = resp["bucket_info"]["name"]
prefix = resp["bucket_info"]["prefix"]
s3 = boto3.client(
"s3",
aws_access_key_id=creds["aws_key_id"],
aws_secret_access_key=creds["aws_secret_access_key"],
aws_session_token=creds["aws_session_token"],
region_name=creds["aws_region"],
)
# Upload into your Incoming/ folder
s3.upload_file("local.csv", bucket, f"{prefix}Incoming/local.csv")
# List what's waiting in Outgoing/
for obj in s3.list_objects_v2(Bucket=bucket, Prefix=f"{prefix}Outgoing/").get("Contents", []):
print(obj["Key"], obj["Size"])
Example — AWS CLI
Export the credentials and use the AWS CLI like normal:RESPONSE=$(curl -s -X POST https://api.minerva.io/storage/boto \
-H "x-api-key: $MINERVA_API_KEY")
export AWS_ACCESS_KEY_ID=$(echo "$RESPONSE" | jq -r '.credentials.aws_key_id')
export AWS_SECRET_ACCESS_KEY=$(echo "$RESPONSE" | jq -r '.credentials.aws_secret_access_key')
export AWS_SESSION_TOKEN=$(echo "$RESPONSE" | jq -r '.credentials.aws_session_token')
export AWS_DEFAULT_REGION=us-east-1
URI=$(echo "$RESPONSE" | jq -r '.bucket_info.uri')
aws s3 cp ./local.csv "${URI}Incoming/local.csv"
aws s3 ls "${URI}Outgoing/"
Notes
- Credential lifetime: typically 1 hour. Refresh by calling the endpoint again before
aws_session_expiration. - Scope: credentials are limited to
s3:GetObject/s3:PutObject/s3:DeleteObjecton{prefix}*ands3:ListBucketfiltered to that same prefix. Other prefixes and operations fail closed. - Caching: the Python SDK caches the credentials in-process and auto-refreshes them near expiry. If you call this endpoint directly, implement the same pattern in your own client (don’t call it per request — keep a single set of creds and refresh ahead of expiry).
- Region: bucket lives in
us-east-1today. - Convention: use
Incoming/for client-to-Minerva transfers and read fromOutgoing/for Minerva-to-client transfers. The session policy permits writes to both, but downstream pipelines watchIncoming/.
Was this page helpful?