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

  1. Introduction
  2. Motivation
  3. URN Format Specification
  4. Using URNs with Existing Endpoints
  5. Discovering URNs
  6. Implementation Notes
  7. Error Handling
  8. Security Considerations
  9. Backward Compatibility
  10. Client Migration Guide
  11. 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


2. Motivation

Current Challenges

  1. Path Knowledge Required: Clients must construct full filesystem paths
  2. Path Coupling: Asset references break if files are moved
  3. Verbose Paths: Long pathnames in API calls are error-prone
  4. No Stable References: File identity tied to filesystem location

Benefits of URN Support

  1. Simplified Client Code: Reference assets by stable identifier
  2. Stable References: URNs remain valid regardless of filesystem changes
  3. Cleaner APIs: Shorter, more readable endpoint calls
  4. Future-Proof: Enables asset relocation without breaking references
  5. 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

  1. UUID Format: fileEID MUST be a valid UUID per RFC 4122
  2. Version Numbers: If present, MUST be positive integers (≥ 1)
  3. URL Encoding: URNs in URL paths MUST be URL-encoded
  4. Case Sensitivity: UUID portion is case-insensitive; prefix is case-sensitive

Detection Logic

Handlers detect URN format when pathname:

  1. Starts with urn:ayode:asset-eid:
  2. Contains a valid UUID after the prefix
  3. 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-EID header, 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

  1. List directory to discover files (existing endpoint)
  2. Extract eid from response
  3. Construct URN: urn:ayode:asset-eid:{eid}
  4. Store URN for future operations
  5. 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:

  1. Add URN parsing function to merlin/shared/paths.go:
    • ParseAssetURN(urnString string) (*ParsedAssetURN, *ErrorDetail)
  2. Add URN resolution function to merlin/interfaces/asset_system.go:
    • LookupAssetPathByEID(ctx, request, fileEID) (*ParsedAssetPath, *ErrorDetail)
  3. Add detection logic to existing handlers:
    • lambda/v1/assets/GET/main.go
    • lambda/v1/assets/POST/main.go
    • lambda/v1/assets/DELETE/main.go
    • lambda/v1/assets/PUT/main.go
    • lambda/v1/assets/PATCH/main.go
    • lambda/v1/assets/metadata/GET/main.go
    • lambda/v1/assets/status/GET/main.go
  4. Add stored procedure for URN resolution:
    • api_fileSystemLookupByEID_v1 (returns path components for a fileEID)

No changes required:


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

Rate Limiting


9. Backward Compatibility

Complete Backward Compatibility

Existing clients are completely unaffected:

New capability layers on top:

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:


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

  1. Cache URNs: Store URNs permanently alongside asset metadata
  2. Discover via Listings: Use directory listings to discover file EIDs
  3. Construct URNs: Simply prefix EID with urn:ayode:asset-eid:
  4. Handle 404s: Assets may be deleted; validate existence
  5. URL Encode: Always URL-encode URNs in path parameters
  6. 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

ĀYŌDÈ API Documentation

URN support enables alternate identifiers for existing asset operations. Refer to the interactive API documentation for details on:


Author

ĀYŌDÈ Development Team Codermerlin Academy Backend Architecture Date: 2025-11-19 Version: 2.0


Changelog

Version 2.0 (2025-11-19):

Version 1.0 (2025-11-19):