Direct Upload: AgentOS People Portal v1 API
Direct Upload: AgentOS People Portal v1 API
As of v0.1.71, myProp supports direct file uploads via the original AgentOS People Portal /v1/corporate/peopleportal/... endpoint paths. These complement the existing /v3/ document upload routes and are required for certain upload workflows (maintenance requests, tenancy applications, contractor invoices) that are only available on the v1 API.
Overview
The direct upload service lives in src/lib/letmc/upload.ts and exposes a set of typed async functions. Each function:
- Sends
api_keyas a request header (not a query parameter). - Uses a 120-second timeout to accommodate large file uploads.
- Returns a
DirectUploadResultobject withsuccess,data, anderrorfields so callers can handle failures gracefully without catching exceptions.
For batch operations, uploadMultipleDocuments() introduces a 500 ms delay between sequential uploads to avoid overwhelming the AgentOS API.
Prerequisites
The direct upload service requires the LETMC_API_KEY environment variable to be set. You can check availability at runtime:
import { isDirectUploadAvailable } from "@/lib/letmc/upload";
if (!isDirectUploadAvailable()) {
// LETMC_API_KEY is not configured
}
Endpoints
Generic Document Upload
Attaches any document to an AgentOS object by OID.
Endpoint: POST /v1/corporate/peopleportal/letmcletting/{clientName}/uploads/generic/{objectOID}
import { uploadGenericDocument } from "@/lib/letmc/upload";
const result = await uploadGenericDocument(
"myagency", // clientName — agency identifier
"OBJ-001", // objectOID — target object OID
{
DocumentName: "lease-agreement.pdf",
MimeType: "application/pdf",
Content: "<base64-encoded content>",
Size: 204800,
// Timestamp defaults to Date.now() if omitted
}
);
if (!result.success) {
console.error(result.error);
}
Payload: GenericDocumentPayload
| Field | Type | Required | Description |
|---|---|---|---|
DocumentName | string | ✅ | File name including extension |
MimeType | string | ✅ | MIME type (e.g. application/pdf) |
Content | string | ✅ | Base64-encoded file content |
Size | number | ✅ | File size in bytes |
Timestamp | number | — | Epoch ms; defaults to Date.now() |
Maintenance Request
Submits a maintenance request on behalf of a person.
Endpoint: POST /v1/corporate/peopleportal/{shortName}/{clientName}/uploads/{personID}/maintenance-request
import { uploadMaintenanceRequest } from "@/lib/letmc/upload";
const result = await uploadMaintenanceRequest(
"agencyslug", // shortName — agency slug
"myagency", // clientName — agency identifier
"PER-123", // personID — tenant/landlord person OID
{
Fault: "Plumbing",
Message: "Leak under kitchen sink.",
PresenceRequested: true,
}
);
Payload: MaintenanceRequestPayload
| Field | Type | Required | Description |
|---|---|---|---|
Fault | string | ✅ | Fault/issue category or ID |
Message | string | ✅ | Description of the issue |
PresenceRequested | boolean | ✅ | Whether tenant presence is needed |
Tenancy Application
Submits a tenancy application for a specific tenancy.
Endpoint: POST /v1/corporate/peopleportal/letmcletting/{clientName}/uploads/{personID}/tenancy-application/{tenancyID}
import { uploadTenancyApplication } from "@/lib/letmc/upload";
const result = await uploadTenancyApplication(
"myagency", // clientName
"PER-456", // personID — tenant OID
"TEN-789", // tenancyID — tenancy OID
{ /* application fields */ }
);
The TenancyApplicationPayload is an open object ([key: string]: unknown) — pass whatever fields the AgentOS API requires for the application.
Contractor Invoice
Submits an invoice from a contractor against a maintenance job.
Endpoint: POST /v1/corporate/peopleportal/letmcletting/{clientName}/uploads/{contractorID}/submitted-invoices/submit
import { uploadContractorInvoice } from "@/lib/letmc/upload";
const result = await uploadContractorInvoice(
"myagency", // clientName
"CON-999", // contractorID
{
MaitenanceJobID: "JOB-001",
PropertyID: "PROP-002",
Amount: 350.00,
Description: "Emergency plumbing repair",
Documents: [
{
DocumentName: "invoice.pdf",
MimeType: "application/pdf",
Content: "<base64>",
Size: 51200,
},
],
}
);
Payload: ContractorInvoicePayload
| Field | Type | Required | Description |
|---|---|---|---|
MaitenanceJobID | string | ✅ | Maintenance job OID |
PropertyID | string | ✅ | Property OID |
Amount | number | ✅ | Invoice amount |
Description | string | ✅ | Invoice description |
Documents | InvoiceDocument[] | ✅ | Attached invoice files |
Batch (Multi-file) Upload
Upload multiple documents to the same object with a 500 ms stagger between requests.
import { uploadMultipleDocuments } from "@/lib/letmc/upload";
const result = await uploadMultipleDocuments(
"myagency",
"OBJ-001",
[doc1, doc2, doc3]
);
console.log(`${result.successCount} succeeded, ${result.failedCount} failed`);
result.results.forEach((r, i) => {
if (!r.success) console.error(`File ${i} failed: ${r.error}`);
});
MultiUploadResult
| Field | Type | Description |
|---|---|---|
success | boolean | true only if all files succeeded |
results | DirectUploadResult[] | Per-file result in order |
successCount | number | Number of successful uploads |
failedCount | number | Number of failed uploads |
Passing an empty array returns { success: true, results: [], successCount: 0, failedCount: 0 } immediately.
tRPC Procedures
All functions are also exposed through the document router as tRPC procedures:
| Procedure | Description |
|---|---|
document.directUpload | Single generic document via v1 path |
document.directUploadMultiple | Batch upload with 500 ms stagger |
document.submitMaintenanceRequest | Maintenance request via v1 path |
document.submitTenancyApplication | Tenancy application via v1 path |
document.submitContractorInvoice | Contractor invoice via v1 path |
document.status | Reports directUploadAvailable: boolean alongside existing status fields |
Error Handling
All upload functions return a DirectUploadResult and do not throw. Errors are captured internally and returned in the error field.
If the upload times out (after 120 s), the error message will indicate a timeout with HTTP status 408. For non-2xx responses, the DirectUploadError includes the HTTP status code and the response body for debugging.
if (!result.success) {
// result.error is a human-readable string
// Log or surface to the user as appropriate
}
Comparison with /v3/ Endpoints
| Behaviour | /v3/ (existing) | /v1/ (this feature) |
|---|---|---|
| API key transport | Query parameter (?api_key=…) | Request header (api_key) |
| Timeout | 30 s | 120 s |
| Multi-file support | — | 500 ms stagger via uploadMultipleDocuments() |
| Backward compatibility | Unchanged | Fully additive — no existing behaviour changed |