Calling Briefcase on behalf of a member#
This guide is for an Application that wants to create a file in Silicon Briefcase for a Carbon or Silicon it already represents, without ever holding a Briefcase credential of its own. It is self-contained.
On-behalf-of (OBO) means IAM mints a proof for one exact request. Briefcase verifies that proof with IAM, consumes it, and then acts as the represented member — with that member's current role and tags, never more. There is no long-lived delegated key to store, leak, or revoke.
- Audience Application:
tos>briefcase - Briefcase API base:
https://backend.briefcase.teamofsilicons.com/api/v1 - IAM base:
https://backend.iam.teamofsilicons.com/
X-Org-ID, org_id and the {org_id} URL segment carry the Team ID; they
are wire names from the IAM contract, kept verbatim throughout this guide.
Choose an operation#
Briefcase supports the following proof-authorized operations. Discover the
audience's registered endpoints with
GET {iam}/api/v1/obo-access/applications/tos>briefcase/endpoints
(URL-encode the >). Register the fixed paths below before issuing proofs;
registration is separate in production and each imported IAM test Application.
| Endpoint ID | Method and registered path | Metadata schema |
|---|---|---|
briefcase.files.create |
POST /api/v1/obo/files |
path, name, content_type — all required strings |
briefcase.folders.create |
POST /api/v1/obo/folders/create |
Empty object {} |
briefcase.entries.list |
POST /api/v1/obo/entries/list |
Empty object {} |
briefcase.files.read |
POST /api/v1/obo/files/read |
Empty object {} |
briefcase.entries.trash |
POST /api/v1/obo/entries/trash |
Empty object {} |
briefcase.uploads.reserve |
POST /api/v1/obo/uploads/reserve |
Empty object {} |
briefcase.uploads.commit |
POST /api/v1/obo/uploads/commit |
Empty object {} |
briefcase.uploads.status |
POST /api/v1/obo/uploads/status |
Empty object {} |
briefcase.uploads.cancel |
POST /api/v1/obo/uploads/cancel |
Empty object {} |
- Large, slow, or recoverable uploads: use reserve → private transfer → fresh-authorized commit. Retain the logical operation UUID and reconcile an uncertain result with status. See delegated uploads.
- Small, immediate uploads: the compatible one-shot
files.createroute sends raw file bytes. Its proof must still be valid after the body arrives; the 5 TiB size ceiling does not guarantee a transfer fits the proof lifetime. A fresh proof after an uncertain one-shot result is a new upload attempt. - Folder hierarchy, browsing, download and deletion: use the JSON controls below with the represented member's current permissions.
The byte-only PUT /api/v1/obo/uploads/{upload_id}/content is not registered in
IAM. It uses the reservation's private upload capability, not an IAM proof or
member bearer. It cannot publish a file; only fresh-authorized commit can.
An endpoint missing from the selected audience's live catalog cannot receive a proof. Confirm registration and scope disclosure before sending either production or sandbox operations.
Before you start#
You need all five of these. Briefcase fails closed on any of them.
- Your Application is registered in IAM, in the same Team as Briefcase. OBO never crosses a Team. IAM derives the Team from the two Applications and refuses to accept one from you.
- A subject token: the member's IAM access token, issued to your
Application (
oat_…). A token issued to some other Application is refused. obo.issueon that subject token. Without it the exchange answers403 obo_subject_token_forbidden.self.membership.readandself.identity.readdisclosure, in both the subject token and Briefcase's currently approved scopes. Briefcase requires the delegated authorization snapshot and answers403when role or membership disclosure is missing. Never infer authority from an undisclosed (null) field.- Your Application's own secret, used only for HTTP Basic and the HMAC on the IAM exchange. It is never sent to Briefcase.
The member must be an active member of your Application's Team at verification time. Ending a session does not by itself extend or revoke IAM authority.
JSON controls and recoverable uploads#
All eight JSON operations use POST, Content-Type: application/json, and an
empty IAM metadata object. Bind the complete serialized JSON body, not file
bytes or a subset of its fields. Send X-App-ID and
X-IAM-OBO-Access-Proof, never a bearer. A supplied X-Org-ID must agree with
IAM; a sandbox also needs its separate IAM testing app secret.
| Operation | JSON inputs | Result |
|---|---|---|
| Folder create | operation_id, parent_path, name |
201 created entry |
| Entries list | Optional parent_id or path, filter, cursor, limit |
200 page; limit 1–100 |
| File read | entry_id; optional range, download |
200 or 206 bytes |
| Entry trash | operation_id, entry_id |
204; recoverable bin deletion |
| Upload reserve | operation_id, parent_path, name, content_type, size, sha256 |
200 status and an idle reservation's private capability |
| Upload commit | operation_id, upload_id |
200 status and published entry ID |
| Upload status | operation_id |
200 current state; no capability |
| Upload cancel | operation_id |
200 cancellation or cleanup-pending state |
Mutation operation_id values are caller-generated, non-nil UUIDs. Persist
the UUID and unchanged request before sending. After an uncertain result,
obtain a fresh proof for the same logical operation; never reuse the consumed
proof. A successful repeated commit does not publish a second version. A
repeated trash cannot delete an entry that has since been restored.
An empty creation parent_path selects the member's private app folder.
Otherwise the parent must already exist and be writable. Create a hierarchy
one folder at a time. Listing and reads retain ordinary permission filtering;
range, disposition and pagination values are proof-bound JSON, not override
headers or query parameters. See the exact JSON API contract.
For uploads, prepare the complete file manifest before requesting a proof:
briefcase app prepare-upload --operation-id "$UPLOAD_OPERATION_ID" \
--parent-path '' ./recording.webm > manifest.json
briefcase app request upload-reserve --body manifest.json --describe
# Ask IAM for a fresh proof using the described endpoint, method and exact body.
briefcase app request upload-reserve --body manifest.json \
--app-id 'tos>your-app' --capability-file upload.capability
# The hidden prompt accepts the proof; keep the returned upload_id.
briefcase app transfer "$UPLOAD_ID" ./recording.webm \
--capability-file upload.capability
Transfer only stages bytes. Prepare {"operation_id":"<original UUID>", "upload_id":"<returned UUID>"} in commit.json, describe upload-commit,
obtain a new IAM proof, and send briefcase app request upload-commit --body commit.json --app-id 'tos>your-app'. Use upload-status with a body containing only the
original operation_id after an uncertain response. The
upload guide specifies states, limits, cancellation
and cleanup. Keep capability files owner-only; they are credentials.
The official Rust client prepares the exact body and binding through typed
DelegatedReserveUpload, DelegatedCommitUpload, DelegatedUploadQuery and
DelegatedCancelUpload requests. Use the prepared value for both IAM proof
issuance and its matching SDK call. The operation map
lists every SDK method and CLI verb.
One-shot upload: the shape of the call#
For the compatible raw-byte endpoint, use these four steps:
1. Discover GET {iam}/api/v1/obo-access/applications/tos%3Ebriefcase/endpoints
2. Hash body_sha256 = lowercase hex SHA-256 of the EXACT file bytes
3. Exchange POST {iam}/api/v1/obo-access/exchanges -> access_proof (obo_…)
4. Call POST {briefcase}/obo/files with those exact bytes + the proof
Briefcase then calls IAM's verify itself, which consumes the proof, and only
then writes anything.
Exchange request#
{
"subject_token": "oat_…",
"audience": "tos>briefcase",
"endpoint_id": "briefcase.files.create",
"metadata": {
"path": "",
"name": "report.pdf",
"content_type": "application/pdf"
},
"request": {
"method": "POST",
"body_sha256": "…64 lowercase hex chars…"
}
}
Sent with HTTP Basic (your Application credential), an Idempotency-Key, and:
X-OBO-Timestamp: <unix seconds>
X-OBO-Signature: <lowercase hex HMAC-SHA256 with your app secret over>
{timestamp}.{UPPERCASE_METHOD}.{registered_path}.{body_sha256}.{idempotency_key}
registered_path is /api/v1/obo/files — the catalog path including
/api/v1, exactly as registered. The file never goes to IAM, only its digest.
The bytes travel straight to Briefcase, and the proof commits to what they will
be.
Calling Briefcase#
POST /api/v1/obo/files
X-App-ID: tos>your-app
X-IAM-OBO-Access-Proof: obo_…
Content-Type: application/octet-stream
<the raw file bytes>
| Header | Rule |
|---|---|
X-App-ID |
Your canonical {org_id}>{handle}. Must equal IAM's issuer_app_id. |
X-IAM-OBO-Access-Proof |
The access_proof from the exchange; always starts obo_. |
X-Org-ID |
Optional. If sent, it must agree with the Team IAM reports. |
X-Briefcase-App-Secret |
Sandbox only; see Sandboxes. |
Authorization |
Never. A bearer alongside a proof is 400 ambiguous_authentication. |
Returns 201 with the created entry.
Two rules that break integrations#
Exact bytes. Hash the file once and send that same buffer. Briefcase recomputes the digest from what it actually received, and any difference is a binding failure.
One proof, one request. A proof is valid for one verification or 60 seconds, whichever comes first. IAM consumes it exactly once; a retry is indistinguishable from a replay and is refused as one. Disable automatic HTTP retries. For a JSON mutation, keep its logical operation UUID and inputs but mint a fresh proof. The one-shot endpoint has no separate logical retry UUID; a fresh proof can create another version, so do not blindly resend it.
One-shot upload in detail#
Everything that decides where the file lands travels as proof-bound metadata, not as a header or query parameter, so a proof you legitimately obtained cannot be redirected somewhere else.
| Metadata key | Meaning |
|---|---|
path |
Destination folder. Empty selects the member's private Application folder. |
name |
The file name to create. |
content_type |
Media type of the bytes; empty defaults to application/octet-stream. |
- Size ceiling: 5 TiB, subject to quota and HTTP deadlines. The complete body must arrive before IAM verification and proof expiry. Use the staged protocol for large or slow transfers instead of relying on this ceiling.
- Versioning is automatic. A name an active file already carries publishes that file's next version rather than a duplicate.
- The proof identifier is the idempotency key, so a proof cannot create two files.
- Quota is the member's. The Team's storage and daily upload allowances apply exactly as they do to that member's own uploads.
This is not a recoverable operation: after an uncertain response, a fresh proof is a new attempt, not an idempotent retry. Read back the destination folder before re-sending if a duplicate would matter.
Where files go#
Every operation stays inside apps/<calling-app-id>/. The default empty
destination is apps/<app-id>/private/<represented-member-id>; a public
upload names apps/<app-id>/public. App folders are materialized on first use.
Normal private visibility, inherited grants and tag membership still apply.
An owner subject does not bypass the app namespace boundary. An entry's
originating-app metadata is attribution rather than a separate ownership rule.
Critical sharing operations#
Register briefcase.invitations.create at POST /api/v1/obo/invitations and
briefcase.link_access.update at POST /api/v1/obo/link-access as critical
IAM endpoints. They require user approval. Both use empty endpoint metadata;
the entire operation is bound into the exact JSON body SHA-256.
{"operation_id":"<uuid>","entry_id":"<uuid>","invitation":{"principal":{"type":"carbon","id":"alex:tos"},"access":["read"],"inherit":true}}
{"operation_id":"<uuid>","entry_id":"<uuid>","enabled":true}
Use delegated::DelegatedInvite and delegated::DelegatedLinkAccess in Rust,
or briefcase app request invite --body manifest.json --describe and
briefcase app request link-access --body manifest.json --describe in the CLI.
Mint a fresh IAM proof from the described binding, then repeat without
--describe and supply --app-id. Proofs are one-use, including uncertain
responses; keep the logical operation UUID and exact bytes for retries.
Errors#
Every error is {"error": {"code": …, "message": …, "request_id": …}}.
| Status | Typical code | Meaning and what to do |
|---|---|---|
400 |
ambiguous_authentication |
You sent Authorization with a proof. Send only the proof. |
400 |
invalid_app_id, invalid_org_id |
Use the canonical {org_id}>{handle} and a valid Team ID. |
401 |
unauthenticated |
Proof missing, malformed, expired, already consumed, or refused by IAM — including altered bytes. Do not retry — mint a new proof. |
403 |
forbidden |
A binding failed (issuer ≠ X-App-ID, wrong audience, path or Team mismatch, wrong endpoint), authorization was undisclosed, or the member cannot write to the destination. |
404 |
not_found |
The destination folder is missing or invisible to the member — deliberately indistinguishable. Do not retry unchanged. |
413 |
— | Body above the accepted size. |
422 |
invalid_obo_metadata, invalid_name, invalid_path, invalid_content_type |
The proof-bound metadata failed validation. Re-read the catalog and compare what you actually bound. |
429 / 507 |
— | Rate limited, or the Team's storage allowance is exhausted. |
503 |
— | IAM or a dependency is unavailable. This is not a refused proof; that is 401. |
At the IAM exchange, expect 403 (Team mismatch, inactive membership, missing
obo.issue), 404 (unknown endpoint_id or audience), 409 (proof consumed,
or an idempotency key reused with different input), 410 proof_expired (more
than 60 seconds elapsed), and 422 (metadata does not satisfy the schema).
Sandboxes#
A Briefcase testing environment is a full replica paired with one IAM test plane. To use OBO there, send the IAM testing app secret for the plane paired with the proof's IAM environment:
X-Briefcase-App-Secret: <ask_ test Application secret>
The test app secret does not replace the proof, and a proof from the wrong plane cannot fall back to production. Register the endpoint in the paired IAM test plane before testing. Full setup is in the testing-environment guide.
One-shot Rust call#
Use the official briefcase-client package. It never sends your bearer on this
call, never retries it, and never stores a session.
use briefcase_client::OnBehalfOfUpload;
// `proof` is the access_proof you just exchanged for exactly these bytes.
let entry = client
.create_file_on_behalf_of(
&OnBehalfOfUpload::file("tos>your-app", proof, "./report.pdf"),
)
.await?;
OnBehalfOfUpload::bytes(app_id, proof, bytes) takes an in-memory body instead.
The proof is redacted in debug output and consumed by the call. Keep the source
file unchanged between hashing and sending, and use a real file — not a symlink,
pipe or device.
One-shot CLI call#
Useful for trying the flow before you write code:
briefcase app upload --app-id 'tos>your-app' ./report.pdf # hidden proof prompt
briefcase app upload --app-id 'tos>your-app' --proof-stdin ./report.pdf < proof.txt
The destination, name and media type come from the proof, so the command takes
no path argument. Pass proofs through the hidden prompt or --proof-stdin,
never --proof in a shared shell, where the process list would expose them.
One-shot checklist#
- Same Team as Briefcase; subject token issued to your Application.
-
obo.issue,self.membership.read,self.identity.readpresent. - Digest taken over the exact bytes you will send.
-
/api/v1/obo/filesbound as the path, with its/api/v1prefix. - Destination bound as metadata, not as a header or query parameter.
- Exchange immediately before the call; no proof cached or reused.
-
X-App-ID+X-IAM-OBO-Access-Proofonly — noAuthorization. - Automatic HTTP retries disabled for this route.
Reference#
- API reference — every operation, filters, errors, limits
- IAM integration — registration, scopes, webhook approval
- Rust client · CLI · Testing environments