All endpoints under /api/v1/* require Authorization: Bearer <JWT> from Entra ID.
Public share endpoints (/s/*, /u/*) are unauthenticated but rule-checked.
Responses are JSON, using RFC 7807 problem details on 4xx/5xx.
Start an upload. Returns a write-SAS URL the client uses to upload the bytes directly to Blob Storage.
Request
{
"name": "quarterly-report.pdf",
"sizeBytes": 4823904,
"contentType": "application/pdf"
}201 Created
{
"fileId": "3f1d…",
"uploadUrl": "https://<account>.blob.core.windows.net/files/users/…?sv=…&sig=…",
"uploadMethod": "PUT",
"expiresAt": "2026-07-18T09:00:00Z"
}Signal that the upload is done. The server HEADs the blob to verify.
204 No ContentList the caller's files. Supports ?page=1&pageSize=50&folder=…&search=….
{
"fileId": "3f1d…",
"slug": "project-x", // optional; if omitted, a random slug is generated
"password": "s3cret", // optional
"expiresAt": "2026-08-01T00:00Z", // optional
"maxDownloads": 25, // optional
"message": "Here's the deck we discussed.",
"notifyOnAccess": true
}201 Created
{
"id": "…",
"slug": "project-x",
"url": "https://<host>/s/project-x",
"qrCodeUrl": "/api/v1/links/…/qr.svg",
"expiresAt": "2026-08-01T00:00Z",
"maxDownloads": 25,
"downloadCount": 0,
"createdAt": "2026-07-18T08:45:00Z"
}Errors:
409 slug_taken— that slug is already in use.422 invalid_slug— doesn't match^[a-z0-9](?:[a-z0-9_-]{1,62}[a-z0-9])?$.403 file_not_owned— the fileId doesn't belong to the caller.
{
"slug": "send-me-your-invoice",
"password": null,
"expiresAt": "2026-08-01T00:00Z",
"maxUploads": 5,
"message": "Drop the PDF here — thanks!"
}Returns a public URL https://<host>/u/{slug}. Recipient POSTs the file; the server issues a per-upload write-SAS bound to a fresh StorageFile row owned by the request creator.
Renders the branded landing page for the download. HTML response. Accept-Language decides the language.
If the link needs a password, the page shows a password form (POSTs back to the same URL).
Form-encoded password=…. On success, 302 Location: <short-lived SAS URL> and increments the counter atomically.
Same pattern, for upload-request links.
Standard problem-details:
{
"type": "https://nimshare.example.com/problems/link-expired",
"title": "Link expired",
"status": 410,
"detail": "This link expired on 2026-07-01T00:00:00Z.",
"instance": "/s/project-x"
}