curl --request POST \
--url https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"filename": "proof_of_address.pdf",
"contentType": "application/pdf",
"sizeBytes": 348211
}
'import requests
url = "https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents"
payload = {
"filename": "proof_of_address.pdf",
"contentType": "application/pdf",
"sizeBytes": 348211
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filename: 'proof_of_address.pdf',
contentType: 'application/pdf',
sizeBytes: 348211
})
};
fetch('https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents', 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://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'proof_of_address.pdf',
'contentType' => 'application/pdf',
'sizeBytes' => 348211
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents"
payload := strings.NewReader("{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}"
response = http.request(request)
puts response.read_body{
"uploadId": "9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49",
"uploadUrl": "https://example-documents-bucket.s3.amazonaws.com/tenants/tenant_01HABCXYZ/cases/case_01HABCXYZ/rfi-uploads/9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=EXAMPLE",
"expiresAt": "2026-09-16T10:12:00Z"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>",
"answeredBy": "<string>",
"answeredAt": "2023-11-07T05:31:56Z",
"uploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "<string>",
"message": "<string>"
}{
"error": "<string>",
"answeredBy": "<string>",
"answeredAt": "2023-11-07T05:31:56Z",
"uploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "<string>",
"message": "<string>"
}Mint an upload slot for a reply
Step 1 of answering an information request: mint a presigned upload
slot for one file. Requires the cases:write scope, and the
request must still be pending. Call it once per file.
PUT the file’s bytes to uploadUrl before expiresAt, with
exactly the Content-Type and Content-Length you declared and
the header If-None-Match: * — all three are pinned into the
signature, and the upload is rejected otherwise:
PUT {uploadUrl}
Content-Type: application/pdf
Content-Length: 348211
If-None-Match: *
The URL is create-only: once the object exists a second PUT returns
412 Precondition Failed, so an upload cannot be replaced once it has
landed. expiresAt also bounds step 2 — a slot not attached by then is
refused, and you mint a fresh one.
Frayme does not inspect file content. The declared contentType is
pinned into the signature and checked against the stored object’s
header, but the bytes are never sniffed, validated, or scanned for
malware. Scan files you collect from end customers before uploading
them — an attached document is shown to analysts and may be read by an
AI workflow node.
Every slot you mint counts against the budget for good, even one you never upload to or that expires: 30 per request, 100 per case (a single response attaches at most 10 documents).
curl --request POST \
--url https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"filename": "proof_of_address.pdf",
"contentType": "application/pdf",
"sizeBytes": 348211
}
'import requests
url = "https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents"
payload = {
"filename": "proof_of_address.pdf",
"contentType": "application/pdf",
"sizeBytes": 348211
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
filename: 'proof_of_address.pdf',
contentType: 'application/pdf',
sizeBytes: 348211
})
};
fetch('https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents', 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://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'filename' => 'proof_of_address.pdf',
'contentType' => 'application/pdf',
'sizeBytes' => 348211
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"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"
"strings"
"net/http"
"io"
)
func main() {
url := "https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents"
payload := strings.NewReader("{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://core.us.api.frayme.io/cases/{caseId}/rfi/{rfiId}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"filename\": \"proof_of_address.pdf\",\n \"contentType\": \"application/pdf\",\n \"sizeBytes\": 348211\n}"
response = http.request(request)
puts response.read_body{
"uploadId": "9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49",
"uploadUrl": "https://example-documents-bucket.s3.amazonaws.com/tenants/tenant_01HABCXYZ/cases/case_01HABCXYZ/rfi-uploads/9a2e4d71-0b83-4c17-8f5a-1d6e7c0b3a49?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=EXAMPLE",
"expiresAt": "2026-09-16T10:12:00Z"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>",
"answeredBy": "<string>",
"answeredAt": "2023-11-07T05:31:56Z",
"uploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "<string>",
"message": "<string>"
}{
"error": "<string>",
"answeredBy": "<string>",
"answeredAt": "2023-11-07T05:31:56Z",
"uploadId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"key": "<string>",
"message": "<string>"
}Authorizations
A tenant-scoped API key provisioned by Frayme.
Path Parameters
The case identifier returned by POST /cases.
The information request's id, delivered as rfi_id on the case.rfi_requested webhook.
Body
The file's name, stored with the slot. Maximum 255 bytes.
255Pinned into the presigned URL's signature.
application/pdf, image/jpeg, image/png The exact byte length, pinned into the signature. Maximum 20 MB.
1 <= x <= 20971520