Locker API Quickstart
Upload a document and chat with it using AI in under 5 minutes.
Prerequisites
- A Propper account with API access
- API credentials from the dashboard
Step 1: Get an Access Token
Exchange your client credentials for an access token with locker scopes.
- cURL
- JavaScript
- Python
curl -X POST "https://auth.propper.ai/oauth2/token" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "YOUR_CLIENT_ID",
"client_secret": "YOUR_CLIENT_SECRET",
"scope": "locker:read locker:write"
}'
const getAccessToken = async () => {
const response = await fetch('https://auth.propper.ai/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: process.env.PROPPER_CLIENT_ID,
client_secret: process.env.PROPPER_CLIENT_SECRET,
scope: 'locker:read locker:write',
}),
});
const { access_token } = await response.json();
return access_token;
};
const token = await getAccessToken();
import os
import requests
def get_access_token() -> str:
response = requests.post(
"https://auth.propper.ai/oauth2/token",
json={
"grant_type": "client_credentials",
"client_id": os.environ["PROPPER_CLIENT_ID"],
"client_secret": os.environ["PROPPER_CLIENT_SECRET"],
"scope": "locker:read locker:write",
},
)
response.raise_for_status()
return response.json()["access_token"]
token = get_access_token()
Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "locker:read locker:write"
}
tip
Tokens expire in 1 hour. Cache them and refresh before expiry.
Step 2: Upload a Document
Upload a document using multipart form data.
- cURL
- JavaScript
- Python
curl -X POST "https://api.propper.ai/v1/locker/documents/upload" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@contract.pdf" \
-F "name=Service Agreement" \
-F "documentType=CONTRACT" \
-F "tags=legal,active"
const uploadDocument = async (token, filePath) => {
const fs = await import('fs');
const form = new FormData();
form.append('file', new Blob([fs.readFileSync(filePath)]));
form.append('name', 'Service Agreement');
form.append('documentType', 'CONTRACT');
form.append('tags', 'legal,active');
const response = await fetch(
'https://api.propper.ai/v1/locker/documents/upload',
{
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
body: form,
},
);
return response.json();
};
const document = await uploadDocument(token, './contract.pdf');
console.log('Document ID:', document.id);
def upload_document(token: str, file_path: str) -> dict:
with open(file_path, "rb") as f:
response = requests.post(
"https://api.propper.ai/v1/locker/documents/upload",
headers={"Authorization": f"Bearer {token}"},
files={"file": f},
data={
"name": "Service Agreement",
"documentType": "CONTRACT",
"tags": "legal,active",
},
)
response.raise_for_status()
return response.json()
document = upload_document(token, "./contract.pdf")
print(f"Document ID: {document['id']}")
Response:
{
"id": "d7f3a1b2-4c5e-6f7a-8b9c-0d1e2f3a4b5c",
"name": "Service Agreement",
"documentType": "CONTRACT",
"mimeType": "application/pdf",
"tags": ["legal", "active"],
"createdAt": "2024-01-15T10:00:00Z"
}
Step 3: Chat with the Document
Ask a question about the uploaded document.
- cURL
- JavaScript
- Python
curl -X POST "https://api.propper.ai/v1/locker/chat" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "What are the key terms of this agreement?",
"documentIds": ["d7f3a1b2-4c5e-6f7a-8b9c-0d1e2f3a4b5c"]
}'
const chatWithDocument = async (token, documentId, message) => {
const response = await fetch('https://api.propper.ai/v1/locker/chat', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
documentIds: [documentId],
}),
});
return response.json();
};
const answer = await chatWithDocument(
token,
document.id,
'What are the key terms of this agreement?',
);
console.log('Answer:', answer.response);
def chat_with_document(token: str, document_id: str, message: str) -> dict:
response = requests.post(
"https://api.propper.ai/v1/locker/chat",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"message": message,
"documentIds": [document_id],
},
)
response.raise_for_status()
return response.json()
answer = chat_with_document(
token,
document["id"],
"What are the key terms of this agreement?",
)
print(f"Answer: {answer['response']}")
Step 4: Stream a Chat Response
For longer answers, use the streaming endpoint for real-time responses.
- cURL
- JavaScript
- Python
curl -N -X POST "https://api.propper.ai/v1/locker/chat/stream" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"message": "Summarize the obligations in this agreement",
"documentIds": ["d7f3a1b2-4c5e-6f7a-8b9c-0d1e2f3a4b5c"]
}'
const streamChat = async (token, documentId, message) => {
const response = await fetch(
'https://api.propper.ai/v1/locker/chat/stream',
{
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message,
documentIds: [documentId],
}),
},
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
process.stdout.write(decoder.decode(value));
}
};
await streamChat(
token,
document.id,
'Summarize the obligations in this agreement',
);
def stream_chat(token: str, document_id: str, message: str):
response = requests.post(
"https://api.propper.ai/v1/locker/chat/stream",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
json={
"message": message,
"documentIds": [document_id],
},
stream=True,
)
response.raise_for_status()
for chunk in response.iter_content(decode_unicode=True):
print(chunk, end="", flush=True)
stream_chat(
token,
document["id"],
"Summarize the obligations in this agreement",
)
Common Mistakes
Don't forget these
1. Missing document IDs in chat
// Wrong - no documents to query
{ message: "What are the terms?" }
// Correct - specify which documents to chat with
{ message: "What are the terms?", documentIds: ["d7f3a1b2-..."] }
2. Exceeding file size limit
The maximum upload size is 50 MB. For larger files, split them into smaller documents.
3. Using wrong scope
# Wrong - read scope can't upload documents
scope: "locker:read"
# Correct - write scope needed for uploads
scope: "locker:read locker:write"