{"resource":"https://portfolios.agents.showcase.raidiam.io","resource_name":"Portfolios resource","resource_documentation":"https://portfolios.agents.showcase.raidiam.io/","authorization_servers":["https://api.agents.showcase.raidiam.io"],"bearer_methods_supported":["header"],"scopes_supported":[],"authorization_details_types_supported":["portfolios.read","portfolios.annotate"],"dpop_signing_alg_values_supported":["ES256"],"dpop_bound_access_tokens_required":false,"tls_client_certificate_bound_access_tokens":false,"resource_profile":{"profile_version":"2.0","treatment":"Reference implementation","treatment_note":"Reference implementation, built to demonstrate integration","operator":"Tarnbrook Platforms","domain":"tarnbrook-agents","summary":"A synthetic portfolio set behind two detail types: read a collection, or write a note on one record. Nothing here moves value.","read_only":false,"interface":"mcp","key_custody":{"custody":"supplied","description":"A public key registered on this resource's record, nothing issued. The authority vouches the supplied key by its kid; the private half never leaves this service."},"limits":{"per_transaction":null,"cumulative":null,"currency":"CHF"},"dpop_binding":{"mode":"bound","description":"This resource verifies a DPoP proof whenever one is presented, and requires one whenever the access token carries a cnf.jkt claim. Plain bearer tokens are still accepted while the authorization server is being moved onto sender constrained tokens."},"trust":{"authorization_server":"https://api.agents.showcase.raidiam.io","trust_anchor":"https://federation.directory.showcase.raidiam.io/federation_entity/7b60c9fa-00dd-46d9-80de-cd899ee5a58d","federation_required":true,"description":"The authorization server named above is trusted only while it resolves to the federation trust anchor named here, whose keys this resource holds from configuration. The chain is walked and each subordinate statement verified before any token is accepted, and the keys that verify a token are the ones the chain carries. The key set is resolved again when the shortest statement on the chain expires or when a token names a key the chain did not publish."},"authorization_details_profiles":[{"type":"portfolios.read","title":"portfolios.read","description":"Read discretionary portfolio records from named collections.","actions":["read"],"required_members":["type","collections"],"optional_members":[],"example":{"type":"portfolios.read","collections":["client-records"]}},{"type":"portfolios.annotate","title":"portfolios.annotate","description":"Write a note on a portfolio record. Moves nothing.","actions":["annotate"],"required_members":["type","collections"],"optional_members":[],"example":{"type":"portfolios.annotate","collections":["client-records"]}}],"authorization_details_type_tree":[{"name":"records.read","narrows_to":[]},{"name":"records.annotate","narrows_to":[]},{"name":"transfer.status","narrows_to":[]},{"name":"transfer.initiate","narrows_to":[]},{"name":"review.submit","narrows_to":[]},{"name":"portfolios.read","narrows_to":[]},{"name":"portfolios.annotate","narrows_to":[]}],"required_token_claims":[{"claim":"sub","required":true,"description":"The agent the authorization server admitted, or the person on whose authority it acts."},{"claim":"authorization_details","required":true,"description":"The RFC 9396 grant. Each tool names the detail type it requires."},{"claim":"act","required":false,"description":"The RFC 8693 actor chain when the token was exchanged."},{"claim":"cnf","required":false,"description":"The DPoP key thumbprint when the token is sender constrained."},{"claim":"trace_id","required":false,"description":"The trace id of the run, echoed on every decision."}],"policies":[{"id":"rs.authenticated_caller","title":"Every call carries a token from the named authorization server, resolved through the federation","statement":"Calls are accepted only with an access token issued by https://api.agents.showcase.raidiam.io, addressed to this resource as its audience, and verified against keys that the walked chain from that server to the pinned trust anchor vouches. A token minted for a different resource, or signed with a key the chain does not publish, is refused even when it is otherwise valid.","denial_reason":"missing_access_token, invalid_token, issuer_unresolved","enforced_at":"authorization","remedy":"Read this metadata document, then request a token from the authorization server it names, with this resource as the audience."},{"id":"rs.authority_gate","title":"Authority is the RFC 9396 detail type, not a scope","statement":"Each tool names one authorization_details type. The token must carry that type, or a type that narrows to it in the published type tree. Holding a scope, or holding authority for a neighbouring resource, does not admit the call.","denial_reason":"insufficient_authority","enforced_at":"authorization","remedy":"Obtain a token carrying the detail type the tool names."},{"id":"rs.dpop_binding","title":"Sender constrained tokens are bound to the key that holds them","statement":"A DPoP proof is verified whenever one is presented, and is required whenever the access token names a key in its cnf.jkt claim. Each proof is accepted once, so a captured proof cannot be replayed.","denial_reason":"dpop_proof_required, invalid_dpop_proof, dpop_key_mismatch, dpop_proof_replayed, access_token_not_dpop_bound","enforced_at":"authorization","remedy":"Request the access token with a DPoP proof so the authorization server binds it to your key, then send a fresh proof with every call."},{"id":"rs.request_validity","title":"A malformed request is refused with the reason it was malformed","statement":"Arguments are validated before any business rule runs, and the refusal names the argument at fault rather than returning a bare failure.","denial_reason":"tool_error, invalid_amount, unknown_tool","enforced_at":"execution","remedy":"Correct the named argument. The tool schemas are published in this document."},{"id":"portfolios.collection_granted","title":"A grant names the collections it reaches","statement":"Both detail types carry a collections member. A call that names a collection outside the grant is refused before any record is read, and the refusal names the collection at fault.","denial_reason":"collection_not_granted","enforced_at":"authorization","remedy":"Ask for a grant whose collections member names the collection."},{"id":"portfolios.annotate_moves_nothing","title":"A note changes a record and never moves value","statement":"portfolios.annotate writes a note against one record. There is no path from this resource to a transfer, so the widest authority it can exercise is a sentence on a file. portfolios.read reads and changes nothing at all.","denial_reason":"record_not_found","enforced_at":"execution","remedy":"Name a record that exists in a collection the grant reaches."}],"tools":[{"name":"read_portfolios","description":"Read discretionary portfolio records from a collection.","required_authorization_details_type":"portfolios.read","mutates":false,"http_method":"POST","http_endpoint":"https://portfolios.agents.showcase.raidiam.io/api/read_portfolios","mcp_tool":"read_portfolios"},{"name":"annotate_portfolio","description":"Write a note on a portfolio record. Moves nothing.","required_authorization_details_type":"portfolios.annotate","mutates":true,"http_method":"POST","http_endpoint":"https://portfolios.agents.showcase.raidiam.io/api/annotate_portfolio","mcp_tool":"annotate_portfolio"}],"interfaces":{"mcp":{"transport":"streamable-http","endpoint":"https://portfolios.agents.showcase.raidiam.io/mcp","description":"Model Context Protocol over Streamable HTTP. The same authority gate applies as on the HTTP endpoints."},"http":{"style":"json","endpoint_template":"https://portfolios.agents.showcase.raidiam.io/api/{tool}","description":"A plain JSON twin of every tool, sharing one handler with the MCP surface."}},"observability":{"decisions_endpoint":"https://portfolios.agents.showcase.raidiam.io/decisions","description":"The most recent admission decisions, allowed and refused, each carrying the checks that ran, the policy that decided it and the values the decision turned on.","trace":{"request_header":"x-trace-id","also_accepted":["x-correlation-id","traceparent","a trace_id argument","the access token trace_id claim","the access token txn claim"],"response_header":"x-trace-id","echoed_in":["the response body of every allowed call","the body of every refusal","every decision log entry"],"lookup":"https://portfolios.agents.showcase.raidiam.io/decisions?trace={id}","description":"Send the identifier of whatever triggered the work and this resource records its decision under that identifier rather than minting its own. One id therefore runs from the trigger, through the token issued for it, to the decision made here, and the decisions taken under it can be read back in order."},"recorded_per_decision":["the trace id and where it came from","the authorization server, the published key that verified the token, the hops walked to the pinned trust anchor and when the key set was resolved","the agent identity that made the call, as the authorization server asserted it in the subject or the RFC 8693 act chain","the authorization_details types held against the type the tool required","the checks that ran, in order, with their outcomes","the outcome, and for a refusal the policy that decided it and the values it turned on"]}}}