ARFC-1001: URN-Based Asset Identifiers
Status
| Implemented with modifications | Draft Date: 2025-11-19 | Last Call Date: 2025-12-03 | Publication Date: 2025-12-10 | Version: 2.0 |
Abstract
This RFC proposes enabling URN-based identifiers as an alternate format for the {pathname+} parameter in existing asset management endpoints. Clients can use stable URN identifiers (urn:ayode:asset-eid:{uuid}[@version]) instead of filesystem paths, providing path-agnostic asset access. URNs are constructed from the fileEID already returned in directory listings. No new endpoints or architectural changes required.
Table of Contents
- Introduction
- Motivation
- URN Format Specification
- Using URNs with Existing Endpoints
- 4.1 Read Asset
- 4.2 Write Asset
- 4.3 Delete Asset
- 4.4 Initialize Large Upload
- 4.5 Finalize Large Upload
- 4.6 Get Asset Metadata
- 4.7 Get Upload Status
- Discovering URNs
- Implementation Notes
- Error Handling
- Security Considerations
- Backward Compatibility
- Client Migration Guide
- Examples
1. Introduction
The Codermerlin Academy API currently provides asset management endpoints that accept a {pathname+} parameter:
/v1/assets/{pathname+}
Where pathname follows the pattern:
{realmEID}/{userID}/{path}/{filename}.{extension}[@version]
For complete documentation of existing asset endpoints, see the ĀYŌDÈ API Documentation.
This proposal enables these same endpoints to accept URN identifiers as an alternate format for the pathname parameter. Handlers detect URN format and resolve to path components transparently.
Key Design Principles
- Zero New Endpoints: URNs work with all existing asset endpoints
- Zero New API Resources: No Lambda functions, no API Gateway changes
- Self-Discoverable: URNs constructed from
fileEIDin directory listings - Transparent Resolution: Handlers detect URN format and resolve automatically
- Backward Compatibility: Path-based identifiers continue to work unchanged
- Minimal Changes: Only pathname parsing logic needs updating
2. Motivation
Current Challenges
- Path Knowledge Required: Clients must construct full filesystem paths
- Path Coupling: Asset references break if files are moved
- Verbose Paths: Long pathnames in API calls are error-prone
- No Stable References: File identity tied to filesystem location
Benefits of URN Support
- Simplified Client Code: Reference assets by stable identifier
- Stable References: URNs remain valid regardless of filesystem changes
- Cleaner APIs: Shorter, more readable endpoint calls
- Future-Proof: Enables asset relocation without breaking references
- Zero Endpoint Proliferation: Same endpoints, alternate identifier format
3. URN Format Specification
Syntax
urn:ayode:asset-eid:{fileEID}[@version]
Components
| Component | Description | Format | Required |
|---|---|---|---|
urn |
URN scheme identifier | Literal urn |
Yes |
ayode |
Namespace identifier | Literal ayode |
Yes |
asset-eid |
Asset EID namespace | Literal asset-eid |
Yes |
fileEID |
File external identifier | UUID (RFC 4122) | Yes |
@version |
Version number | @ followed by positive integer |
No |
Examples
Latest version:
urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890
Specific version:
urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890@3
Validation Rules
- UUID Format:
fileEIDMUST be a valid UUID per RFC 4122 - Version Numbers: If present, MUST be positive integers (≥ 1)
- URL Encoding: URNs in URL paths MUST be URL-encoded
- Case Sensitivity: UUID portion is case-insensitive; prefix is case-sensitive
Detection Logic
Handlers detect URN format when pathname:
- Starts with
urn:ayode:asset-eid: - Contains a valid UUID after the prefix
- Optionally ends with
@{version}
If detected, resolve URN to path components; otherwise, treat as traditional path.
4. Using URNs with Existing Endpoints
All existing asset endpoints accept URNs as an alternate format for the {pathname+} parameter. Zero new endpoints required.
4.1. Read Asset
Existing Endpoint:
GET /v1/assets/{pathname+}
Operation ID: readAssetV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN |
Query Parameters:
| Parameter | Type | Required | Values | Description |
|---|---|---|---|---|
encoding |
string | Yes | base64, text, rawBytes |
Response encoding format |
Headers:
| Header | Required | Format |
|---|---|---|
Authorization |
Yes | Bearer {jwt} |
X-Ayode-Asserted-Realm-EID |
Yes | urn:ayode:realm-eid:{uuid} |
Note: For details on authentication and the
X-Ayode-Asserted-Realm-EIDheader, see the API Documentation.
Example with Path:
curl -X GET \
"https://api.codermerlin.academy/v1/assets/f9e8d7c6-b5a4-3210-fedc-ba9876543210/~/documents/report.pdf?encoding=rawBytes" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
Example with URN:
curl -X GET \
"https://api.codermerlin.academy/v1/assets/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890?encoding=rawBytes" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
4.2. Write Asset
Existing Endpoint:
POST /v1/assets/{pathname+}
Operation ID: writeAssetV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN (version MUST NOT be specified) |
Headers:
| Header | Required | Description |
|---|---|---|
Authorization |
Yes | Bearer token |
X-Ayode-Asserted-Realm-EID |
Yes | Realm URN |
Content-Type |
Yes | MIME type of uploaded content |
Body: Binary file contents
Example with URN:
curl -X POST \
"https://api.codermerlin.academy/v1/assets/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210" \
-H "Content-Type: application/pdf" \
--data-binary @updated-document.pdf
4.3. Delete Asset
Existing Endpoint:
DELETE /v1/assets/{pathname+}
Operation ID: deleteAssetV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN (version MUST NOT be specified) |
Example with URN:
curl -X DELETE \
"https://api.codermerlin.academy/v1/assets/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
4.4. Initialize Large Upload
Existing Endpoint:
PUT /v1/assets/{pathname+}
Operation ID: initializeLargeAssetUploadV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN (version MUST NOT be specified) |
Body:
{
"contentLengthInBytes": 524288000
}
Example with URN:
curl -X PUT \
"https://api.codermerlin.academy/v1/assets/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210" \
-H "Content-Type: application/json" \
-d '{"contentLengthInBytes": 524288000}'
4.5. Finalize Large Upload
Existing Endpoint:
PATCH /v1/assets/{pathname+}
Operation ID: finalizeLargeAssetUploadV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN |
Example with URN:
curl -X PATCH \
"https://api.codermerlin.academy/v1/assets/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
4.6. Get Asset Metadata
Existing Endpoint:
GET /v1/assets/metadata/{pathname+}
Operation ID: readAssetMetadataV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN with optional version |
Example with URN:
curl -X GET \
"https://api.codermerlin.academy/v1/assets/metadata/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
4.7. Get Upload Status
Existing Endpoint:
GET /v1/assets/status/{pathname+}
Operation ID: readAssetUploadStatusV1
Path Parameter:
| Parameter | Type | Description |
|---|---|---|
pathname+ |
string | Either traditional path OR URL-encoded URN |
Example with URN:
curl -X GET \
"https://api.codermerlin.academy/v1/assets/status/urn%3Aayode%3Aasset-eid%3Aa1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
5. Discovering URNs
URNs are self-discoverable through existing directory listing endpoints. No new lookup endpoint is needed.
From Directory Listings
Directory listings already return fileEID for each entry:
Request:
curl -X GET \
"https://api.codermerlin.academy/v1/assets/f9e8d7c6-b5a4-3210-fedc-ba9876543210/~/documents/" \
-H "Authorization: Bearer eyJhbGc..." \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210"
Response:
{
"envelope": { ... },
"data": [
{
"entityType": "file",
"userBasename": "contract",
"userExtension": "pdf",
"eid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"version": 3,
"physicalSizeInBytes": 1234567,
"mimetype": "application/pdf"
}
]
}
Construct URN from EID:
const fileEID = entry.eid; // "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
const assetURN = `urn:ayode:asset-eid:${fileEID}`;
// Result: "urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Discovery Workflow
- List directory to discover files (existing endpoint)
- Extract
eidfrom response - Construct URN:
urn:ayode:asset-eid:{eid} - Store URN for future operations
- Use URN in place of path for all subsequent operations
No lookup API needed! URNs are trivially constructed from information already provided by directory listings.
6. Implementation Notes
Handler Detection Logic
Each existing handler that processes {pathname+} needs minimal modification:
// Pseudocode for handler modification
pathname := getPathParameter("pathname")
var parsedPath *shared.ParsedAssetPath
var errorDetail *shared.ErrorDetail
if strings.HasPrefix(pathname, "urn:ayode:asset-eid:") {
// URN format detected
parsedURN, err := shared.ParseAssetURN(pathname)
if err != nil {
return error
}
// Resolve URN to path components
parsedPath, err = interfaces.LookupAssetPathByEID(ctx, request, parsedURN.FileEID)
if err != nil {
return error
}
// Override version if specified in URN
if parsedURN.Version != nil {
parsedPath.Version = parsedURN.Version
}
} else {
// Traditional path format
parsedPath, err = shared.ParseAssetPathname(pathname)
if err != nil {
return error
}
}
// Continue with existing logic using parsedPath
Required Changes
Minimal code changes required:
- Add URN parsing function to
merlin/shared/paths.go:ParseAssetURN(urnString string) (*ParsedAssetURN, *ErrorDetail)
- Add URN resolution function to
merlin/interfaces/asset_system.go:LookupAssetPathByEID(ctx, request, fileEID) (*ParsedAssetPath, *ErrorDetail)
- Add detection logic to existing handlers:
lambda/v1/assets/GET/main.golambda/v1/assets/POST/main.golambda/v1/assets/DELETE/main.golambda/v1/assets/PUT/main.golambda/v1/assets/PATCH/main.golambda/v1/assets/metadata/GET/main.golambda/v1/assets/status/GET/main.go
- Add stored procedure for URN resolution:
api_fileSystemLookupByEID_v1(returns path components for a fileEID)
No changes required:
- ❌ No new API Gateway resources
- ❌ No new Lambda functions
- ❌ No new endpoints
- ❌ No database schema changes
- ❌ No interface signatures changed
7. Error Handling
All URN-based requests follow the standard ĀYŌDÈ error response format. For complete error handling documentation, see the ĀYŌDÈ API Documentation.
URN-Specific Error Cases
Invalid URN Format:
{
"envelope": {
"error": {
"errorType": "InvalidRequest_ValueOfIncorrectFormat",
"userMessage": "Invalid asset URN format",
"parameters": {
"pathname": "urn:invalid:format"
}
}
}
}
URN Not Found:
{
"envelope": {
"error": {
"errorType": "NotFound_EntityDoesNotExist",
"userMessage": "Asset URN not found",
"parameters": {
"fileEID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}
}
}
Version Specified Where Forbidden:
{
"envelope": {
"error": {
"errorType": "InvalidRequest_ValueSpecificationForbidden",
"userMessage": "Version specification forbidden in URN for write operations",
"parameters": {
"pathname": "urn:ayode:asset-eid:a1b2c3d4-...@3"
}
}
}
}
8. Security Considerations
For comprehensive security documentation including authentication flows, privilege management, and rate limiting policies, see the ĀYŌDÈ API Documentation.
Authorization
URN-based requests enforce the same privilege checks as path-based requests:
| Operation | Required Privilege |
|---|---|
| Read (GET) | urn:ayode:privilege-action:/file-system/file/read |
| Write (POST, PUT, PATCH) | urn:ayode:privilege-action:/file-system/file/write |
| Delete (DELETE) | urn:ayode:privilege-action:/file-system/file/delete |
URN Resolution Security
- Authorization checked before URN resolution
- URN resolution respects realm boundaries
- Unauthorized access returns
403 Forbidden(not404) - Prevents URN enumeration attacks
Rate Limiting
- URN-based and path-based requests share the same rate limits
- No separate rate limit buckets needed
- Same token bucket per operation type
9. Backward Compatibility
Complete Backward Compatibility
Existing clients are completely unaffected:
- ✅ All path-based requests work identically
- ✅ No behavior changes for existing API calls
- ✅ Same endpoints, same responses
- ✅ Zero breaking changes
New capability layers on top:
- ✅ URNs are alternate identifiers for the same resources
- ✅ Same authorization, same rate limits, same responses
- ✅ Path and URN formats can be mixed within same application
Comparison Matrix
For documentation of existing path-based asset operations, see the ĀYŌDÈ API Documentation.
| Operation | Path Format | URN Format | Endpoint |
|---|---|---|---|
| Read file | ✅ | ✅ | GET /v1/assets/{pathname+} |
| Write file | ✅ | ✅ | POST /v1/assets/{pathname+} |
| Delete file | ✅ | ✅ | DELETE /v1/assets/{pathname+} |
| Large upload init | ✅ | ✅ | PUT /v1/assets/{pathname+} |
| Large upload finalize | ✅ | ✅ | PATCH /v1/assets/{pathname+} |
| Get metadata | ✅ | ✅ | GET /v1/assets/metadata/{pathname+} |
| Get status | ✅ | ✅ | GET /v1/assets/status/{pathname+} |
| List versions | ✅ | ✅ | GET /v1/assets/{pathname+}@/ |
| Read directory | ✅ | ❌ | GET /v1/assets/{pathname+}/ |
Notes:
- Directory listing remains path-based only, as URNs identify files, not directories
- Directory listings provide
fileEIDfor constructing URNs
10. Client Migration Guide
Step 1: Discover URNs from Directory Listings
List the directory to get file EIDs:
# List directory
curl -X GET \
"https://api.codermerlin.academy/v1/assets/$REALM_EID/~/projects/" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID"
# Response includes EID for each file:
# {
# "data": [
# {
# "entityType": "file",
# "userBasename": "report",
# "userExtension": "pdf",
# "eid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
# ...
# }
# ]
# }
# Construct URN from EID
URN="urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Step 2: Store URNs
Persist URNs in your application:
{
"assetName": "Annual Report",
"assetURN": "urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"description": "Fiscal year 2025 annual report"
}
Step 3: Update API Calls
Replace path construction with URN:
Before (Path-Based):
const pathname = `${realmEID}/${userID}/${directory}/${filename}.${ext}`;
const url = `/v1/assets/${encodeURIComponent(pathname)}?encoding=rawBytes`;
After (URN-Based):
const url = `/v1/assets/${encodeURIComponent(assetURN)}?encoding=rawBytes`;
Same endpoint, simpler code!
Best Practices
- Cache URNs: Store URNs permanently alongside asset metadata
- Discover via Listings: Use directory listings to discover file EIDs
- Construct URNs: Simply prefix EID with
urn:ayode:asset-eid: - Handle 404s: Assets may be deleted; validate existence
- URL Encode: Always URL-encode URNs in path parameters
- Mix Freely: Use paths for discovery, URNs for known assets
11. Examples
Example 1: Discover and Use URN
# 1. List directory to discover file EID (one-time operation)
RESPONSE=$(curl -X GET \
"https://api.codermerlin.academy/v1/assets/$REALM_EID/~/documents/" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID")
# 2. Extract EID and construct URN
FILE_EID=$(echo $RESPONSE | jq -r '.data[] | select(.userBasename=="contract" and .userExtension=="pdf") | .eid')
URN="urn:ayode:asset-eid:$FILE_EID"
echo "Asset URN: $URN"
# 3. Read file using URN (same endpoint as path-based!)
curl -X GET \
"https://api.codermerlin.academy/v1/assets/$(echo $URN | jq -sRr @uri)?encoding=rawBytes" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID" \
-o contract.pdf
# 4. Update file using URN (same endpoint as path-based!)
curl -X POST \
"https://api.codermerlin.academy/v1/assets/$(echo $URN | jq -sRr @uri)" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID" \
-H "Content-Type: application/pdf" \
--data-binary @updated-contract.pdf
Example 2: Large File Upload with URN
URN="urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
ENCODED_URN=$(echo $URN | jq -sRr @uri)
# 1. Initialize upload (same endpoint!)
RESPONSE=$(curl -X PUT \
"https://api.codermerlin.academy/v1/assets/$ENCODED_URN" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID" \
-H "Content-Type: application/json" \
-d '{"contentLengthInBytes": 524288000}')
S3_URL=$(echo $RESPONSE | jq -r '.data.transientUploadURL')
# 2. Upload to S3
curl -X PUT "$S3_URL" \
-H "Content-Type: application/octet-stream" \
--data-binary @large-file.zip
# 3. Finalize upload (same endpoint!)
curl -X PATCH \
"https://api.codermerlin.academy/v1/assets/$ENCODED_URN" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID"
# 4. Poll status (same endpoint!)
while true; do
STATUS=$(curl -s -X GET \
"https://api.codermerlin.academy/v1/assets/status/$ENCODED_URN" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID")
IS_TERMINAL=$(echo $STATUS | jq -r '.envelope.asynchronousData.isTerminal')
CURRENT_STATUS=$(echo $STATUS | jq -r '.envelope.asynchronousData.status')
echo "Status: $CURRENT_STATUS"
if [ "$IS_TERMINAL" = "true" ]; then
if [ "$CURRENT_STATUS" = "completed" ]; then
echo "Upload completed!"
break
else
echo "Upload failed!"
exit 1
fi
fi
sleep 10
done
Example 3: Version Management with URN
URN="urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890"
ENCODED_URN=$(echo $URN | jq -sRr @uri)
# 1. List all versions (same endpoint pattern!)
curl -X GET \
"https://api.codermerlin.academy/v1/assets/$ENCODED_URN@/" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID"
# 2. Read specific version (same endpoint!)
URN_V2="urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890@2"
ENCODED_URN_V2=$(echo $URN_V2 | jq -sRr @uri)
curl -X GET \
"https://api.codermerlin.academy/v1/assets/$ENCODED_URN_V2?encoding=rawBytes" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID" \
-o version-2.pdf
# 3. Get metadata (same endpoint!)
curl -X GET \
"https://api.codermerlin.academy/v1/assets/metadata/$ENCODED_URN" \
-H "Authorization: Bearer $JWT" \
-H "X-Ayode-Asserted-Realm-EID: urn:ayode:realm-eid:$REALM_EID"
Example 4: JavaScript/TypeScript Client
class AssetClient {
private baseURL: string;
private jwt: string;
private realmURN: string;
constructor(baseURL: string, jwt: string, realmURN: string) {
this.baseURL = baseURL;
this.jwt = jwt;
this.realmURN = realmURN;
}
private headers() {
return {
'Authorization': `Bearer ${this.jwt}`,
'X-Ayode-Asserted-Realm-EID': this.realmURN
};
}
// Build URL with either path or URN - same endpoint!
private assetURL(pathOrURN: string): string {
return `${this.baseURL}/v1/assets/${encodeURIComponent(pathOrURN)}`;
}
async readAsset(pathOrURN: string, encoding: 'base64' | 'text' | 'rawBytes'): Promise<Blob | string> {
const url = `${this.assetURL(pathOrURN)}?encoding=${encoding}`;
const response = await fetch(url, {
method: 'GET',
headers: this.headers()
});
if (!response.ok) {
throw new Error(`Failed to read asset: ${response.statusText}`);
}
return encoding === 'rawBytes'
? await response.blob()
: await response.text();
}
async writeAsset(pathOrURN: string, content: Blob, mimeType: string): Promise<any> {
const response = await fetch(this.assetURL(pathOrURN), {
method: 'POST',
headers: {
...this.headers(),
'Content-Type': mimeType
},
body: content
});
if (!response.ok) {
throw new Error(`Failed to write asset: ${response.statusText}`);
}
return await response.json();
}
async listDirectory(path: string): Promise<any[]> {
const url = `${this.assetURL(path)}/`;
const response = await fetch(url, {
method: 'GET',
headers: this.headers()
});
if (!response.ok) {
throw new Error(`Failed to list directory: ${response.statusText}`);
}
const result = await response.json();
return result.data;
}
// Construct URN from file EID
static urnFromEID(fileEID: string): string {
return `urn:ayode:asset-eid:${fileEID}`;
}
}
// Usage example
const client = new AssetClient(
'https://api.codermerlin.academy',
'eyJhbGc...',
'urn:ayode:realm-eid:f9e8d7c6-b5a4-3210-fedc-ba9876543210'
);
// Works with paths
const pathContent = await client.readAsset(
'f9e8d7c6-b5a4-3210-fedc-ba9876543210/~/documents/report.pdf',
'rawBytes'
);
// Works with URNs - same method!
const urnContent = await client.readAsset(
'urn:ayode:asset-eid:a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'rawBytes'
);
// Discover URN from directory listing
const files = await client.listDirectory(
'f9e8d7c6-b5a4-3210-fedc-ba9876543210/~/documents'
);
const reportFile = files.find(f => f.userBasename === 'report' && f.userExtension === 'pdf');
const urn = AssetClient.urnFromEID(reportFile.eid);
// Store URN and use for all future operations
await client.writeAsset(urn, newContent, 'application/pdf');
References
Standards
- RFC 4122: A Universally Unique Identifier (UUID) URN Namespace
- RFC 8141: Uniform Resource Names (URNs)
ĀYŌDÈ API Documentation
- Interactive API Documentation: ĀYŌDÈ Backend APIs
URN support enables alternate identifiers for existing asset operations. Refer to the interactive API documentation for details on:
- Asset Management Operations:
readAssetV1,writeAssetV1,deleteAssetV1 - Large Upload Operations:
initializeLargeAssetUploadV1,finalizeLargeAssetUploadV1 - Metadata Operations:
readAssetMetadataV1 - Authentication & Security: Bearer token requirements, realm assertion, privilege checks
- Error Handling: Standard error response format and error types
Author
ĀYŌDÈ Development Team Codermerlin Academy Backend Architecture Date: 2025-11-19 Version: 2.0
Changelog
Version 2.0 (2025-11-19):
- Simplified design: URNs accepted in existing
{pathname+}parameter - Zero new endpoints: URNs discoverable via existing directory listings
- Implementation reduced to handler detection logic only
- True layering: no API changes, just alternate identifier format
Version 1.0 (2025-11-19):
- Initial proposal with separate
/v1/assets/by-urn/*endpoints